The locked tech stack

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.

The backend question

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.

API Routes (Node, server-side)

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 Postgres + Prisma v7

Neon's HTTP serverless driver kills cold-start connection pool issues. Prisma v7's new prisma-client generator + @prisma/adapter-pg wires Neon natively.

Better Auth + @better-auth/expo

Server config uses prismaAdapter + expo() plugin. Mobile uses createAuthClient + expoClient() + expo-secure-store. Email + password, OAuth (Google / Apple / GitHub), magic links, OTP — all configurable.

EAS Hosting deploy

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-validated
import { 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 });
}
The full stack

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.

Mobile10 layers
FrameworkExpo SDK 55+ · React Native 0.83+Largest mobile community. OTA updates via EAS. Single codebase iOS + Android (+ web).
LanguageTypeScript 5.9 strictEnd-to-end types — Zod schemas shared between mobile forms and API routes.
Routingexpo-router (file-based)Same mental model as Next.js App Router — agents know the pattern.
StylingNativeWind v4 (Tailwind for RN)Reuses Tailwind muscle memory; design tokens via theme.ts.
Lists@shopify/flash-listRecycled rows. Smooth at 10,000+ items.
Imagesexpo-imageDisk cache + blurhash placeholders. No layout shift.
Animationsreact-native-reanimated 3 + MotiRuns on the UI thread. 60fps even when JS thread is busy.
Gesturesreact-native-gesture-handlerNative gesture detection.
Icons@expo/vector-icons (Ionicons)Bundled with Expo. Zero linking.
Hapticsexpo-hapticsBuilt into the registry button. Tap / select / success / error / warning.
State + Storage5 layers
Server stateTanStack Query v5Retries, refetch-on-focus, cursor pagination, offline persister.
Client stateZustandLightweight. No boilerplate. Use only when state spans 3+ screens.
Fast storagereact-native-mmkv30× faster than AsyncStorage. Preferences, cache, last-seen IDs.
Sensitive storageexpo-secure-storeiOS Keychain + Android EncryptedSharedPreferences. Auth tokens, biometric secrets.
Formsreact-hook-form + ZodShared schemas with API routes.
Backend (same repo, same deploy)7 layers
API RoutesExpo API Routes (app/api/**/+api.ts)File-based. Web standard Request/Response. Runs on Node when deployed to EAS Hosting.
DatabaseNeon Postgres (HTTP serverless driver)No connection pool issues on cold-start. Pay-per-use. Branching for preview environments.
ORMPrisma v7 + @prisma/adapter-pg + @neondatabase/serverlessNew prisma-client generator. Driver adapter pattern for HTTP.
AuthBetter Auth + @better-auth/expoNative OAuth via expo-web-browser. Sessions in expo-secure-store. Server-side via Prisma adapter.
EmailResendTransactional only. React Email templates.
File uploadsCloudinary or R2 via signed URLsMint short-lived signed URLs server-side. Never proxy bytes through the API.
WebhooksHMAC verification + idempotent dedupeDGateway, Stripe — both verified with crypto.timingSafeEqual before parsing body.
Payments3 layers
East Africa mobile moneyDGateway (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 PaymentSheetApple Pay, Google Pay, cards. Backend mints PaymentIntent / Subscription.
Marketplace seller onboardingStripe Connect Express + DGateway payouts5-step wizard component. Hosted KYC via expo-web-browser. Mobile-money payouts for EA sellers.
Native integrations5 layers
Push notificationsexpo-notifications + Expo Push ServiceFree. Works on iOS + Android. usePushNotifications hook ships in the registry.
Deep linksexpo-linking + Universal Links + App LinksRequired for OAuth callbacks, magic-link sign-in, share targets.
Biometricsexpo-local-authenticationFace ID / Touch ID / Fingerprint. biometric-unlock-screen component.
Camera + barcodeexpo-camera (CameraView)QR + every common barcode format. barcode-scanner-screen component.
Signature capturereact-native-signature-canvasProof-of-delivery patterns. signature-capture-screen component.
Build + Deploy5 layers
Native binariesEAS BuildiOS + Android in one command. Auto-incrementing build numbers.
Store submissionEAS SubmitApp Store + Play Console. Credentials managed by Expo.
OTA updatesEAS UpdateJS-only fixes ship without store review. Native changes still rebuild.
API hostingEAS HostingNode runtime for Expo API Routes. Auto-scaled. Free tier covers dev.
SecretsEAS SecretServer-side env vars. Never committed. Never bundled into the mobile binary.
Observability2 layers
Crash + error reportingSentry React Native + Sentry NodeMobile + server. Source maps uploaded via EAS post-build hook.
AnalyticsPostHog React Native (optional)Self-hostable. Event funnels. Only if project-description.md says yes.
The blocklist

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.

BannedUse 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-imageexpo-image — maintained, better API
react-navigation (raw)expo-router — file-based, matches Next.js mental model
axiosnative fetch — smaller bundle
momentdate-fns or native Intl.DateTimeFormat
lodashNative ES2022 methods
redux / redux-toolkitZustand + TanStack Query
react-native-elements / react-native-paperVibeKit Native registry + NativeWind
Animated from react-nativereact-native-reanimated 3 (UI thread = 60fps)
FlatList for >50 items@shopify/flash-list (recycled rows)
Why one repo

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.

One TypeScript build, one set of types

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.

Auth session resolves identically on both sides

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.

One deploy, one rollback

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.

No CORS, no separate domains

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.