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
| Layer | Technology |
|---|---|
| Framework | Expo 54 + React Native 0.81 |
| Routing | Expo Router 6 (file-based) |
| Language | TypeScript (strict mode) |
| Styling | NativeWind 4 (Tailwind CSS for RN) |
| State / Data | TanStack Query 5 (with offline persistence) |
| Database | Vibecode DB (Mock / Supabase / PocketBase) |
| Icons | lucide-react-native |
| Build | EAS 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.mdand.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 installEnvironment
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):
| Variable | Purpose | Pre-filled in download? |
|---|---|---|
EXPO_PUBLIC_ADAPTER_TYPE | Picks the Vibecode adapter (mock / supabase / pocketbase) | ✅ pocketbase |
EXPO_PUBLIC_POCKETBASE_URL | PocketBase server URL | ✅ your project's URL |
EXPO_PUBLIC_SUPABASE_URL | Supabase project URL | Only if you switch to Supabase |
EXPO_PUBLIC_SUPABASE_KEY | Supabase anon key | Only if you switch to Supabase |
Server-side variable for migration scripts:
| Variable | Purpose | Pre-filled in download? |
|---|---|---|
POCKETBASE_SERVICE_ROLE_KEY | Superuser 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 phoneThe 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:
- Never import a global
vibecodeclient. Always go throughuseApp(). The client is built async on app start and stored on context. usercomes fromuseAuth(), never fromuseApp().useApp()returns only theclient. 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.comNote: 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=mockSupabase — 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-keyYou 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:seedMigration 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:
src/db/schema.ts—defineTable()call + TypeScript typesrc/db/seeds/<table>.ts— mock data (SeedEntryexport)src/db/seeds/index.ts— registers the seed in theseedsarray
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 submitBefore shipping:
- Switch from Mock to your real backend (
EXPO_PUBLIC_ADAPTER_TYPE=supabaseorpocketbase) - Push the schema to your backend (Supabase migrations OR
npm run pocketbase:migrate) - Configure EAS Build profiles in
eas.jsonfor your team's signing identity - 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 forvibecode-db,tanstack-query,react-native-patterns, anddata-patterns. Useful as quick references when extending the app. - Vibecode DB official docs — vibecode-db.geekyants.com
- Expo & React Native — standard docs apply, no overrides
- NativeWind — nativewind.dev
- TanStack Query — tanstack.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.