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

2.4 KiB

paths
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