# Pattern: NestJS Modules (the way we use them)

Conventions for `apps/api-nest` and `apps/worker-nest`.

## Module shape

- One Nest module per cohesive feature: `modules/<name>/<name>.module.ts`.
- Inside: `controller.ts` (if HTTP), `service.ts`, `dto/`, optional `*.processor.ts` (for workers).
- Register in the host's `app.module.ts`.

## Cross-cutting (global)

Wire once in `main.ts`:

- `ValidationPipe({ whitelist: true, transform: true, forbidNonWhitelisted: true })`
- `AllExceptionsFilter` → RFC 7807 ProblemDetails
- `CorrelationIdInterceptor` → `X-Correlation-Id`
- `TransformInterceptor` → wraps responses in `ApiResponse<T>` envelope

## Enterprise-module wiring

In `app.module.ts`:

```ts
EmAuthModule.forRoot({ jwt: { accessSecret, issuer, audience }, registerGlobalGuard: false }),
EmRbacModule,
EmAuditModule.forRoot({ redisUrl }),
EmNotificationsModule.forRoot({ redisUrl }),
```

The audit/notification dispatchers become DI-available everywhere. Workers import the **sink** halves (`PrismaAuditSink`, channel implementations), not the dispatchers.

## DTOs

`class-validator` + `class-transformer` on DTOs:

```ts
export class LoginDto {
  @IsEmail() @MaxLength(256) email!: string;
  @IsString() @MinLength(6) @MaxLength(256) password!: string;
}
```

Then `@Body() dto: LoginDto` — the `ValidationPipe` does the rest.

## Authz on routes

```ts
@UseGuards(JwtAuthGuard)        // from em-auth (off-by-default global; opt in here)
@RequirePermissions('users.write')
@Post() create(@Body() dto: CreateUserDto) { ... }
```

## Anti-patterns

- Don't reach into `req` directly — use `@CurrentUser()`.
- Don't return raw entities — return DTOs (`TransformInterceptor` will wrap).
- Don't throw plain `Error` — throw Nest's HTTP exceptions or a domain-specific one filtered by `AllExceptionsFilter`.

## Related

- `apps/api-nest/README.md`
- `.claude/agents/backend-agent.md`
