Skip to content

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.

Terminal window
vaif init --template expo-mobile-app

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

FileWritten
package.jsonAlways
app.jsonAlways
app/_layout.tsxAlways
app/index.tsxAlways; replaced by the Authentication version when you include that feature
lib/vaif.tsAlways
.env.exampleAlways
.gitignoreAlways
README.mdAlways
drizzle/0001_initial.sqlWith Database
drizzle.config.tsWith Database
src/db/schema.tsWith Database
app/(auth)/login.tsxWith Authentication
app/(auth)/signup.tsxWith Authentication
hooks/useRealtimeMessages.tsWith Realtime
hooks/useImagePicker.tsWith Storage
functions/hello.tsWith 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.

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.

Copy .env.example to .env and fill it in. The project ID and the keys are on Security > Credentials in the console.

VariableValue
EXPO_PUBLIC_VAIF_PROJECT_IDYour project ID, for the app.
EXPO_PUBLIC_VAIF_PUBLISHABLE_KEYThe project's publishable key (vk_pub_). Row rules apply to it, so it is safe in an app.
VAIF_PROJECT_IDYour 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.

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.

drizzle/0001_initial.sql
-- 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 id
CREATE 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_id
CREATE 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, and id is filled in from their sign-in.
  • posts (-- vaif:owner user_id): each signed-in user reads and changes only their own rows, and user_id is 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.

Terminal window
vaif login
vaif db push --dry-run # what each file would change
vaif db push

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

Terminal window
npx expo start