RapidNative Logo
Docs

Developer Handoff Guide

You're inheriting a React Native app that was generated by AI. This guide gets you from "what is all this" to shipping confidently in about ten minutes.

It assumes you've shipped React Native or Expo apps before. We won't explain useEffect or what a FlatList is. The focus is on what's RapidNative-specific — namely, the Vibecode DB layer and how the project is wired around it.

Mental model in 60 seconds

The codebase is a standard Expo + Expo Router project. Screens are in app/. Components in components/. Hooks in src/hooks/. Styling is NativeWind (Tailwind for React Native). State and data fetching is TanStack Query.

The one piece you haven't seen before is Vibecode DB — a thin database layer that gives you a Supabase-style API but lets you swap backends (in-memory mock / Supabase / PocketBase) by flipping an env var. No app code changes when you switch.

That's it. If you've shipped Expo apps with TanStack Query and a Supabase-like backend, you already know 95% of this stack.

The stack

LayerTechnology
FrameworkExpo 54 + React Native 0.81
RoutingExpo Router 6 (file-based)
LanguageTypeScript (strict mode)
StylingNativeWind 4 (Tailwind CSS for RN)
State / DataTanStack Query 5 (with offline persistence)
DatabaseVibecode DB (Mock / Supabase / PocketBase)
Iconslucide-react-native
BuildEAS Build + EAS Submit

Everything is standard except Vibecode DB. We'll cover that in detail below.

Folder structure

1project-root/
2├── app/                         # Expo Router pages (file-based routing)
3│   ├── _layout.tsx              # Root layout — wraps in providers, auth guard
4├── (auth)/                  # Public routes (sign in / sign up)
5│   │   ├── _layout.tsx
6│   │   ├── signin.tsx
7│   │   └── signup.tsx
8└── (app)/                   # Protected routes
9│       ├── _layout.tsx          # Auth-gated layout
10└── (tabs)/              # Bottom tab navigation
11├── components/                  # Reusable UI components
12│   └── index.ts                 # Barrel export
13├── src/
14│   ├── db/
15│   │   ├── client.ts            # buildClient() factory + getEnvConfig()
16│   │   ├── schema.ts            # defineTable() exports + TypeScript types
17│   │   └── seeds/
18│   │       ├── index.ts         # Seed registry — `seeds` array
19│   │       └── <table>.ts       # Per-table seed data
20│   ├── hooks/
21│   │   ├── index.ts             # Barrel export
22│   │   ├── useAuth.ts           # Auth wrapped in React Query
23│   │   └── useOffline.ts        # Network state
24│   ├── lib/
25│   │   └── queryClient.ts       # TanStack Query client + persistence
26│   └── providers/
27│       ├── AppProvider.tsx      # Vibecode client context (useApp hook)
28│       └── ThemeProvider.tsx    # Dark / light theme
29│                                # (Provider tree is composed in app/_layout.tsx)
30├── pocketbase/                  # Only used if you ship to PocketBase
31│   ├── migrations/              # Schema migrations (PB JS format)
32│   ├── seeds/                   # PocketBase seed data
33│   └── scripts/
34│       ├── migrate.mjs          # Push schema to PocketBase
35│       ├── seed.mjs             # Push seed data to PocketBase
36│       └── env.mjs              # Env resolution helper
37├── theme.ts                     # Color tokens (light + dark)
38├── tailwind.config.js           # NativeWind config
39├── global.css                   # Tailwind base styles
40├── app.json                     # Expo config
41├── eas.json                     # EAS Build profiles
42├── package.json
43├── README.md
44└── claude.md                    # AI agent context (skim this — it's useful)

A few notes:

  • What's always there: everything in the tree above is in your downloaded project.
  • One conditional: app/(auth)/ only exists if your app uses authentication.
  • Load-bearing files (read before refactoring): src/db/schema.ts, src/db/seeds/index.ts, src/providers/AppProvider.tsx, app/_layout.tsx (composes the provider tree).
  • claude.md and .claude/ directory: these power AI-assisted development inside the project. You can keep using them, ignore them, or delete them. They don't affect runtime.

Installation

Prerequisites

  • Node.js 20+
  • Expo CLI (installed globally or via npx)
  • iOS Simulator (Xcode) or Android Emulator (Android Studio), or the Expo Go app on a physical device

Install dependencies

1npm install

Environment

Your downloaded project already ships with a working .env file. RapidNative pre-fills it with credentials for your project's hosted PocketBase backend, so the app talks to a real database out of the box — no setup required. You'll see something like:

1EXPO_PUBLIC_ADAPTER_TYPE=pocketbase
2EXPO_PUBLIC_POCKETBASE_URL=https://pb.your-project.rapidnative.com
3POCKETBASE_SERVICE_ROLE_KEY=...

If you want to develop against in-memory mock data instead (no network calls, seeded fixtures from src/db/seeds/), set EXPO_PUBLIC_ADAPTER_TYPE=mock in your .env. To swap to your own Supabase backend, see the adapter section below.

Environment variables

Your downloaded .env is already populated with what the app needs to run. Reference table below for what each variable does, in case you need to switch adapters or wire your own backend.

App-side variables (Expo bundles EXPO_PUBLIC_* into the client at build time):

VariablePurposePre-filled in download?
EXPO_PUBLIC_ADAPTER_TYPEPicks the Vibecode adapter (mock / supabase / pocketbase)pocketbase
EXPO_PUBLIC_POCKETBASE_URLPocketBase server URL✅ your project's URL
EXPO_PUBLIC_SUPABASE_URLSupabase project URLOnly if you switch to Supabase
EXPO_PUBLIC_SUPABASE_KEYSupabase anon keyOnly if you switch to Supabase

Server-side variable for migration scripts:

VariablePurposePre-filled in download?
POCKETBASE_SERVICE_ROLE_KEYSuperuser auth for schema operations✅ admin key for your PocketBase

Quick start

1# 1. Install
2npm install
3
4# 2. Start the Expo dev server (uses the pre-configured PocketBase adapter)
5npm start
6
7# 3. Open the app
8# Press 'i' for iOS Simulator
9# Press 'a' for Android Emulator
10# Or scan the QR code with Expo Go on your phone

The downloaded project is wired to the live PocketBase instance RapidNative provisioned for it, so signing up / logging in / creating data all work against a real backend on first run — no extra setup.

If you'd rather develop offline against seeded mock data, set EXPO_PUBLIC_ADAPTER_TYPE=mock in .env. The mock adapter auto-signs you in as demo@example.com and seeds all tables from src/db/seeds/.

Core concepts

Vibecode DB

Vibecode DB is the data layer the app talks to. It exposes a Supabase-style query builder (from(...).select(...), .eq(), .insert(), etc.) but the actual backend behind it is configurable.

You access the client through React context, not a global import:

1import { useApp } from '@/src/providers/AppProvider';
2import { useAuth } from '@/src/hooks';
3
4function PostsScreen() {
5  const { client } = useApp();   // DB client
6  const { user } = useAuth();    // Current user (NEVER from useApp)
7
8  // Query data:
9  const { data, error } = await client
10    .from('posts')
11    .eq('user_id', user.id)
12    .order('created_at', { ascending: false })
13    .limit(50)
14    .select('*');
15}

Two rules that aren't optional:

  1. Never import a global vibecode client. Always go through useApp(). The client is built async on app start and stored on context.
  2. user comes from useAuth(), never from useApp(). useApp() returns only the client. Mixing these up is the most common bug when devs first touch the codebase.

The full Vibecode DB API reference (filter methods, query patterns, auth methods) lives at vibecode-db.geekyants.com. What's documented there applies one-for-one inside this codebase, with the one exception that vibecode here is useApp().client.

The three adapters

The same client.from('posts').select('*') call hits a different backend depending on EXPO_PUBLIC_ADAPTER_TYPE. Three options:

PocketBase — what your downloaded project is wired to by default. Self-hosted single-binary backend with auth, file storage, and admin UI. RapidNative provisions a hosted PocketBase instance for every project and pre-fills the URL + admin key in your .env. Schema is managed via the included migration scripts (see below).

1EXPO_PUBLIC_ADAPTER_TYPE=pocketbase
2EXPO_PUBLIC_POCKETBASE_URL=https://pb.your-project.rapidnative.com

Note: When you preview your project inside the RapidNative editor or as a hosted PWA, the bundler defaults to the mock adapter — so the in-product preview shows seed data from src/db/seeds/, not live PocketBase data. Your downloaded project hits the real PocketBase backend. This is why the editor preview and your local build can show different data.

Mock — In-memory data seeded from src/db/seeds/. Auto-signs in as demo@example.com. Persists session via AsyncStorage but data resets when the user clears storage. Use it when you want to develop offline, write tests, or work without touching the production backend.

1EXPO_PUBLIC_ADAPTER_TYPE=mock

Supabase — Cloud Postgres with auth, storage, and realtime. Choose this if your team prefers Supabase or already has infra there. Schema must exist in Supabase before you point the app at it (you handle migrations through the Supabase dashboard, the SQL editor, or your own tooling — the included PocketBase migration scripts don't apply).

1EXPO_PUBLIC_ADAPTER_TYPE=supabase
2EXPO_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
3EXPO_PUBLIC_SUPABASE_KEY=your-anon-key

You can swap adapters without touching app code. The same client.from(...) calls work against any backend.

PocketBase scripts

If you choose the PocketBase adapter, two npm scripts handle schema and seed data:

1# Push schema collections to your PocketBase server
2npm run pocketbase:migrate
3
4# Seed data into your PocketBase collections
5npm run pocketbase:seed

Migration files live in pocketbase/migrations/ in PocketBase's native JS migration format. Seed data lives in pocketbase/seeds/. Both scripts authenticate as superuser using POCKETBASE_SERVICE_ROLE_KEY from your .env.

The migration runner is two-pass — it creates collections without unresolvable relations first, then patches relation fields once all referenced collections exist. So you don't need to order migrations carefully by dependency.

Authentication

Auth is handled by Vibecode DB and exposed through the useAuth() hook. Never call client.auth.* directly in screens — use the hook. It wraps auth in React Query mutations so cache invalidation, optimistic updates, and loading states all work correctly.

1import { useAuth } from '@/src/hooks';
2
3function SignInScreen() {
4  const { user, isAuthenticated, signIn, signOut } = useAuth();
5
6  // Sign in:
7  signIn.mutate(
8    { email, password },
9    { onSuccess: () => router.replace('/') }
10  );
11
12  // Sign out:
13  signOut.mutate();
14}

The Mock adapter auto-signs the user in during client build, so you don't need to wire up sign-in screens for development. They still work — they're just not required.

The schema 3-file sync rule

This is the one rule that bites people if they don't know about it. Every database table is described in three files, and all three must stay in sync:

  1. src/db/schema.tsdefineTable() call + TypeScript type
  2. src/db/seeds/<table>.ts — mock data (SeedEntry export)
  3. src/db/seeds/index.ts — registers the seed in the seeds array

When you add, rename, or drop a column or table, update all three in the same change. Skipping one will either crash the build or break the mock adapter silently.

Example — adding a posts table:

1// 1. src/db/schema.ts
2import { defineTable, belongsTo } from '@vibecode-db/client';
3
4export const posts = defineTable('posts', {
5  id: 'string',
6  user_id: 'string',
7  caption: 'string',
8  created_at: 'string',
9  updated_at: 'string',
10  author: belongsTo(() => users, 'user_id'),
11});
12
13export type Post = {
14  id: string;
15  user_id: string;
16  caption: string;
17  created_at: string;
18  updated_at: string;
19};
1// 2. src/db/seeds/posts.ts
2import type { Post } from '../schema';
3import type { SeedEntry } from './index';
4
5const seed: SeedEntry<Post> = {
6  table: 'posts',
7  rows: [
8    { id: '1', user_id: 'user-1', caption: 'Hello', created_at: '...', updated_at: '...' },
9    // 6–7 rows ideally — list screens with 2 items look broken
10  ],
11};
12
13export default seed;
1// 3. src/db/seeds/index.ts
2import users from './users';
3import posts from './posts';
4
5export type SeedEntry<T = Record<string, unknown>> = { table: string; rows: T[] };
6export const seeds: SeedEntry[] = [users, posts];

The project's claude.md covers this rule in detail and the AI slash commands inside the project (/new-table) automate it for you.

Going to production

Standard Expo path:

1# Build for production (iOS + Android)
2npm run build:production
3
4# Submit to App Store / Play Store
5npm run submit

Before shipping:

  1. Switch from Mock to your real backend (EXPO_PUBLIC_ADAPTER_TYPE=supabase or pocketbase)
  2. Push the schema to your backend (Supabase migrations OR npm run pocketbase:migrate)
  3. Configure EAS Build profiles in eas.json for your team's signing identity
  4. Add app store metadata, icons, screenshots

If your team would rather skip the certificates / store listings / submission grind, RapidNative offers an end-to-end deployment service starting at $499 — see rapidnative.com/deploy.

Where to get help

  • The project's own claude.md — covers conventions, file locations, and rules in detail. Worth a five-minute skim even if you're not using AI tooling.
  • The .claude/skills/ directory — pattern guides for vibecode-db, tanstack-query, react-native-patterns, and data-patterns. Useful as quick references when extending the app.
  • Vibecode DB official docsvibecode-db.geekyants.com
  • Expo & React Native — standard docs apply, no overrides
  • NativeWindnativewind.dev
  • TanStack Querytanstack.com/query

If something doesn't make sense after reading this, the answer is almost certainly in claude.md or one of the skill files inside the project.