RapidNative Logo
Docs

Developer Handoff: Fullstack

You're inheriting a fullstack React Native app that was generated by AI: app + Vibecode DB + auth + Supabase backend. 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.

If you downloaded a frontend-only project (no backend, no src/db/ folder), use the Developer Handoff: Frontend guide instead.

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) 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 (extends expo/tsconfig.base, not strict)
StylingNativeWind 4 (Tailwind CSS for RN)
State / DataTanStack Query 5 (with offline persistence)
DatabaseVibecode DB (Mock / Supabase)
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├── theme.ts                     # Color tokens (light + dark)
31├── tailwind.config.js           # NativeWind config
32├── global.css                   # Tailwind base styles
33├── app.json                     # Expo config
34├── eas.json                     # EAS Build profiles
35├── package.json
36├── README.md
37└── 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 Supabase backend, so the app talks to a real database out of the box. No setup required. You'll see something like:

1EXPO_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
2EXPO_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

If you want to develop against in-memory mock data instead (no network calls, seeded fixtures from src/db/seeds/), you can use the mock adapter. 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_SUPABASE_URLSupabase project URL✅ your project's URL
EXPO_PUBLIC_SUPABASE_ANON_KEYSupabase anon key✅ your project's key

Quick start

1# 1. Install
2npm install
3
4# 2. Start the Expo dev server (uses the pre-configured Supabase 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 Supabase 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, you can switch to the mock adapter. 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 two adapters

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

Supabase: what your downloaded project is wired to by default. Cloud Postgres with auth, storage, and realtime. RapidNative provisions a hosted Supabase instance for every project and pre-fills the URL + key in your .env.

1EXPO_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
2EXPO_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

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 Supabase data. Your downloaded project hits the real Supabase 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.

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

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.ts: defineTable() 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 (Supabase)
  2. Push the schema to your backend via Supabase migrations
  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 via letsdeploy.it. Flat fee, unlimited resubmissions, live on the App Store and Play Store in 10 to 14 days.

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 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.