[API Name]
A REST API. Single Node process. Postgres in the back.
Source of truth
Production runs on Fly.io (one VM, one Postgres). Local dev mirrors prod via fly proxy 5432 -a yourapp-db. The deployed image is the source of truth for what handles requests.
Tech stack
Node 22 + Express 5 + TypeScript. Drizzle ORM (typed SQL queries, generated migrations). Zod for request validation. JWT auth with refresh. Pino for structured JSON logging. Vitest for unit tests. Supertest for route tests. Deployed via fly deploy.
Deploy
fly deploy from local. Postgres lives in the same Fly region. Secrets via fly secrets set. Logs via fly logs.
File map
src/index.tsapp entrypoint, port + signal handlingsrc/routes/route modules, one per resourcesrc/middleware/auth, rate limit, error handlersrc/db/schema.tsDrizzle table definitionssrc/db/migrations/generated SQL migrationssrc/lib/auth.tsJWT sign + verifysrc/lib/logger.tsPino setuptests/Vitest + Supertest specsdrizzle.config.tsschema + out dirfly.tomlFly app config
.env keys
DATABASE_URLJWT_SECRETJWT_REFRESH_SECRETPORTdefaults 8080NODE_ENVdevelopment|productionLOG_LEVELinfo|debug|error
Hard rules
- Every route validates request body with Zod before any logic.
- DB queries via Drizzle, no raw SQL strings in handlers (except in
db/). - All handlers wrapped with
asyncHandlerso errors hit the central error middleware. - Logs are JSON. No
console.login production code paths. - Migrations generated via
drizzle-kit generate, applied viadrizzle-kit migrate. Never edit migrations by hand. - Rate limit on
/api/auth/*routes. 5 attempts per minute per IP.
Recent significant changes
- 2026-05-08: Scaffolded. Locked: Drizzle over Prisma (Edge-friendlier, smaller binary). Express 5 over Fastify (boring + familiar). Fly over Vercel (long-running process).
Next session: start here
fly launchto create the app + Postgres.fly secrets setforJWT_SECRET,JWT_REFRESH_SECRET.npm run db:generatethennpm run db:migrate.- Implement first resource route + Zod schema + test.
- Smoke-test JWT flow with a real client before connecting frontend.