# Building Backend APIs for Mobile Apps (iOS, Android, Flutter) on DomainIndia Hosting
TL;DR
Mobile apps need a backend. This guide covers REST vs GraphQL for mobile, authentication (JWT, OAuth, biometric), push notifications (FCM + APNs), file uploads, offline sync, and choosing the right DomainIndia hosting tier for your app's stage.
## 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](https://domainindia.com/support/kb/category/dev-graphql).
**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.
1
User logs in with email + password (or social OAuth)
3
Every API call sends Authorization: Bearer <access_token>
4
Before expiry, app POSTs refresh token → gets new access token
5
Refresh token can be revoked server-side (logout all devices)
Node.js example:
```javascript
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.
1
Create Firebase project, add iOS + Android apps
2
Download google-services.json (Android) + GoogleService-Info.plist (iOS)
3
App registers with FCM on first launch → gets device token → POSTs to your /push/register endpoint
4
Backend stores userId ↔ deviceToken mapping
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-url` with 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.
```javascript
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:
1. **Optimistic writes** — app writes to local SQLite first, then syncs. Show "syncing..." indicator.
2. **Pull-based sync with timestamps** — `GET /sync?since=` returns changes. Client applies.
3. **Realm / WatermelonDB / PowerSync** — libraries that handle sync for you.
Backend needs:
- `updatedAt` timestamp 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](https://domainindia.com/support/kb/razorpay-integration-php-nodejs).
## 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 # next
```
If 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
Q
Can I host a mobile backend on shared cPanel?
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.
Q
Do I need a separate dev / staging / production?
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.
Q
How much does mobile backend cost at 100K users?
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.
Q
React Native vs Flutter vs native — does the backend change?
No. Your API is stack-agnostic. Pick frontend based on team skills. Backend stays the same.
Q
Should I use Firebase instead of a custom backend?
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.