One repo. One deploy. The whole backend.
VibeKit Native runs your mobile app and backend in the same Expo project. Screens live in app/; API routes live in app/api/. Both deploy together via EAS — no separate Next.js server, no separate Vercel project, no monorepo.
What's the actual backend?
The most common question we get. The short answer: Expo API Routes + Neon + Prisma v7 + Better Auth — all in the same Expo project, all deployed by one command.
Files at app/api/**/+api.ts in your Expo project. Export named methods (GET, POST, PATCH). Use Web standard Request/Response. Run on Node when deployed to EAS Hosting.
Neon's HTTP serverless driver kills cold-start connection pool issues. Prisma v7's new prisma-client generator + @prisma/adapter-pg wires Neon natively.
Server config uses prismaAdapter + expo() plugin. Mobile uses createAuthClient + expoClient() + expo-secure-store. Email + password, OAuth (Google / Apple / GitHub), magic links, OTP — all configurable.
One command (eas deploy --prod) ships the API routes. Production env vars via eas secret:create. Auto-scaled Node runtime. Free tier for dev.
Sample API route
app/api/posts/+api.tsCursor-paginated, auth-gated, Zod-validatedimport { auth } from "@/src/lib/auth";
import { prisma } from "@/src/lib/prisma";
import { CreatePostSchema } from "@/src/lib/schemas/post";
export async function GET(request: Request) {
const session = await auth.api.getSession({ headers: request.headers });
if (!session) return Response.json({ error: "Unauthorized" }, { status: 401 });
const url = new URL(request.url);
const cursor = url.searchParams.get("cursor") ?? undefined;
const items = await prisma.post.findMany({
where: { userId: session.user.id },
take: 21,
...(cursor && { cursor: { id: cursor }, skip: 1 }),
orderBy: { createdAt: "desc" },
});
const hasMore = items.length > 20;
return Response.json({
data: hasMore ? items.slice(0, -1) : items,
nextCursor: hasMore ? items[19].id : null,
});
}
export async function POST(request: Request) {
const session = await auth.api.getSession({ headers: request.headers });
if (!session) return Response.json({ error: "Unauthorized" }, { status: 401 });
const parsed = CreatePostSchema.safeParse(await request.json());
if (!parsed.success) {
return Response.json({ error: "Invalid input", issues: parsed.error.issues }, { status: 400 });
}
const post = await prisma.post.create({
data: { ...parsed.data, userId: session.user.id },
});
return Response.json({ data: post }, { status: 201 });
}Every layer, locked.
The master_prompt.md tells the AI agent to use exactly these. Swapping any layer means losing the AI agent's pattern recognition — only deviate when project-description.md explicitly says so.
| Framework | Expo SDK 55+ · React Native 0.83+ | Largest mobile community. OTA updates via EAS. Single codebase iOS + Android (+ web). |
| Language | TypeScript 5.9 strict | End-to-end types — Zod schemas shared between mobile forms and API routes. |
| Routing | expo-router (file-based) | Same mental model as Next.js App Router — agents know the pattern. |
| Styling | NativeWind v4 (Tailwind for RN) | Reuses Tailwind muscle memory; design tokens via theme.ts. |
| Lists | @shopify/flash-list | Recycled rows. Smooth at 10,000+ items. |
| Images | expo-image | Disk cache + blurhash placeholders. No layout shift. |
| Animations | react-native-reanimated 3 + Moti | Runs on the UI thread. 60fps even when JS thread is busy. |
| Gestures | react-native-gesture-handler | Native gesture detection. |
| Icons | @expo/vector-icons (Ionicons) | Bundled with Expo. Zero linking. |
| Haptics | expo-haptics | Built into the registry button. Tap / select / success / error / warning. |
| Server state | TanStack Query v5 | Retries, refetch-on-focus, cursor pagination, offline persister. |
| Client state | Zustand | Lightweight. No boilerplate. Use only when state spans 3+ screens. |
| Fast storage | react-native-mmkv | 30× faster than AsyncStorage. Preferences, cache, last-seen IDs. |
| Sensitive storage | expo-secure-store | iOS Keychain + Android EncryptedSharedPreferences. Auth tokens, biometric secrets. |
| Forms | react-hook-form + Zod | Shared schemas with API routes. |
| API Routes | Expo API Routes (app/api/**/+api.ts) | File-based. Web standard Request/Response. Runs on Node when deployed to EAS Hosting. |
| Database | Neon Postgres (HTTP serverless driver) | No connection pool issues on cold-start. Pay-per-use. Branching for preview environments. |
| ORM | Prisma v7 + @prisma/adapter-pg + @neondatabase/serverless | New prisma-client generator. Driver adapter pattern for HTTP. |
| Auth | Better Auth + @better-auth/expo | Native OAuth via expo-web-browser. Sessions in expo-secure-store. Server-side via Prisma adapter. |
| Resend | Transactional only. React Email templates. | |
| File uploads | Cloudinary or R2 via signed URLs | Mint short-lived signed URLs server-side. Never proxy bytes through the API. |
| Webhooks | HMAC verification + idempotent dedupe | DGateway, Stripe — both verified with crypto.timingSafeEqual before parsing body. |
| East Africa mobile money | DGateway (UGX/KES/TZS/RWF) | Iotec for UGX, Relworx for KES/TZS/RWF — routed automatically. Mobile NEVER calls DGateway directly; backend proxies with the API key. |
| Global cards | @stripe/stripe-react-native PaymentSheet | Apple Pay, Google Pay, cards. Backend mints PaymentIntent / Subscription. |
| Marketplace seller onboarding | Stripe Connect Express + DGateway payouts | 5-step wizard component. Hosted KYC via expo-web-browser. Mobile-money payouts for EA sellers. |
| Push notifications | expo-notifications + Expo Push Service | Free. Works on iOS + Android. usePushNotifications hook ships in the registry. |
| Deep links | expo-linking + Universal Links + App Links | Required for OAuth callbacks, magic-link sign-in, share targets. |
| Biometrics | expo-local-authentication | Face ID / Touch ID / Fingerprint. biometric-unlock-screen component. |
| Camera + barcode | expo-camera (CameraView) | QR + every common barcode format. barcode-scanner-screen component. |
| Signature capture | react-native-signature-canvas | Proof-of-delivery patterns. signature-capture-screen component. |
| Native binaries | EAS Build | iOS + Android in one command. Auto-incrementing build numbers. |
| Store submission | EAS Submit | App Store + Play Console. Credentials managed by Expo. |
| OTA updates | EAS Update | JS-only fixes ship without store review. Native changes still rebuild. |
| API hosting | EAS Hosting | Node runtime for Expo API Routes. Auto-scaled. Free tier covers dev. |
| Secrets | EAS Secret | Server-side env vars. Never committed. Never bundled into the mobile binary. |
| Crash + error reporting | Sentry React Native + Sentry Node | Mobile + server. Source maps uploaded via EAS post-build hook. |
| Analytics | PostHog React Native (optional) | Self-hostable. Event funnels. Only if project-description.md says yes. |
What you DON'T install.
The master_prompt.md ships a banned-deps list so the AI agent doesn't drift off-stack. Each ban has a better alternative on the locked stack.
| Banned | Use instead |
|---|---|
| @react-native-async-storage/async-storage (for primary data) | react-native-mmkv (via the storage lib component) |
| react-native-vector-icons | @expo/vector-icons — bundled with Expo, zero linking |
| react-native-fast-image | expo-image — maintained, better API |
| react-navigation (raw) | expo-router — file-based, matches Next.js mental model |
| axios | native fetch — smaller bundle |
| moment | date-fns or native Intl.DateTimeFormat |
| lodash | Native ES2022 methods |
| redux / redux-toolkit | Zustand + TanStack Query |
| react-native-elements / react-native-paper | VibeKit Native registry + NativeWind |
| Animated from react-native | react-native-reanimated 3 (UI thread = 60fps) |
| FlatList for >50 items | @shopify/flash-list (recycled rows) |
Not a separate Next.js + Expo monorepo.
Every other mobile-plus-API stack is two projects in two repos with two CI pipelines. VibeKit Native collapses both into one Expo project with one deploy. Here is why that works.
The mobile app imports the same Zod schemas from src/lib/schemas/ that the API routes use to validate incoming requests. Type safety end-to-end with no codegen step.
Better Auth's @better-auth/expo plugin handles the mobile session in expo-secure-store. The same Better Auth instance handles server-side auth.api.getSession({ headers }) on every API route. One source of truth.
eas build ships the binary. eas deploy --prod ships the API routes. eas update ships JS-only patches via OTA. If something breaks, you roll back EAS Update — instantly, without store review.
The mobile app is signed with the same scheme as the API routes' trustedOrigins. No cross-origin headers to debug. No separate sub-domain to provision.