Back to Blog
Tutorial9 min read2026-07-17

How to Deploy a Backend for Your Expo / React Native App (2026)

Your Expo app ships to the App Store, but the API, database, and over-the-air update server live somewhere. Here's how to stand up a production React Native backend on PandaStack in an afternoon.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Everyone talks about shipping the React Native app. Nobody talks about the boring half: the API it phones home to, the Postgres behind that API, the cron that expires stale sessions, and the webhook endpoint your payment provider hammers at 3am. Expo (https://docs.expo.dev) is fantastic at the client — EAS Build, EAS Update, push notifications — but it deliberately doesn't run your backend. That's on you. I run PandaStack, so I'll show you the backend half end to end, and I'll be honest about the parts Expo already does better than I could.

What Expo gives you, and what it doesn't

EAS (Expo Application Services) handles the mobile-specific work brilliantly: cloud builds for iOS/Android, over-the-air JS updates so you can push a bug fix without an App Store review, and a push notification service. Keep using all of that — there's no reason to reinvent it.

What EAS does not give you is a place to run server code, a managed database, or scheduled jobs. That's the gap we're filling. A typical React Native app needs:

  • An HTTP API (auth, user data, business logic)
  • A database (Postgres is the sane default)
  • Somewhere to receive webhooks (Stripe, RevenueCat, Twilio)
  • Scheduled cleanup / digest jobs
  • Optionally, a self-hosted OTA update endpoint if you outgrow EAS Update

PandaStack runs all of that. The mobile app stays on Expo; the server lives here.

Step 1: A minimal API your app can talk to

We'll use a plain Node + Express API because it's the least surprising thing to debug from a phone on a hotel wifi. server.js:

import express from 'express'
import postgres from 'postgres'

const sql = postgres(process.env.DATABASE_URL, { ssl: 'require' })
const app = express()
app.use(express.json())

app.get('/health', (_req, res) => res.json({ ok: true }))

app.get('/me/:id', async (req, res) => {
  const [user] = await sql`select id, name, created_at from users where id = ${req.params.id}`
  if (!user) return res.status(404).json({ error: 'not found' })
  res.json(user)
})

const port = process.env.PORT || 8080
app.listen(port, () => console.log(`api on :${port}`))

Note process.env.DATABASE_URL. On PandaStack, when you attach a managed database to an app, that variable is injected for you — you don't paste credentials anywhere.

Step 2: Dockerfile

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 8080
CMD ["node", "server.js"]

Step 3: Create the database and deploy

From the dashboard (https://dashboard.pandastack.io):

  1. 1Databases → New Database → PostgreSQL. On the free tier you get one database — plenty for launch.
  2. 2New App → connect your Git repo. PandaStack auto-detects the Dockerfile (or, if you delete it, the Node buildpack), builds with rootless BuildKit, and deploys.
  3. 3In the app's settings, attach the database. DATABASE_URL now appears in the app's environment automatically.
  4. 4Push to your default branch and every commit redeploys.

Prefer the terminal? The CLI does the same:

npm install -g @pandastack/cli
panda login
panda deploy

Your API is now at https://your-app.pandastack.io. Point your Expo app's API base URL at it.

Step 4: Wire the mobile app to it

In your Expo project, keep the base URL in an env-driven config so dev and prod differ:

// api.js
const BASE = process.env.EXPO_PUBLIC_API_URL // e.g. https://your-app.pandastack.io

export async function getMe(id) {
  const r = await fetch(`${BASE}/me/${id}`)
  if (!r.ok) throw new Error(`HTTP ${r.status}`)
  return r.json()
}

EXPO_PUBLIC_ variables are inlined at build time by Expo — never put secrets there. Secrets (your Stripe secret key, database password) live only on the server, in PandaStack's encrypted environment variables.

Step 5: The webhook endpoint everyone forgets

If you sell subscriptions through RevenueCat or the App Store, you need a server endpoint to receive events — a mobile app can't be trusted to report its own purchases. Add a route:

app.post('/webhooks/revenuecat', async (req, res) => {
  const event = req.body.event
  if (event?.type === 'INITIAL_PURCHASE') {
    await sql`update users set pro = true where id = ${event.app_user_id}`
  }
  res.sendStatus(200)
})

Because this runs on a real server with a stable URL and automatic SSL, you can paste https://your-app.pandastack.io/webhooks/revenuecat straight into the provider's dashboard.

Step 6: A scheduled cleanup job

Mobile apps accumulate junk: unverified accounts, expired magic links, orphaned uploads. A PandaStack cronjob is a container that runs on a schedule and exits — no idle server to pay for.

// cleanup.js — its own tiny image, run daily
import postgres from 'postgres'
const sql = postgres(process.env.DATABASE_URL, { ssl: 'require' })
const n = await sql`delete from users where verified = false and created_at < now() - interval '7 days'`
console.log(`pruned ${n.count} stale accounts`)
await sql.end()

Create it under Cronjobs, set the schedule (0 3 * * * for 3am daily), attach the same database, done.

What about EAS Update / OTA?

For most teams, keep EAS Update — it's purpose-built and free at reasonable volume. You'd only self-host an OTA endpoint if you have compliance reasons to own the update channel or you've outgrown EAS pricing. If you go that route, the expo-updates protocol is just static JSON + asset serving, which PandaStack's static hosting handles — but that's an edge case, not the default. Don't build it until you need it.

Honest tradeoffs

  • PandaStack doesn't build your iOS/Android binaries. EAS Build does that and does it well; this backend sits beside it, it doesn't replace it.
  • Free-tier apps scale to zero and cold-start on the next request. For a background API that's usually fine; if your first request after idle feels slow, a paid tier keeps it warm.
  • PandaStack is a newer platform than the incumbents — a plus for DX, worth knowing if you need a huge third-party ecosystem.

Wrap-up

Ship the app with Expo. Run the API, database, webhooks, and cron jobs on PandaStack, all wired together with an injected DATABASE_URL and Git-push deploys. That's the whole backend, and it fits in an afternoon.

Docs: https://docs.pandastack.io. Start free at https://dashboard.pandastack.io.

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also