A TypeScript-native API codegen for React Native + React Query. Parses typed service definitions written against a typed createApi builder and generates per-endpoint hooks, query keys, prefetchers, invalidators, and barrels.
This repo is both a working Expo example and a portable template: run it directly with npm start, or copy scripts/api-codegen/ and src/shared/api/ into an existing React Native / Expo app. A demo screen under app/(tabs)/codegen.tsx shows one of the generated hooks in use.
For every service you declare (e.g. src/shared/api/auth/Auth.service.ts), the codegen produces:
| Artifact | Purpose |
|---|---|
queries/use<Name>Query.<svc>.ts |
One hook file per query endpoint (query, infiniteQuery, suspenseQuery, suspenseInfiniteQuery, queries, prefetch, prefetchInfiniteQuery) |
mutations/use<Name>Mutation.<svc>.ts |
One hook file per mutation endpoint |
QueryKeys.<svc>.ts |
Per-service query key factory |
queries/index.ts, mutations/index.ts, <svc>/index.ts |
Barrel files |
models/QueryKeys.ts |
Merged queryKeys object + QueryKeyType union across all services |
api/index.ts |
Top-level barrel re-exporting every service folder |
Every file is fully overwritten on each run. Never hand-edit generated files — change the service definition instead.
# 1. Install
bun install
# 2. Run the Expo dev server (open in Expo Go, or press `w` for web)
bun start
# 3. Edit or add a service under src/shared/api/<svc>/<Svc>.service.ts
# (See src/shared/api/auth/Auth.service.ts for 15 example endpoints
# covering every builder kind.)
# 4. Regenerate hooks + keys
bun run api-codegenNavigate to the Codegen tab in the running app to see one of the generated hooks wired up end-to-end (app/(tabs)/codegen.tsx). The React Query client is provided at the root in app/_layout.tsx.
bun ios and bun android now run expo run:ios / expo run:android — they compile a native dev build, not just start Metro. They require the native folders to exist first:
# 1. Generate native ios/ and android/ folders
bun expo prebuild # both platforms
bun expo prebuild --clean # wipe + regenerate
# 2. Then:
bun ios # builds + launches iOS
bun android # builds + launches AndroidIf you only want the JS dev server (Expo Go + web), bun start is enough — no prebuild needed.
Inspect scripts/api-codegen/ir.json after a run to see exactly what the parser extracted — most surprises become obvious there.
react-native-codegen/
├── CLAUDE.md Agent guide for working in this repo
├── README.md This file
├── ARCHITECTURE.md Runtime abstractions + IR design
├── package.json
├── tsconfig.json
├── app/ Expo Router entry (file-based routing)
│ ├── _layout.tsx Root layout — wraps tree in QueryClientProvider
│ ├── modal.tsx
│ └── (tabs)/
│ ├── _layout.tsx Tab bar
│ ├── index.tsx Home
│ ├── explore.tsx Expo's default explore tab
│ └── codegen.tsx Demo: calls a codegen-generated hook
├── components/, constants/, hooks/, assets/ Standard Expo template files
├── scripts/
│ ├── reset-project.js Expo's reset script
│ └── api-codegen/
│ ├── INTEGRATION.md Full integration guide (conventions, checkers, troubleshooting)
│ ├── run.ts Entry point — orchestrates the pipeline
│ ├── parse.ts ts-morph parser → IR
│ ├── ir-types.ts IR + CodegenConfig types
│ ├── checkers.ts Validation rules (run before any write)
│ ├── utils.ts String/path/IO helpers
│ ├── codegen.config.json Project config (paths, globs, lint command)
│ └── generators/ One file per output artifact
└── src/
└── shared/
└── api/
├── createApi.ts Typed builder factory (~115 LOC, zero external deps)
├── queryClient.ts React Query client factory
├── baseQuery.ts Default HTTP client (axios)
├── common.api.ts Shared model types
├── hooks/ Hand-written wrapper hooks over @tanstack/react-query
├── models/ QueryError + typed param interfaces + generated QueryKeys.ts
└── auth/ Example service (every builder kind demonstrated)
├── Auth.service.ts ← you write this
├── models.ts ← you write this
├── QueryKeys.auth.ts ← generated
├── queries/ ← generated
├── mutations/ ← generated
└── index.ts ← generated
| Kind | Example | Output |
|---|---|---|
query |
builder.query(fn) |
useXQueryService, xQueryService (fetcher), invalidateXQueryService |
mutation |
builder.mutation(fn) |
useXMutationService |
infiniteQuery |
builder.infiniteQuery(fn) |
useXInfiniteQueryService, xInfiniteQueryService, invalidateXInfiniteQueryService |
suspenseQuery |
builder.suspenseQuery(fn) |
useXSuspenseQueryService |
suspenseInfiniteQuery |
builder.suspenseInfiniteQuery(fn) |
useXSuspenseInfiniteQueryService |
queries |
builder.queries(fn) |
useXQueriesService (batched) |
prefetch |
builder.prefetch(fn) |
usePrefetchXService |
prefetchInfiniteQuery |
builder.prefetchInfiniteQuery(fn) |
usePrefetchXInfiniteQueryService |
builder.X(fn)— standardbuilder.X.hook(fn)— hook-style:fnmay call React hooks, must return{ queryFn }builder.X.configure({ client })(fn)— per-endpoint client override
See scripts/api-codegen/INTEGRATION.md for a full checklist.
TL;DR:
- Copy
scripts/api-codegen/into the target repo. - Install deps:
bun add -d ts-morph tsx,bun add @tanstack/react-query axios. - Copy
src/shared/api/{createApi.ts,queryClient.ts,baseQuery.ts,hooks/,models/}— the runtime contract the generated code imports from. - Add
"api-codegen": "tsx scripts/api-codegen/run.ts"topackage.jsonscripts. - Edit
scripts/api-codegen/codegen.config.jsonto match your layout. - Write a
*.service.tsand runbun run api-codegen.
HISTORY.md— why this exists: the year-long journey from hand-written hooks → bash → C++ → the current builder pattern, plus what vanilla React Query actually costs you without a codegenARCHITECTURE.md— howcreateApi, the IR, and the generators fit togetherscripts/api-codegen/INTEGRATION.md— conventions, checkers, troubleshootingCLAUDE.md— guide for AI agents editing this repo
MIT. Fork it, adapt it, ship it. The point of writing the generator in TypeScript was to make it trivial for anyone to read and modify — the license should match that intent.