Skip to content
STACK IT FAST

Expo + SQLite + Drizzle (Local-First Mobile App)

Curated rule Mobile App Updated Oct 2026 Which file does my tool read?
expo-sqlite-local-first.md

Rules for an offline-first Expo SDK 57 app: expo-sqlite with Drizzle migrations bundled in the app, no backend by default, sync as an optional module, EAS Build and Update.

Formats
4 files
AGENTS.md
43 lines
CLAUDE.md
14 lines
Languages
TypeScript
Updated
Oct 2026
Used by
4 projects
Install

Writes .claude/skills/expo-sqlite-local-first/SKILL.md

$ curl -s --create-dirs -o .claude/skills/expo-sqlite-local-first/SKILL.md https://stackitfast.com/rules/expo-sqlite-local-first/SKILL.md

Rule files

AGENTS.md· 43 lines · 3.2 KB
1# Project Architecture & Guidelines (Expo + SQLite + Drizzle, Local-First)
2
3## 1. System Architecture
4- **Framework**: Expo SDK 57 with Expo Router (file-based routes in `app/`). The New Architecture is the only architecture from SDK 55 on.
5- **Storage**: `expo-sqlite` on the device is the source of truth, accessed through Drizzle ORM (`drizzle-orm/expo-sqlite`).
6- **Migrations**: Generated by `drizzle-kit` with the `expo` driver and bundled into the app; applied on startup with `useMigrations()` before any screen reads data.
7- **Settings**: Small key-value state in `expo-sqlite/kv-store` or MMKV, never in the relational schema.
8- **Styling**: NativeWind (Tailwind classes) with a small token set.
9- **Releases**: EAS Build for store binaries, EAS Update for JavaScript fixes.
10
11## 2. File Layout
12- `app/`: Routes only. `_layout.tsx` runs migrations and provides the database; screens import from `src/features/`.
13- `src/db/schema.ts`: Every table in one file. `src/db/client.ts`: the `openDatabaseSync()` handle wrapped by `drizzle()`.
14- `drizzle/`: Generated migrations plus `migrations.js`; committed, never edited by hand.
15- `src/features/<workflow>/`: Queries, hooks, components and tests for one workflow together.
16- `src/features/sync/` and `src/features/purchases/`: Only when those features exist, behind their own interface.
17
18## 3. Data Rules
19- The app must work with the network off. Any feature that needs a server is optional and degrades to the local path.
20- Reads go through Drizzle queries in the feature folder; use `useLiveQuery()` for screens that must update when data changes.
21- Every schema change is a new migration generated from `schema.ts`. Never alter tables at runtime.
22- Store ids as client-generated UUIDs so records can sync later without remapping keys.
23- Export and import (a JSON or CSV file through `expo-file-system` and the share sheet) is the first sync story; it is enough until someone asks for a second device.
24
25## 4. Adding Sync Later
26- Sync lives in `src/features/sync/` and talks to the rest of the app only through the local database.
27- Prefer an engine with visible queues and a client you can inspect (PowerSync, or a small Hono API with a change log) over opaque magic.
28- Accounts exist only when sync is on; a user who never syncs never signs in.
29
30## 5. Coding Standards
31- Strict TypeScript, zero `any`. Types come from the Drizzle schema (`typeof items.$inferSelect`), not hand-written interfaces.
32- No native modules for storage, files or settings; the Expo SDK covers them.
33- Keep screens thin: a screen composes hooks from its feature folder and renders.
34
35## 6. Testing Conventions
36- Unit test queries and data logic with Jest (`jest-expo`) against an in-memory SQLite database.
37- Maestro flows cover the core workflow with airplane mode on before anything network-related.
38- Test a fresh install and an upgrade from the previous release's database, so migrations are exercised on real data.
39
40## 7. Git Workflow & PR Conventions
41- Conventional Commits scoped to the feature: `feat(export): CSV export from the history screen`.
42- A schema change ships with its generated migration in the same commit.
43- Native config changes (plugins in `app.json`) need a new EAS Build; JavaScript-only fixes go out with EAS Update.

Works with Cursor · Claude Code · Windsurf · AGY

Architecture notes

Architecture Overview

Guidelines for an offline-first mobile app on Expo SDK 57: data lives on the device in SQLite through Drizzle ORM, migrations ship inside the app, and sync is an optional module added only when users ask for it.

Key Advantages

  • No backend to run: the whole app is one TypeScript project an agent can read, run and test end to end.
  • Typed local data: Drizzle gives the device database the same schema discipline as a server database.
  • Sync without a rewrite: client UUIDs and a dedicated sync module let a server arrive later without touching the core workflow.

Frequently asked questions

Why not start with Supabase or Firebase for a mobile tool?

A tool that keeps data on the device has no server to deploy, secure or pay for, and an agent can run and test the entire app locally. A backend becomes worth it when users ask for a second device or for sharing; at that point sync is added as a module rather than rebuilding the app around a server database.

How do Drizzle migrations run inside an Expo app?

drizzle-kit generates the SQL migrations and a migrations.js bundle with the expo driver. The root layout calls useMigrations(db, migrations) and renders nothing until it reports success, so every screen sees the current schema.

Does this AGENTS.md work with Cursor, Claude Code and Windsurf?

Yes. AGENTS.md is the cross-tool standard read by Cursor, Claude Code, Windsurf, Codex and others. A .mdc file is included for Cursor's native rules format.

Used in production

Explore all stacks
Where this stack fits

Stack It First recommends it at:

Walk the mobile roadmap