Building Backend APIs for Mobile Apps (iOS, Android, Flutter) on DomainIndia Hosting
What mobile backends must handle
Every mobile app (iOS, Android, React Native, Flutter) talks to a backend for:
- User signup / login (email, Google, Apple, phone OTP)
- Content feeds — loaded lazily, paginated
- File uploads (profile photos, documents, audio)
- Push notifications
- Real-time updates (chat, live scores, order tracking)
- Analytics events
- Payments (Razorpay/Stripe for Indian + global markets)
- Offline sync (app works without internet, syncs on reconnect)
Which DomainIndia plan?
| App stage | MAU (monthly active users) | Recommended plan |
|---|---|---|
| Prototype / testing | <1,000 | Shared cPanel with Node.js or Laravel |
| MVP / growing | 1,000 – 10,000 | VPS Starter (2 GB) |
| Production / scaling | 10,000 – 100,000 | VPS Business (4 GB) + Cloudflare CDN |
| High-scale | 100,000+ | Multi-VPS + load balancer + managed PostgreSQL |
Mobile backends are API-only (no HTML rendering), so they're efficient. A 2 GB VPS easily handles 10,000 MAU for a typical social/commerce app.
Choosing the API style
REST — simple, works everywhere, easy caching. Best default.
GraphQL — thin clients love minimal payloads over 3G. See our GraphQL APIs article.
gRPC — internal microservices; rarely used client-side for mobile (except streaming/IoT).
WebSockets / Server-Sent Events — real-time (chat, live trackers).
For most Indian apps: REST with JSON + WebSockets for real-time features. Add GraphQL only if you have multiple mobile platforms with different field needs.
Authentication patterns
Pattern 1 — JWT with refresh tokens
Classic for mobile. Apps can't keep a cookie-based session easily.
- User logs in with email + password (or social OAuth)
- Backend issues:
- Short-lived access token (15 min)
- Long-lived refresh token (30 days), stored encrypted on device
- Every API call sends
Authorization: Bearer <access_token> - Before expiry, app POSTs refresh token → gets new access token
- Refresh token can be revoked server-side (logout all devices)
Node.js example:
import jwt from 'jsonwebtoken';
function issueTokens(userId) {
const accessToken = jwt.sign({ sub: userId }, ACCESS_SECRET, { expiresIn: '15m' });
const refreshToken = jwt.sign({ sub: userId, type: 'refresh' }, REFRESH_SECRET, { expiresIn: '30d' });
// Store hash of refresh token in DB for revocation
db.refreshToken.create({ userId, hash: sha256(refreshToken), expiresAt: ... });
return { accessToken, refreshToken };
}
// Client calls POST /auth/refresh with the refresh token:
app.post('/auth/refresh', async (req, res) => {
const { refreshToken } = req.body;
const payload = jwt.verify(refreshToken, REFRESH_SECRET);
const stored = await db.refreshToken.findFirst({ where: { hash: sha256(refreshToken) } });
if (!stored || stored.revoked) return res.status(401).json({ error: 'Invalid' });
res.json(issueTokens(payload.sub));
});Pattern 2 — OAuth (Sign in with Apple / Google)
Mandatory for iOS apps that offer any other login (App Store rule). Google Sign-In covers Android.
- iOS: Apple Sign-In, returns identity token, verify signature against Apple's JWK
- Android: Google Sign-In, same pattern with Google JWK
Your backend stores a provider + providerUserId → internalUserId mapping.
Pattern 3 — Biometric unlock (local)
Device unlocks the refresh token using FaceID / fingerprint, then sends it to your API. Your API doesn't know or care about biometrics — it just sees refresh → access.
Push notifications
Two providers: Firebase Cloud Messaging (FCM) for Android + cross-platform, Apple Push Notification service (APNs) for iOS.
Easiest path: use FCM for both. Firebase handles APNs relay for you.
- Create Firebase project, add iOS + Android apps
- Download google-services.json (Android) + GoogleService-Info.plist (iOS)
- App registers with FCM on first launch → gets device token → POSTs to your
/push/registerendpoint - Backend stores
userId ↔ deviceTokenmapping - To notify:
```bash
curl -X POST https://fcm.googleapis.com/v1/projects/YOUR_PROJECT/messages:send
-H "Authorization: Bearer $ACCESS_TOKEN"
-H "Content-Type: application/json"
-d '{"message":{"token":"DEVICE_TOKEN","notification":{"title":"Order shipped","body":"Track now"}}}'
```
Libraries: firebase-admin (Node), pyfcm (Python), kreait/firebase-php.
File uploads
Mobile devices upload photos, videos, documents. Strategies:
Direct-to-S3 (or Backblaze B2) with presigned URLs — recommended.
- Client requests
POST /upload-urlwith filename + content-type - Backend returns a 10-minute signed URL to upload directly to S3
- Client PUTs the file to S3; no hop through your API
- Client notifies backend with the final S3 key
Saves your VPS bandwidth + CPU. Scales infinitely.
Through-your-API upload — only for small files (<5 MB). Simpler but consumes VPS RAM/bandwidth.
import multer from 'multer';
const upload = multer({ limits: { fileSize: 5 * 1024 * 1024 }, dest: '/tmp/' });
app.post('/upload', upload.single('file'), async (req, res) => {
const key = `users/${req.user.id}/${Date.now()}-${req.file.originalname}`;
await s3.putObject({ Bucket: 'uploads', Key: key, Body: fs.createReadStream(req.file.path) });
res.json({ url: `https://cdn.yourcompany.com/${key}` });
});Offline sync
Users in India's metro-to-tier-2 transitions (weak 4G, metro tunnels) demand offline-first apps.
Patterns:
- Optimistic writes — app writes to local SQLite first, then syncs. Show "syncing..." indicator.
- Pull-based sync with timestamps —
GET /sync?since=<last_sync_at>returns changes. Client applies. - Realm / WatermelonDB / PowerSync — libraries that handle sync for you.
Backend needs:
updatedAttimestamp on every row (auto-update on change)- Soft deletes (flag
deletedAt, don't hard-delete) - Tombstone endpoint listing deleted IDs since timestamp
Payments (Indian market)
Primary: Razorpay (UPI, cards, netbanking, wallets, subscriptions). Alternative: Stripe (cards, international).
Mobile integration uses their official SDKs — your backend creates the order on Razorpay API, passes order ID to app, app opens Razorpay Checkout, app sends signature back to backend for verification. See our Razorpay Integration article.
Versioning your API
Mobile apps don't auto-update — expect users running 6-month-old app versions for years.
Pattern: version your API URL (/v1/, /v2/) and keep old versions running indefinitely.
GET /v1/feed # legacy — keep forever
GET /v2/feed # current
GET /v3/feed # nextIf you can't maintain N versions forever, use feature-flagged responses keyed by the User-Agent or a custom X-App-Version header.
Security essentials
FAQ
Yes for prototypes and small apps (<1K users). Node.js via Setup Node.js App, or Laravel/Rails for traditional stacks. For serious traffic, upgrade to VPS.
Yes. Mobile apps can't "hotfix" server bugs — apps run against whatever backend version they target. Never point new app builds at production until tested on staging.
Typical stack on DomainIndia: VPS Business (~₹3,000/mo) + PostgreSQL managed or self-hosted + S3-compatible storage (~₹500/mo) + Cloudflare Free + FCM Free = ~₹4,000/mo total. Scales linearly with active usage.
No. Your API is stack-agnostic. Pick frontend based on team skills. Backend stays the same.
Firebase is faster to launch (auth, DB, push, storage bundled) but vendor-locked and costly past ~10K MAU. Custom on DomainIndia gives you full control and predictable costs.
Build your mobile API with predictable costs and full control. See our VPS plans