Expo mobile app
A complete Expo app that uses Expo Router. app/_layout.tsx wraps every screen in the Expo SDK’s provider, which keeps the user’s session in the device’s storage, so the user stays signed in between launches.
Create the app
Section titled “Create the app”vaif init --template expo-mobile-appRun it in a new, empty folder. It writes vaif.config.json and the template's files there. It stops if the folder already has a vaif.config.json, and leaves any other file that already exists alone; --force overwrites both.
It asks which features to include. This template has files for Database, Authentication, Realtime, Storage and Functions; Database and Authentication are selected to start with. To skip the question, name them: vaif init --template expo-mobile-app --features database,auth,realtime,storage,functions. Add one later with vaif init --template expo-mobile-app --add-features <feature>.
What it writes
Section titled “What it writes”| File | Written |
|---|---|
package.json | Always |
app.json | Always |
app/_layout.tsx | Always |
app/index.tsx | Always; replaced by the Authentication version when you include that feature |
lib/vaif.ts | Always |
.env.example | Always |
.gitignore | Always |
README.md | Always |
drizzle/0001_initial.sql | With Database |
drizzle.config.ts | With Database |
src/db/schema.ts | With Database |
app/(auth)/login.tsx | With Authentication |
app/(auth)/signup.tsx | With Authentication |
hooks/useRealtimeMessages.ts | With Realtime |
hooks/useImagePicker.ts | With Storage |
functions/hello.ts | With Functions |
The Authentication feature adds sign-in and sign-up screens. Storage adds a hook that picks an image on the device and uploads it to a bucket (uploads unless you pass another name), and adds the image picker package to package.json. Realtime adds a hook that loads a channel’s latest rows from a messages table and appends new ones as they are inserted.
Install
Section titled “Install”Its package.json lists @vaifstudio/client and @vaifstudio/expo, with @react-native-async-storage/async-storage, expo, expo-router, react and react-native, so npm install in the folder installs everything.
Configure
Section titled “Configure”Copy .env.example to .env and fill it in. The project ID and the keys are on Security > Credentials in the console.
| Variable | Value |
|---|---|
EXPO_PUBLIC_VAIF_PROJECT_ID | Your project ID, for the app. |
EXPO_PUBLIC_VAIF_PUBLISHABLE_KEY | The project's publishable key (vk_pub_). Row rules apply to it, so it is safe in an app. |
VAIF_PROJECT_ID | Your project ID again, for the CLI, which reads it from .env. |
Expo builds variables that start with EXPO_PUBLIC_ into the app, so anyone with the app can read them. That is fine for the project ID and the publishable key; never put a secret key in an app.
Set up the database
Section titled “Set up the database”The template writes one SQL file for your database. The comment line above each CREATE TABLE sets that table's row rules.
drizzle/0001_initial.sql, written with the Database feature, is an example: edit it to describe your own tables before you apply it.
-- Initial migration. Customize this schema for your project; vaif db push-- applies it. The comment directly above each CREATE TABLE sets that table's-- row rules: here each signed-in user reads and changes only their own rows,-- and the owner column is filled in from their sign-in on insert. Keep a rule-- comment on every table you add: a table with no row rules is closed to-- signed-out visitors but open to every signed-in user.
-- vaif:owner idCREATE TABLE IF NOT EXISTS "users" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(), "email" text NOT NULL UNIQUE, "name" text, "created_at" timestamp with time zone NOT NULL DEFAULT now());
-- vaif:owner user_idCREATE TABLE IF NOT EXISTS "posts" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(), "user_id" uuid NOT NULL REFERENCES "users"("id"), "title" text NOT NULL, "content" text, "published" boolean NOT NULL DEFAULT false, "created_at" timestamp with time zone NOT NULL DEFAULT now());users(-- vaif:owner id): each signed-in user reads and changes only their own rows, andidis filled in from their sign-in.posts(-- vaif:owner user_id): each signed-in user reads and changes only their own rows, anduser_idis filled in from their sign-in.
Put your project ID in vaif.config.json ("projectId"), sign in, and apply it. With no --dir, vaif db push finds the template's SQL folder, or a ./migrations folder, and applies its .sql files in name order.
vaif loginvaif db push --dry-run # what each file would changevaif db pushKeep a rule comment on every table you add: a table with no row rules is open to every signed-in user. See Rules from a schema file.
Run it
Section titled “Run it”npx expo startNext steps
Section titled “Next steps”- Expo SDK for the provider and hooks.
- Buckets and policies to create the bucket the Storage hook uploads to.
- Realtime overview to turn realtime on for the
messagestable the Realtime hook watches.