Writing an AGENTS.md for NestJS
Nest already decides where things go, so this file has one real job: the security configuration the framework will happily let you get wrong.
AGENTS.md is a Markdown file in your repository root that coding agents read before they start. Claude Code reads CLAUDE.md; most other tools read AGENTS.md. Write one and symlink:
ln -s AGENTS.md CLAUDE.mdNest is unusual among the languages on this network because the framework has already answered most of the questions an instructions file normally answers. Where does a database query go? In a service. How is a dependency provided? Constructor injection. What shape is a feature? Module, controller, service, DTOs.
So the file gets short, and it concentrates on the one thing Nest does not decide for you: security configuration.
The two lines that matter most#
Everything else on this page is secondary to these:
- JwtAuthGuard is registered globally with APP_GUARD. Opt out with @Public(),
never opt in with @UseGuards() per route.
- ValidationPipe runs with { whitelist: true, transform: true }.The first fails closed: an endpoint someone forgot to decorate is protected. The per-route alternative fails open, and the endpoint someone forgot is public. That difference is the whole ballgame.
The second prevents mass assignment. new ValidationPipe() with no options validates the fields you declared and passes everything else straight through — so a client sending {"role": "admin"} gets it written to the database if any code path spreads the DTO into an update. Generated Nest bootstraps omit the options constantly. See the Nest failure modes.
The template#
NestJS 11, TypeScript strict, Prisma, pnpm, Express adapter.
## Commands
- Dev: `pnpm start:dev`
- Test: `pnpm test` (unit only — must stay under 10s)
- E2E: `pnpm test:e2e`
- Check: `pnpm check` (eslint + tsc --noEmit + unit tests). Must pass.
- One: `pnpm test -- users.service.spec.ts`
## Generating code
Use the CLI: `nest g resource <name>`. Do not hand-write module, controller
and service files — the skeleton should be identical across every feature.
## Structure
- Controllers are ONE LINE per method: parse, delegate, return.
- Business logic and database access live in services.
- DTOs in `dto/`, one class per operation, validated with class-validator.
- Never inject PrismaService or a repository into a controller.
- Static route segments are declared BEFORE parameterised ones
(`@Get('me')` above `@Get(':id')`) or the parameter swallows them.
## Security — non-negotiable
- JwtAuthGuard is global via APP_GUARD. Opt out with @Public(), never opt in.
- ValidationPipe: { whitelist: true, forbidNonWhitelisted: true, transform: true }.
- Never spread a DTO into a Prisma update. Pick fields explicitly.
- @Exclude() on every secret field, with ClassSerializerInterceptor global.
- Ownership checks live in the SERVICE, beside the query — not in a guard.
Return 404, not 403, for a resource the caller may not see.
- Nested DTOs need @Type(() => Child) alongside @ValidateNested, or the
items are silently not validated at all.
## Testing
- Register the same global pipes in the test setup. main.ts does not run in
tests, so without this your e2e tests pass on payloads production rejects.
- Use overrideProvider for anything crossing the network.
- Every resource endpoint has a test asserting another user gets a 404.
## Avoid
- Scope.REQUEST providers — scope is contagious and makes the whole
injection chain per-request. Use AsyncLocalStorage / nestjs-cls instead.
- forwardRef. One or two is a smell; more means a shared concept wants
extracting into a third module.
- @Global() on anything domain-specific. Config and logging only.
## Landmines
- src/billing/ posts to Stripe. Do not change it without asking.
- Migrations: write them, never run them. A human runs migrations.
- app.enableShutdownHooks() must stay in main.ts or connections leak on
every redeploy.Around sixty lines, and more than half of it is security. That ratio is right for Nest: TypeScript catches the type errors, the framework catches the structural ones, and what is left is the configuration that fails silently.
Enforce the greppable half#
Several of those rules are checkable in a second, so make them a script rather than a hope:
{ "scripts": { "check:conventions": "bash scripts/check-conventions.sh" } }#!/usr/bin/env bash
set -Eeuo pipefail
fail=0
check() { if grep -rqn "$1" src ${3:-}; then echo "✗ $2"; fail=1; fi; }
grep -rn "PrismaService\|Repository<" src --include="*.controller.ts" \
&& { echo "✗ database access in a controller"; fail=1; }
grep -rn "new ValidationPipe" src | grep -qv whitelist \
&& { echo "✗ ValidationPipe without whitelist"; fail=1; }
grep -rn 'data: { \.\.\.' src \
&& { echo "✗ DTO spread into an update (mass assignment)"; fail=1; }
grep -rln "passwordHash\|refreshToken" src | xargs -r grep -L "@Exclude" \
| grep -q . && { echo "✗ secret field without @Exclude"; fail=1; }
exit $failPut it in CI and in pnpm check. Four greps, under a second, and they cover the failures that actually cause incidents.
In a monorepo#
AGENTS.md commands, structure, security policy
apps/api/AGENTS.md route conventions, which guards are global
apps/worker/AGENTS.md queue semantics, idempotency requirements
libs/shared/AGENTS.md "imported by everything — changes are breaking"Common questions#
Does this replace the conventions page?#
No — Nest conventions for agents explains why the framework's structure makes generated code reviewable and what to grep for. This page is the file you actually put in the repo.
Why is the ownership check not in a guard?#
Because it depends on the record. A guard runs before the handler and would have to query the database itself, duplicating the work and drifting out of sync with the real query. Beside the query is where it stays correct — and that is exactly the kind of decision worth writing down, because it is not obvious.
Should I say which ORM?#
Yes, in the first line, and never allow a second one. A codebase with both Prisma and TypeORM is one where nobody knows which layer a given query goes through, and generated code will cheerfully use whichever it saw first.
Get the NestJS agent pack
A battle-tested AGENTS.md, the review checklist, and the failure-mode cheat sheet for NestJS. One email, then occasional updates when the tooling shifts. No course pitch.
AGENTS.md now — no email needed.