Every iOS, Android, React Native or Flutter app that has logins, feeds, uploads or payments needs a backend API. This guide covers the decisions that matter for a mobile backend: the API style, authentication, push notifications, file uploads, offline sync, payments, versioning and security, and which kind of Domain India hosting suits each stage of your app.
Start with a REST/JSON API, short-lived access tokens with rotating refresh tokens, FCM for push on both platforms, presigned URLs for uploads and versioned endpoints so old app builds keep working. A small request/response API can run on shared hosting or the App Platform; once you need real-time features, background workers or Redis, move to a VPS.
1. What a mobile backend must handle
Every mobile app (iOS, Android, React Native, Flutter) talks to a backend for:
- sign-up and login (email, Google, Apple, phone OTP);
- content feeds, loaded lazily and paginated;
- file uploads (profile photos, documents, audio);
- push notifications;
- real-time updates (chat, live scores, order tracking);
- analytics events;
- payments (Razorpay or Stripe for Indian and global customers);
- offline sync (the app works without internet and syncs on reconnect).
2. Which Domain India hosting fits your stage?
User numbers below are rough guides; what matters is what your backend needs to run.
| App stage | What the backend needs | Where it fits |
|---|---|---|
| Prototype or small app | A request/response API (Node.js, Python or PHP) and MySQL | cPanel or DirectAdmin shared hosting, with Setup Node.js App or Setup Python App |
| MVP with a Node.js or Docker API | Deploys from GitHub, PostgreSQL, free SSL | App Platform |
| Growing app with real-time features | WebSockets, queue workers, Redis, your own database tuning | VPS (self-managed, full root access) |
| High scale | Several servers, a load balancer, managed services | Several VPSs; plan the architecture carefully |
What decides the move:
- Shared hosting runs request/response apps only. Long-running processes such as WebSocket servers and queue workers are stopped, cron runs at most every 4 minutes, there's no Redis, and Composer isn't pre-installed (run
composer.pharover jailed SSH, or buildvendor/locally and upload it). - The App Platform auto-detects Node.js apps (other languages need your own Dockerfile) and includes PostgreSQL and free SSL, but has no WebSockets, no Redis and no SSH.
- A VPS (from ₹553 a month, excluding GST) gives you full root access for anything else. It is self-managed and includes no backups, so you patch and back it up yourself.
Mobile backends are API-only (no HTML rendering), so a small server goes a long way. Load-test your own API before you size a server.
3. Choosing the API style
REST: simple, works everywhere, easy to cache. The best default.
GraphQL: lets each screen ask for exactly the fields it needs, which keeps payloads small on slow mobile networks. See our GraphQL articles.
gRPC: mostly for internal services; rarely called directly from mobile apps.
WebSockets or Server-Sent Events: for real-time features such as chat and live tracking. See real-time sync and push notifications for mobile apps.
For most apps: REST with JSON, plus WebSockets for the real-time screens. Add GraphQL when several clients need very different data from the same endpoints.
4. Authentication patterns
Pattern 1: JWT access tokens with refresh tokens
The usual choice for mobile, where cookie sessions are awkward.
- Log in.The user signs in with email and password, a social login or an OTP.
- Issue two tokens.A short-lived access token (for example 15 minutes) and a long-lived refresh token (for example 30 days), kept in the Keychain on iOS or the Keystore-backed encrypted storage on Android.
- Call the API.Every request sends
Authorization: Bearer ACCESS_TOKEN. - Refresh.When the access token expires, the app sends the refresh token and gets a new pair. The old refresh token stops working (rotation).
- Revoke.Logout, or "log out of all devices", deletes the stored refresh tokens.
Node.js example:
import jwt from 'jsonwebtoken';
import crypto from 'node:crypto';
const sha256 = (s) => crypto.createHash('sha256').update(s).digest('hex');
const DAY = 24 * 60 * 60 * 1000;
async function issueTokens(userId) {
const accessToken = jwt.sign({ sub: userId }, ACCESS_SECRET, { expiresIn: '15m' });
const refreshToken = crypto.randomBytes(32).toString('base64url'); // opaque, not a JWT
await db.refreshToken.create({
data: { userId, hash: sha256(refreshToken), expiresAt: new Date(Date.now() + 30 * DAY) },
});
return { accessToken, refreshToken };
}
app.post('/auth/refresh', async (req, res) => {
const stored = await db.refreshToken.findFirst({
where: { hash: sha256(String(req.body.refreshToken ?? '')) },
});
if (!stored || stored.revoked || stored.expiresAt < new Date()) {
return res.status(401).json({ error: 'invalid_refresh_token' });
}
// Rotate: the old refresh token can't be used again
await db.refreshToken.update({ where: { id: stored.id }, data: { revoked: true } });
res.json(await issueTokens(stored.userId));
});If a revoked refresh token is ever presented again, treat it as stolen and revoke all of that user's tokens. More detail is in JWT security best practices.
Pattern 2: Sign in with Apple and Google
Social login removes password handling. Apple's App Review rules require iOS apps that offer a third-party login (such as Google) to also offer a privacy-focused login option; Sign in with Apple is the usual way to meet that.
- iOS: Sign in with Apple returns an identity token (a JWT). Verify its signature against Apple's published keys, and check the issuer, audience and expiry.
- Android: Google Sign-In (Credential Manager) works the same way, with Google's keys.
Your backend stores a provider + providerUserId → internalUserId mapping.
Pattern 3: biometric unlock (on the device)
Face ID or the fingerprint sensor unlocks the stored refresh token on the device, and the app then uses it as normal. Your API never sees biometrics; it only sees a refresh request.
5. Push notifications
There are two delivery services: Firebase Cloud Messaging (FCM) for Android and Apple Push Notification service (APNs) for iOS. The simplest path is FCM for both, because Firebase relays to APNs for you.
- Set up Firebase.Create a project and add your iOS and Android apps.
- Add the config files.
google-services.json(Android) andGoogleService-Info.plist(iOS), plus an APNs key uploaded in Firebase. - Register devices.On first launch the app gets an FCM token and POSTs it to your
/push/registerendpoint. - Store the mapping.Keep
userId ↔ deviceToken, and update it whenever the token changes. - Send.Call the FCM HTTP v1 API from your backend, ideally through the Firebase Admin SDK.
The raw HTTP v1 call looks like this (the access token is a short-lived OAuth token from your service account, which the Admin SDKs handle for you):
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 for Node.js and for Python, and kreait/firebase-php for PHP (on shared hosting, install it with Composer on your own computer and upload vendor/).
6. File uploads
Direct-to-storage with presigned URLs (recommended):
- the app asks your API for an upload URL (
POST /upload-urlwith the file type and size); - the API checks the request and returns a presigned URL valid for a few minutes, for S3, Cloudflare R2 or Backblaze B2;
- the app uploads straight to storage, with no hop through your server;
- the app tells your API the final object key.
This saves your server's bandwidth, CPU and upload limits. See Cloudflare Workers and R2 storage for an R2 example.
Upload through your API suits only small files:
import multer from 'multer';
import fs from 'node:fs';
import crypto from 'node:crypto';
import { PutObjectCommand } from '@aws-sdk/client-s3';
const upload = multer({ dest: '/tmp/uploads', limits: { fileSize: 5 * 1024 * 1024 } });
app.post('/upload', upload.single('file'), async (req, res) => {
const allowed = { 'image/jpeg': 'jpg', 'image/png': 'png', 'image/webp': 'webp' };
const ext = allowed[req.file?.mimetype];
if (!ext) return res.status(415).json({ error: 'unsupported_type' });
// Never use the client's file name in the key
const key = `users/${req.user.id}/${crypto.randomUUID()}.${ext}`;
await s3.send(new PutObjectCommand({
Bucket: 'uploads', Key: key,
Body: fs.createReadStream(req.file.path), ContentType: req.file.mimetype,
}));
fs.unlink(req.file.path, () => {});
res.json({ key });
});If a PHP API on shared hosting receives uploads itself, the upload limit is 256 MB on cPanel and 64 MB on DirectAdmin.
7. Offline sync
Weak 4G, lifts and metro tunnels mean many users are offline for part of the day, so plan for it:
- Optimistic writes: the app writes to local SQLite first, then syncs, with a "syncing" indicator.
- Pull-based sync with timestamps:
GET /sync?since=LAST_SYNC_TIMESTAMPreturns everything changed since then. - Sync libraries such as PowerSync or WatermelonDB handle much of this for you. (MongoDB's Atlas Device Sync for Realm has been discontinued.)
Your backend needs:
- an
updatedAttimestamp on every row, set on each change; - soft deletes (a
deletedAtcolumn instead of deleting the row); - a way to list IDs deleted since a timestamp, so apps can remove them too.
8. Payments in India
The usual choice is Razorpay (UPI, cards, netbanking, wallets, subscriptions); Stripe suits international cards.
The flow is the same with both official mobile SDKs: your backend creates the order through the gateway's API, passes the order ID to the app, the app opens the gateway's checkout, and the app sends the result back to your backend, which verifies the signature before marking the order paid. Also handle the gateway's webhook, because the app may close before it reports back. See our Razorpay integration article.
9. Versioning your API
Users don't all update their apps, so old builds will call your API for months or years.
Put a version in the URL (/v1/, /v2/) and keep the old version running for as long as a meaningful share of users is on builds that call it:
GET /v1/feed # older app builds
GET /v2/feed # currentHave the app send an X-App-Version header. It lets you adjust responses for older builds and, when you must, return a "please update" response below a minimum version.
10. Security essentials
FAQ
Can I host a mobile backend on Domain India shared hosting?
Yes, for prototypes and small request/response APIs, using Setup Node.js App or Setup Python App on cPanel or DirectAdmin, or PHP. WebSocket servers and queue workers are stopped on shared hosting and there is no Redis, so real-time features need a VPS.
Do I need separate development, staging and production backends?
Yes. You can't instantly fix an app build that's already on users' phones, so test every new app build against a staging API before you point it at production.
How much does a mobile backend cost?
It depends on traffic and features. A Domain India VPS starts at ₹553 a month excluding GST; add object storage, and note that FCM push is free from Google. Load-test your own API to size the server rather than relying on user counts.
React Native, Flutter or native: does the backend change?
No. A REST or GraphQL API works the same for every client. Choose the app framework by your team's skills; the backend stays the same.
Should I use Firebase instead of my own backend?
Firebase is faster to launch because auth, database, push and storage come together, but you are tied to Google's services and pricing, which grows with usage. Your own API gives you control of your data and costs; many apps use their own API and still use Firebase only for push.
Where does real-time chat or live tracking run?
On a VPS. WebSocket servers are long-running processes, which are stopped on shared hosting, and the App Platform does not support WebSockets. Keep the REST API wherever suits you and run the socket server on the VPS.
Ready to build your app's backend? Start small on cPanel hosting, deploy a Node.js API on the App Platform, or take full control on a VPS. Not sure which fits? Open a support ticket.
Full root access for your API, database, workers and WebSocket server, from ₹553 a month excluding GST.
See our VPS plans