Template
A published database port puts the data on the public internet, and Docker's own iptables rules bypass ufw — the server looks firewalled while the port is open. Previously this was only a comment in compose.yaml, which an agent adding a db service later is free to ignore. Anchored at every point where a service could be added or shipped: - rules/security.md: the rule, the reasoning, and the audit commands - rules/backend.md + architecture SKILL: constraint when picking the data layer - agents/backend-dev.md: non-negotiable for any service it adds - agents/qa-engineer.md: a published port is a Critical finding - skills/deploy: blocking pre-flight audit - compose.yaml: db example now uses expose:, with depends_on healthcheck - docs/DEPLOYMENT.md + README: SSH tunnel instead of publishing for debugging
60 lines
2.3 KiB
Markdown
60 lines
2.3 KiB
Markdown
---
|
|
paths:
|
|
- "src/app/api/**"
|
|
- "src/lib/**"
|
|
- "src/server/**"
|
|
- "drizzle/**"
|
|
- "prisma/**"
|
|
---
|
|
|
|
# Backend Rules
|
|
|
|
## No preselected data layer
|
|
|
|
This template ships **no** database, ORM or BaaS. `/architecture` picks one per
|
|
project and records the choice in the spec's Technical Decisions table. Until that
|
|
decision exists, do not install an ORM or write schema code.
|
|
|
|
Whatever is chosen must be self-hostable inside the Docker Compose stack —
|
|
the deployment target is a plain server, not a managed platform.
|
|
|
|
When you add it to `compose.yaml`, it gets **no published port**. The app reaches it
|
|
by service name over the compose network. See the port-exposure rule in
|
|
`.claude/rules/security.md`.
|
|
|
|
## Route Handlers
|
|
|
|
- Validate every request body with a Zod schema before touching it. Reuse the
|
|
schema the form already uses; never trust client-side validation alone.
|
|
- Check authentication before doing any work — see `.claude/rules/security.md`
|
|
- Return correct status codes: 400 validation, 401 unauthenticated,
|
|
403 unauthorized, 404 missing, 409 conflict, 500 unexpected
|
|
- Error responses carry a message safe to show a user. Log the detail server-side;
|
|
never leak stack traces, SQL or internal IDs to the client.
|
|
|
|
## Data Access
|
|
|
|
- **Scope every query by the current user.** Ownership is checked in the query
|
|
itself (`where userId = session.user.id`), not after fetching. A filter applied
|
|
in application code after a broad read is a data leak waiting for a bug.
|
|
- Index columns used in WHERE, ORDER BY and JOIN
|
|
- Foreign keys with an explicit ON DELETE behaviour
|
|
- Always paginate or `.limit()` list queries — no unbounded reads
|
|
- Use joins instead of looping queries (N+1)
|
|
- Only parameterized queries. Never build SQL through string concatenation.
|
|
- Handle the error branch of every database call
|
|
|
|
## Migrations
|
|
|
|
- Schema changes go through checked-in migration files, never manual edits on a
|
|
running database
|
|
- Migrations must be reversible or explicitly documented as one-way
|
|
- The deployment step runs pending migrations before the new container serves
|
|
traffic — see `docs/DEPLOYMENT.md`
|
|
|
|
## Secrets
|
|
|
|
- Read configuration from `process.env`, never hardcode it
|
|
- Every new variable goes into `.env.example` with a dummy value in the same commit
|
|
- `NEXT_PUBLIC_` only for values that are safe in a browser bundle
|