# Writing an AGENTS.md for NestJS

> Source: https://learn-nestjs.com/ai/agents-md/
> Part of Learn NestJS, free to read.

`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:

```bash
ln -s AGENTS.md CLAUDE.md
```

Nest 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:

```markdown
- 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](/review/failure-modes/).

## The template

```markdown AGENTS.md
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.

:::tip The test line pays for itself immediately
"Register the same global pipes in the test setup" catches a problem that bites nearly every Nest project once: `useGlobalPipes` lives in `main.ts`, the testing module never runs it, so your end-to-end tests validate nothing and pass on input production would reject.
:::

## Enforce the greppable half

Several of those rules are checkable in a second, so make them a script rather than a hope:

```json package.json
{ "scripts": { "check:conventions": "bash scripts/check-conventions.sh" } }
```

```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 $fail
```

Put it in CI and in `pnpm check`. Four greps, under a second, and they cover the failures that actually cause incidents.

## In a monorepo

```text
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](/ai/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.
