Files
ai-coding-starter-kit-v2/.claude/rules/backend.md
T
patrickandClaude Sonnet 5 4a19335a1e feat: rework starter kit for self-hosted deploy (Vite/React + Fastify/Drizzle/Postgres)
Replace Next.js/Supabase/Vercel stack with a self-hostable setup: Vite+React
frontend, Fastify+Drizzle+PostgreSQL backend with own JWT auth, deploy via
git pull + rsync + systemd (checkout/service separated, updates pulled from
Gitea). Adds a reference auth route (register/login/me) as the canonical
pattern for new routes. Updates all skills/rules/agents/docs to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BMdBJfD9TT6XUU2NqGG1PN
2026-09-08 11:42:47 +02:00

41 lines
2.4 KiB
Markdown

---
paths:
- "backend/src/routes/**"
- "backend/src/db/**"
---
# Backend Development Rules
## Database (PostgreSQL + Drizzle)
- Every table gets explicit foreign keys with `onDelete` behavior defined
- Add indexes on columns used in WHERE, ORDER BY, and JOIN clauses
- Auth/ownership checks happen in route handlers, not in the database — there is no RLS equivalent
- Migrations generated via `npm run db:generate`, applied via `npm run db:migrate` — never hand-edit generated SQL after it has been applied
## API Routes (Fastify)
- Validate all inputs using Zod schemas before processing
- Always check authentication: verify JWT via `request.jwtVerify()` before touching protected data
- Return meaningful error messages with appropriate HTTP status codes
- Paginate all list queries (`limit`/`offset`, sane default limit)
## Query Patterns
- Use Drizzle relational queries / joins instead of N+1 query loops
- Wrap multi-statement writes in a transaction (`db.transaction(...)`)
- Always handle and log errors from `pg` / Drizzle calls
## Security
- Never hardcode secrets in source code
- Use environment variables for all credentials (see `.env.example`)
- Validate and sanitize all user input
- Drizzle uses parameterized queries by default — never build SQL via string concatenation
## Reference Pattern: Auth Routes
`backend/src/routes/auth.ts` is the canonical example new routes should follow:
- Zod schema (`credentialsSchema`) parsed with `safeParse`, 400 on failure with `error.issues[0].message`
- Passwords hashed with `bcryptjs` (`bcrypt.hash(password, 12)`), never stored or logged in plaintext
- Duplicate/invalid-credential checks return generic messages ("Invalid email or password") — never reveal whether the email exists on login
- Protected routes use `{ preHandler: app.authenticate }` (decorator defined in `server.ts`, verifies the JWT via `request.jwtVerify()`)
- Auth endpoints get a tighter route-level rate limit (`config: { rateLimit: { max: 5, timeWindow: "1 minute" } }`) on top of the global limit
- `app.jwt.sign({ sub: user.id, email: user.email })` — the JWT payload shape is declared once in `backend/src/types.d.ts` (`FastifyJWT` module augmentation), so `request.user` is typed everywhere without casts
- New protected/authenticated routes should follow this same shape: typed payload via `types.d.ts`, `app.authenticate` as `preHandler`, never `request.user as any`