
# [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.ts` app entrypoint, port + signal handling
- `src/routes/` route modules, one per resource
- `src/middleware/` auth, rate limit, error handler
- `src/db/schema.ts` Drizzle table definitions
- `src/db/migrations/` generated SQL migrations
- `src/lib/auth.ts` JWT sign + verify
- `src/lib/logger.ts` Pino setup
- `tests/` Vitest + Supertest specs
- `drizzle.config.ts` schema + out dir
- `fly.toml` Fly app config

## .env keys
- `DATABASE_URL`
- `JWT_SECRET`
- `JWT_REFRESH_SECRET`
- `PORT` defaults 8080
- `NODE_ENV` `development` | `production`
- `LOG_LEVEL` `info` | `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 `asyncHandler` so errors hit the central error middleware.
- Logs are JSON. No `console.log` in production code paths.
- Migrations generated via `drizzle-kit generate`, applied via `drizzle-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
1. `fly launch` to create the app + Postgres.
2. `fly secrets set` for `JWT_SECRET`, `JWT_REFRESH_SECRET`.
3. `npm run db:generate` then `npm run db:migrate`.
4. Implement first resource route + Zod schema + test.
5. Smoke-test JWT flow with a real client before connecting frontend.
