nestjs-boost 0.0.0-stage → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -0
- package/LICENSE +21 -0
- package/NOTICE.md +6 -0
- package/README.md +168 -2
- package/bin/nestjs-boost.mjs +148 -0
- package/guidelines/core.md +64 -0
- package/lib/agents.mjs +137 -0
- package/lib/installer.mjs +450 -0
- package/lib/prompt.mjs +131 -0
- package/lib/setup.mjs +60 -0
- package/manifest.json +676 -0
- package/package.json +67 -6
- package/scripts/postinstall.mjs +49 -0
- package/skills/nestjs-ai/SKILL.md +145 -0
- package/skills/nestjs-ai/references/ai-reference.md +143 -0
- package/skills/nestjs-architecture/SKILL.md +83 -0
- package/skills/nestjs-architecture/references/dynamic-modules.md +92 -0
- package/skills/nestjs-architecture/references/scopes-lifecycle.md +98 -0
- package/skills/nestjs-architecture/references/troubleshooting.md +77 -0
- package/skills/nestjs-authentication/SKILL.md +115 -0
- package/skills/nestjs-authentication/references/flows.md +180 -0
- package/skills/nestjs-authorization/SKILL.md +104 -0
- package/skills/nestjs-caching/SKILL.md +109 -0
- package/skills/nestjs-cli-tooling/SKILL.md +94 -0
- package/skills/nestjs-cli-tooling/references/nest-cli-json.md +91 -0
- package/skills/nestjs-configuration/SKILL.md +109 -0
- package/skills/nestjs-cqrs/SKILL.md +124 -0
- package/skills/nestjs-database-drizzle/SKILL.md +117 -0
- package/skills/nestjs-database-mikro-orm/SKILL.md +109 -0
- package/skills/nestjs-database-mongoose/SKILL.md +124 -0
- package/skills/nestjs-database-prisma/SKILL.md +126 -0
- package/skills/nestjs-database-sequelize/SKILL.md +88 -0
- package/skills/nestjs-database-typeorm/SKILL.md +110 -0
- package/skills/nestjs-deployment/SKILL.md +133 -0
- package/skills/nestjs-deployment/references/serverless-and-systemd.md +133 -0
- package/skills/nestjs-events/SKILL.md +78 -0
- package/skills/nestjs-file-storage/SKILL.md +111 -0
- package/skills/nestjs-file-storage/references/storage-reference.md +82 -0
- package/skills/nestjs-graphql/SKILL.md +119 -0
- package/skills/nestjs-graphql/references/federation.md +110 -0
- package/skills/nestjs-graphql/references/schema-features.md +133 -0
- package/skills/nestjs-health-checks/SKILL.md +114 -0
- package/skills/nestjs-http-client/SKILL.md +118 -0
- package/skills/nestjs-http-client/references/http-client-reference.md +116 -0
- package/skills/nestjs-http-platform/SKILL.md +104 -0
- package/skills/nestjs-http-platform/references/adapters.md +88 -0
- package/skills/nestjs-http-web-features/SKILL.md +113 -0
- package/skills/nestjs-http-web-features/references/uploads-streaming.md +108 -0
- package/skills/nestjs-i18n/SKILL.md +113 -0
- package/skills/nestjs-idempotency/SKILL.md +112 -0
- package/skills/nestjs-locks/SKILL.md +108 -0
- package/skills/nestjs-logging/SKILL.md +123 -0
- package/skills/nestjs-mail/SKILL.md +125 -0
- package/skills/nestjs-mail/references/mail-reference.md +90 -0
- package/skills/nestjs-mcp-server/SKILL.md +152 -0
- package/skills/nestjs-mcp-server/references/mcp-reference.md +146 -0
- package/skills/nestjs-microservices/SKILL.md +108 -0
- package/skills/nestjs-microservices/references/transports.md +117 -0
- package/skills/nestjs-observability/SKILL.md +108 -0
- package/skills/nestjs-observability/references/apm-and-otel.md +106 -0
- package/skills/nestjs-openapi/SKILL.md +90 -0
- package/skills/nestjs-openapi/references/swagger-reference.md +180 -0
- package/skills/nestjs-outbox/SKILL.md +119 -0
- package/skills/nestjs-outbox/references/outbox-reference.md +74 -0
- package/skills/nestjs-passport-auth/SKILL.md +125 -0
- package/skills/nestjs-queues/SKILL.md +118 -0
- package/skills/nestjs-queues/references/bull-legacy.md +75 -0
- package/skills/nestjs-request-pipeline/SKILL.md +146 -0
- package/skills/nestjs-request-pipeline/references/filters-interceptors.md +114 -0
- package/skills/nestjs-request-pipeline/references/pipes-decorators.md +80 -0
- package/skills/nestjs-resilience/SKILL.md +116 -0
- package/skills/nestjs-resilience/references/resilience-reference.md +74 -0
- package/skills/nestjs-scheduling/SKILL.md +100 -0
- package/skills/nestjs-security-hardening/SKILL.md +99 -0
- package/skills/nestjs-security-hardening/references/throttler-advanced.md +78 -0
- package/skills/nestjs-standalone-apps/SKILL.md +78 -0
- package/skills/nestjs-testing/SKILL.md +118 -0
- package/skills/nestjs-testing/references/advanced-testing.md +62 -0
- package/skills/nestjs-upgrading/SKILL.md +82 -0
- package/skills/nestjs-upgrading/references/esm-switch.md +114 -0
- package/skills/nestjs-validation-serialization/SKILL.md +124 -0
- package/skills/nestjs-validation-serialization/references/validation-options.md +84 -0
- package/skills/nestjs-webhooks/SKILL.md +116 -0
- package/skills/nestjs-webhooks/references/webhooks-reference.md +132 -0
- package/skills/nestjs-websockets/SKILL.md +108 -0
- package/skills/nestjs-workflows/SKILL.md +123 -0
- package/skills/nestjs-workflows/references/workflows-reference.md +85 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nestjs-deployment
|
|
3
|
+
description: Ships NestJS 12 applications to production — build and run (npm run build, node dist/main.js, NODE_ENV=production, PORT), Node.js 20.19+/22.12+ requirements, Dockerfile and .dockerignore with multi-stage builds, graceful shutdown with enableShutdownHooks, health checks, JSON logging and Observe instrumentation, vertical vs horizontal scaling behind load balancers, nest deploy with Mau on AWS, serverless functions on AWS Lambda (@codegenie/serverless-express, cached bootstrap, webpack bundling, lazy-loaded modules, standalone contexts, Swagger trailing-slash fix) and cold-start optimisation, and systemd socket activation with idle exit. Use when containerising or deploying an app, preparing it for production, running it on Lambda or another serverless platform, reducing cold starts, or setting up zero-downtime restarts. Not for health-check details (nestjs-health-checks), telemetry (nestjs-observability) or security headers and rate limits (nestjs-security-hardening).
|
|
4
|
+
compatibility: NestJS 12.x; Node.js 20.19+ or 22.12+ (prefer active LTS); Docker, AWS Lambda/Serverless Framework, systemd as applicable
|
|
5
|
+
metadata:
|
|
6
|
+
version: "0.1.0"
|
|
7
|
+
nestjs-major: "12"
|
|
8
|
+
docs-crawled: "2026-11-05"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Deploying NestJS
|
|
12
|
+
|
|
13
|
+
## Production baseline
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm run build # → dist/
|
|
17
|
+
NODE_ENV=production node dist/main.js # not `nest start` (it rebuilds) and not npm in containers (signals)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
async function bootstrap() {
|
|
22
|
+
const app = await NestFactory.create(AppModule, { bufferLogs: true });
|
|
23
|
+
app.enableShutdownHooks(); // SIGTERM → onModuleDestroy/beforeApplicationShutdown/onApplicationShutdown
|
|
24
|
+
await app.listen(process.env.PORT ?? 3000, '0.0.0.0');
|
|
25
|
+
}
|
|
26
|
+
await bootstrap();
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- **Node.js:** NestJS 12 requires Node.js 20.19+ or 22.12+; prefer an active LTS. Pin the version in the Docker base image and in `engines`.
|
|
30
|
+
- **`NODE_ENV=production`:** some libraries behave differently with it.
|
|
31
|
+
- **Config:** read config from the environment or a secrets manager, never hard-coded (see nestjs-configuration). Validate it at startup so a missing variable fails the deploy, not the first request.
|
|
32
|
+
- **Health:** expose liveness and readiness probes (nestjs-health-checks) and point the load balancer or orchestrator at them.
|
|
33
|
+
- **Logging:** use JSON logs in production (nestjs-logging).
|
|
34
|
+
- **Telemetry:** `NestFactory.create(AppModule, { instrument: ObserveInstrument })` for NestJS Observe, or OpenTelemetry (nestjs-observability).
|
|
35
|
+
- **Security:** helmet, CORS, rate limiting and body limits (nestjs-security-hardening).
|
|
36
|
+
- **Process:** automate deploys with CI/CD and back up your data.
|
|
37
|
+
|
|
38
|
+
## Scaling
|
|
39
|
+
|
|
40
|
+
| Strategy | When | Requirements on the app |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| Vertical (bigger machine) | Moderate traffic; simplest | — |
|
|
43
|
+
| Horizontal (more instances behind a load balancer such as Nginx, ELB or Kubernetes) | High traffic, redundancy | **Stateless instances.** Sessions and cache go in Redis, not memory. Schedulers need distributed locks (nestjs-locks) or a single runner. WebSockets need the Redis adapter plus sticky sessions (nestjs-websockets). Uploads go to object storage (nestjs-file-storage). Run migrations once per deploy, not on every instance boot |
|
|
44
|
+
|
|
45
|
+
## Docker
|
|
46
|
+
|
|
47
|
+
```dockerfile
|
|
48
|
+
# syntax=docker/dockerfile:1
|
|
49
|
+
FROM node:24-slim AS build # match your project's Node version
|
|
50
|
+
WORKDIR /usr/src/app
|
|
51
|
+
COPY package*.json ./
|
|
52
|
+
RUN npm ci
|
|
53
|
+
COPY . .
|
|
54
|
+
RUN npm run build && npm prune --omit=dev
|
|
55
|
+
|
|
56
|
+
FROM node:24-slim
|
|
57
|
+
ENV NODE_ENV=production
|
|
58
|
+
WORKDIR /usr/src/app
|
|
59
|
+
COPY --from=build /usr/src/app/node_modules ./node_modules
|
|
60
|
+
COPY --from=build /usr/src/app/dist ./dist
|
|
61
|
+
COPY package*.json ./
|
|
62
|
+
USER node
|
|
63
|
+
EXPOSE 3000
|
|
64
|
+
CMD ["node", "dist/main"] # exec form: node is PID 1 and receives SIGTERM
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`.dockerignore`: `node_modules`, `dist`, `*.log`, `*.md`, `.git`, `.env*`.
|
|
68
|
+
|
|
69
|
+
Build with `docker build -t my-app .` and run with `docker run -p 3000:3000 my-app`. Push to Docker Hub, ECR or Artifact Registry.
|
|
70
|
+
|
|
71
|
+
The docs' sample is a single stage (`npm install`, `npm run build`, `CMD ["node", "dist/main"]`). The multi-stage version above follows the same steps, ships without dev dependencies or sources, and runs as a non-root user.
|
|
72
|
+
|
|
73
|
+
## Mau (official AWS platform)
|
|
74
|
+
|
|
75
|
+
`nest deploy` (v12 CLI) offers to add `@nestjs/mau` and forwards options to `mau deploy`. In CI, use `npm i -g @nestjs/mau && mau deploy`, which doesn't prompt. Mau provisions databases (PostgreSQL, MySQL, DocumentDB, Redis) and brokers (RabbitMQ, Kafka, NATS). It also manages environment variables, horizontal scaling, health-check-based instance replacement, CloudWatch metrics and backups.
|
|
76
|
+
|
|
77
|
+
## Serverless (AWS Lambda)
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm i @codegenie/serverless-express
|
|
81
|
+
npm i -D @types/aws-lambda serverless-offline
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
let server: Handler;
|
|
86
|
+
async function bootstrap(): Promise<Handler> {
|
|
87
|
+
const app = await NestFactory.create(AppModule);
|
|
88
|
+
await app.init(); // init, not listen
|
|
89
|
+
return configure({ app: app.getHttpAdapter().getInstance() });
|
|
90
|
+
}
|
|
91
|
+
export const handler: Handler = async (event, context, callback) => {
|
|
92
|
+
server = server ?? (await bootstrap()); // reuse across warm invocations
|
|
93
|
+
return server(event, context, callback);
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- **`serverless.yml`:** `runtime: nodejs22.x`, `handler: dist/main.handler`, `http` events for `/` and `{proxy+}`. Run `npx serverless offline` and test at `/dev/<route>`.
|
|
98
|
+
- **TypeScript:** set `esModuleInterop: true`.
|
|
99
|
+
- **Bundle with webpack** (`externals: []`, `libraryTarget: 'commonjs2'`, Terser with `keep_classnames`). Mark the lazy optional imports (`@nestjs/microservices/microservices-module`, `@nestjs/websockets/socket-module`, …) with `IgnorePlugin`. Benchmarks: an Express-based Nest app drops from ~197 ms to ~81 ms, and a standalone context from ~112 ms to ~32 ms.
|
|
100
|
+
- **CommonJS apps on Lambda's Node 20/22/24 runtimes** need `NODE_OPTIONS=--experimental-require-module`, because the v12 packages are ESM-only (nestjs-upgrading).
|
|
101
|
+
- **Cold starts are dominated by your async providers** (for example DB connections). Load rarely used modules on demand with `LazyModuleLoader`, and split functions by job (monorepo mode).
|
|
102
|
+
- **Non-HTTP handlers:** use `NestFactory.createApplicationContext(AppModule)` and `appContext.get(Service)`. This is lighter, but **no enhancers run**.
|
|
103
|
+
- **Swagger behind API Gateway:** register middleware *before* `SwaggerModule.setup()` that rewrites `originalUrl` `/swagger` → `/swagger/`. Without it, the redirect loops forever.
|
|
104
|
+
|
|
105
|
+
Details in `references/serverless-and-systemd.md`.
|
|
106
|
+
|
|
107
|
+
## systemd socket activation (start on first connection, zero dropped connections across restarts)
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const activated = process.env.LISTEN_FDS === '1' && process.env.LISTEN_PID === String(process.pid);
|
|
111
|
+
if (activated) await app.listen({ fd: 3 } as any); // Fastify: app.listen({ fd: 3 }) is typed
|
|
112
|
+
else await app.listen(process.env.PORT ?? 3000);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- Add a `.socket` unit (`ListenStream=3000`) and a `.service` unit (`ExecStart=/usr/bin/node dist/main.js`, `Restart=on-failure`).
|
|
116
|
+
- Enable the socket only: `systemctl enable --now my-app.socket`.
|
|
117
|
+
- **`ExecStart` must start `node` directly.** With npm, `nest start` or a shell wrapper, `LISTEN_PID` doesn't match, and the app fails with `EADDRINUSE`.
|
|
118
|
+
- Not supported on Windows.
|
|
119
|
+
- Optional idle exit frees memory (see the reference). Keep schedulers and queue consumers in an always-on process.
|
|
120
|
+
|
|
121
|
+
## Workflow
|
|
122
|
+
|
|
123
|
+
- [ ] 1. Validate config at startup, call `enableShutdownHooks()`, bind `0.0.0.0:$PORT`, use JSON logs, add health endpoints.
|
|
124
|
+
- [ ] 2. Make the app stateless for horizontal scaling (shared cache and sessions, locks for cron, WebSocket adapter).
|
|
125
|
+
- [ ] 3. Package: a multi-stage Docker image with a non-root `node` user and exec-form `CMD`, or the Lambda handler with a cached bootstrap and bundling, or systemd units.
|
|
126
|
+
- [ ] 4. CI/CD: build, lint, test, build the image, run migrations once, deploy (`nest deploy` / `mau deploy` or your platform), and smoke-test the health endpoint.
|
|
127
|
+
- [ ] 5. Locally: run the production build (`node dist/main.js` or `docker run`) and confirm graceful shutdown on SIGTERM.
|
|
128
|
+
|
|
129
|
+
## Reference files
|
|
130
|
+
|
|
131
|
+
- `references/serverless-and-systemd.md`: `serverless.yml`, the webpack config for Lambda, the standalone handler, the lazy-loading patterns, the systemd units, `systemd-socket-activate` for local trials, the idle-exit helper, and the compile-cache tip. Read when deploying to Lambda or systemd.
|
|
132
|
+
|
|
133
|
+
Sources: [Deployment](https://docs.nestjs.com/deployment) · [Serverless](https://docs.nestjs.com/faq/serverless) · [Socket activation](https://docs.nestjs.com/recipes/socket-activation)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Serverless and systemd details
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
- serverless.yml
|
|
5
|
+
- Bundling with webpack
|
|
6
|
+
- Standalone handlers
|
|
7
|
+
- Runtime optimisations
|
|
8
|
+
- systemd units
|
|
9
|
+
- Idle exit and compile cache
|
|
10
|
+
|
|
11
|
+
## serverless.yml
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
service: serverless-example
|
|
15
|
+
plugins:
|
|
16
|
+
- serverless-offline
|
|
17
|
+
provider:
|
|
18
|
+
name: aws
|
|
19
|
+
runtime: nodejs22.x
|
|
20
|
+
functions:
|
|
21
|
+
main:
|
|
22
|
+
handler: dist/main.handler
|
|
23
|
+
events:
|
|
24
|
+
- http: { method: ANY, path: / }
|
|
25
|
+
- http: { method: ANY, path: '{proxy+}' }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Test locally with `npm run build && npx serverless offline`, then open `http://localhost:3000/dev/<route>`. Set `"esModuleInterop": true` in `tsconfig.json`. Use CLI monorepo mode for several functions that share modules.
|
|
29
|
+
|
|
30
|
+
## Bundling with webpack
|
|
31
|
+
|
|
32
|
+
`webpack.config.js` in the project root; build with `nest build --webpack`:
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
const TerserPlugin = require('terser-webpack-plugin'); // optional; slows the build
|
|
36
|
+
|
|
37
|
+
module.exports = (options, webpack) => {
|
|
38
|
+
const lazyImports = ['@nestjs/microservices/microservices-module', '@nestjs/websockets/socket-module'];
|
|
39
|
+
return {
|
|
40
|
+
...options,
|
|
41
|
+
externals: [], // bundle node_modules
|
|
42
|
+
output: { ...options.output, libraryTarget: 'commonjs2' },
|
|
43
|
+
optimization: { minimizer: [new TerserPlugin({ terserOptions: { keep_classnames: true } })] }, // class-validator needs class names
|
|
44
|
+
plugins: [
|
|
45
|
+
...options.plugins,
|
|
46
|
+
new webpack.IgnorePlugin({
|
|
47
|
+
checkResource(resource) {
|
|
48
|
+
if (lazyImports.includes(resource)) {
|
|
49
|
+
try { require.resolve(resource); } catch { return true; }
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Docs benchmarks (starter app): Express ~7 ms; Nest/Express 197 ms unbundled → 81.5 ms bundled; standalone context 112 ms → 32 ms. An app with 10 resources takes ~130 ms bundled.
|
|
60
|
+
|
|
61
|
+
## Standalone handlers
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
export const handler: Handler = async (event, context, callback) => {
|
|
65
|
+
const appContext = await NestFactory.createApplicationContext(AppModule);
|
|
66
|
+
return appContext.get(EventsService).process(event); // no guards/interceptors/pipes (no HTTP layer)
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Cache the context in a module-level variable, as with the HTTP handler, to reuse it on warm invocations.
|
|
71
|
+
|
|
72
|
+
## Runtime optimisations
|
|
73
|
+
|
|
74
|
+
- Async providers that connect on boot add their latency to every cold start. A 2 s DB connection means at least 2 s extra on the first response.
|
|
75
|
+
- Load rarely used modules lazily:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
if (request.method === RequestMethod[RequestMethod.GET]) {
|
|
79
|
+
const { CacheModule } = await import('./cache.module.js');
|
|
80
|
+
const moduleRef = await this.lazyModuleLoader.load(() => CacheModule);
|
|
81
|
+
const { CacheService } = await import('./cache.service.js');
|
|
82
|
+
return moduleRef.get(CacheService).get(ENDPOINT_KEY);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- Use the same pattern for workers or webhooks that branch on input (load `WorkerAModule` or `WorkerBModule` on demand).
|
|
87
|
+
- Swagger UI behind API Gateway: `app.use((req, res, next) => { if (req.originalUrl === '/swagger') req.originalUrl = '/swagger/'; next(); })` before `SwaggerModule.setup()`.
|
|
88
|
+
|
|
89
|
+
## systemd units
|
|
90
|
+
|
|
91
|
+
```ini
|
|
92
|
+
# /etc/systemd/system/my-app.socket
|
|
93
|
+
[Socket]
|
|
94
|
+
ListenStream=3000
|
|
95
|
+
[Install]
|
|
96
|
+
WantedBy=sockets.target
|
|
97
|
+
|
|
98
|
+
# /etc/systemd/system/my-app.service
|
|
99
|
+
[Service]
|
|
100
|
+
WorkingDirectory=/srv/my-app
|
|
101
|
+
ExecStart=/usr/bin/node dist/main.js
|
|
102
|
+
Restart=on-failure
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
sudo systemctl daemon-reload
|
|
107
|
+
sudo systemctl enable --now my-app.socket # the service starts on the first connection
|
|
108
|
+
systemd-socket-activate --listen=3000 --setenv=NODE_ENV node dist/main.js # quick local trial (starts once)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
systemd passes fd 3 and sets `LISTEN_FDS`/`LISTEN_PID`. Connections that arrive during a start or restart wait in the socket backlog instead of being refused. Express and the Node adapter pass `{ fd: 3 }` straight to `server.listen()`, but their typings need `as any`.
|
|
112
|
+
|
|
113
|
+
## Idle exit and compile cache
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
export function exitWhenIdle(app: INestApplication, idleMs: number) {
|
|
117
|
+
const server: Server = app.getHttpServer();
|
|
118
|
+
let inFlight = 0;
|
|
119
|
+
let timer: NodeJS.Timeout | undefined;
|
|
120
|
+
const scheduleExit = () => { timer = setTimeout(async () => { await app.close(); process.exit(0); }, idleMs); };
|
|
121
|
+
server.on('request', (req, res) => {
|
|
122
|
+
inFlight++; clearTimeout(timer);
|
|
123
|
+
res.once('close', () => { if (--inFlight === 0) scheduleExit(); });
|
|
124
|
+
});
|
|
125
|
+
scheduleExit();
|
|
126
|
+
}
|
|
127
|
+
// if (activated) { await app.listen({ fd: 3 } as any); exitWhenIdle(app, 5 * 60_000); }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
- Use `Restart=on-failure`. With `Restart=always`, the app restarts immediately after an idle exit.
|
|
131
|
+
- Uptime monitors and health probes keep the app awake.
|
|
132
|
+
- Cron jobs and queue consumers don't run while the app is stopped.
|
|
133
|
+
- The first request waits for the full bootstrap. Speed up restarts with Node's compile cache: add `Environment=NODE_COMPILE_CACHE=/var/cache/my-app` and `CacheDirectory=my-app` to the service unit. The cache is written on exit, so `enableShutdownHooks()` is required for SIGTERM.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nestjs-events
|
|
3
|
+
description: Decouples NestJS 12 modules with in-process events using @nestjs/event-emitter (EventEmitter2) — EventEmitterModule.forRoot options, emitting typed event classes with EventEmitter2.emit/emitAsync, @OnEvent listeners with async, prependListener and suppressErrors options, namespaces and wildcards (order.*, **), request-scoped listeners, avoiding lost events at startup with EventEmitterReadinessWatcher, and testing emitters and listeners. Use when one module should react to something another module did (send a welcome email after sign-up, update stats after an order) without a direct dependency, or when listeners don't fire, errors vanish or startup events are missed. Not for durable/cross-process messaging (nestjs-outbox, nestjs-queues, nestjs-microservices), CQRS buses (nestjs-cqrs) or real-time client push (nestjs-websockets, SSE in nestjs-http-web-features).
|
|
4
|
+
compatibility: NestJS 12.x; @nestjs/event-emitter
|
|
5
|
+
metadata:
|
|
6
|
+
version: "0.1.0"
|
|
7
|
+
nestjs-major: "12"
|
|
8
|
+
docs-crawled: "2026-11-05"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# NestJS events (@nestjs/event-emitter)
|
|
12
|
+
|
|
13
|
+
In-process, fire-and-forget, **not durable**: events are lost if the process crashes or a listener throws. If the reaction must happen (emails, webhooks, other services), write it to the outbox (nestjs-outbox) or a queue (nestjs-queues) in the same transaction instead.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i @nestjs/event-emitter
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
@Module({ imports: [EventEmitterModule.forRoot({ wildcard: true, delimiter: '.', maxListeners: 20 })] })
|
|
23
|
+
export class AppModule {}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Registered globally. Listeners are registered in `onApplicationBootstrap`. Other options: `newListener`, `removeListener`, `verboseMemoryLeak`, `ignoreErrors`.
|
|
27
|
+
|
|
28
|
+
## Emit and listen
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// orders/events/order-created.event.ts
|
|
32
|
+
export class OrderCreatedEvent {
|
|
33
|
+
static readonly name = 'order.created'; // keep event names in one place
|
|
34
|
+
constructor(readonly orderId: string, readonly userId: string) {}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// orders/orders.service.ts
|
|
38
|
+
constructor(private readonly events: EventEmitter2) {}
|
|
39
|
+
async create(dto: CreateOrderDto) {
|
|
40
|
+
const order = await this.repo.create(dto);
|
|
41
|
+
this.events.emit(OrderCreatedEvent.name, new OrderCreatedEvent(order.id, dto.userId));
|
|
42
|
+
return order;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// notifications/order-listener.ts
|
|
46
|
+
@Injectable()
|
|
47
|
+
export class OrderCreatedListener {
|
|
48
|
+
private readonly logger = new Logger(OrderCreatedListener.name);
|
|
49
|
+
@OnEvent(OrderCreatedEvent.name, { async: true })
|
|
50
|
+
async handle(event: OrderCreatedEvent) { await this.mailer.sendReceipt(event.orderId); }
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- Listener classes must be providers of a module that's loaded.
|
|
55
|
+
- `emit()` returns immediately with sync listeners run inline; `emitAsync()` returns a promise of all listener results (use when the caller must wait).
|
|
56
|
+
- `@OnEvent` options: `async`, `prependListener`, `suppressErrors` (default **true**: errors are logged, not rethrown), plus EventEmitter2 `OnOptions`.
|
|
57
|
+
- Emit after the transaction commits; emitting inside it notifies listeners about data that may roll back.
|
|
58
|
+
- Wildcards (with `wildcard: true`): `order.*` matches one segment; `**` matches all. Events can be arrays (`['order', 'created']`).
|
|
59
|
+
- Request-scoped listener providers get a new instance per event; `REQUEST` resolves to the payload.
|
|
60
|
+
|
|
61
|
+
## Startup events
|
|
62
|
+
|
|
63
|
+
Events emitted from constructors or `onModuleInit` can be lost. In `onApplicationBootstrap`, `await this.readiness.waitUntilReady()` (`EventEmitterReadinessWatcher`) before emitting.
|
|
64
|
+
|
|
65
|
+
## Testing
|
|
66
|
+
|
|
67
|
+
- Emitter: provide `{ provide: EventEmitter2, useValue: { emit: vi.fn() } }` and assert the event name and payload.
|
|
68
|
+
- Listener: call the method directly with an event instance.
|
|
69
|
+
- Integration: import `EventEmitterModule.forRoot()` in the testing module, call `app.init()` (listeners register on bootstrap), emit with `emitAsync()` and await.
|
|
70
|
+
|
|
71
|
+
## Workflow
|
|
72
|
+
|
|
73
|
+
- [ ] 1. Install; `EventEmitterModule.forRoot()` in `AppModule`.
|
|
74
|
+
- [ ] 2. Define event classes with a name constant; emit after commit.
|
|
75
|
+
- [ ] 3. Add listener providers with `@OnEvent`; decide `async` and error handling (log + metrics; don't rely on suppressed errors silently).
|
|
76
|
+
- [ ] 4. Tests for emit + listener. Build, lint, run tests, start the app.
|
|
77
|
+
|
|
78
|
+
Sources: [Events](https://docs.nestjs.com/application/events)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nestjs-file-storage
|
|
3
|
+
description: Stores files in NestJS 12 with the first-party @nestjs/storage package — named disks (LocalDisk for development, S3Disk for AWS S3, Cloudflare R2, MinIO, Backblaze B2, GCS; InMemoryDisk for tests) behind one API (put/get/stat/list/delete/copy/move), StorageModule.forRootAsync from config, @InjectDisk and the Storage registry, streaming Multer uploads straight to a disk with uploadToDisk() and content sniffing, public URLs, short-lived signed download URLs, presigned direct uploads with signedUpload(), serveFile/serveSignedUrl/receiveSignedUpload helpers, key safety, and storage errors. Use when the user needs to save uploads, product images, invoices or user files, generate download links, upload directly from clients to S3/R2, or switch between local and cloud storage. Not for parsing multipart requests themselves (nestjs-http-web-features) or static frontend hosting (serve-static in nestjs-http-web-features).
|
|
4
|
+
compatibility: NestJS 12.x; @nestjs/storage (no AWS SDK needed); @nestjs/config
|
|
5
|
+
metadata:
|
|
6
|
+
version: "0.1.0"
|
|
7
|
+
nestjs-major: "12"
|
|
8
|
+
docs-crawled: "2026-11-05"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# NestJS file storage (@nestjs/storage)
|
|
12
|
+
|
|
13
|
+
Use `@nestjs/storage` instead of hand-written `fs` writes or the AWS SDK: same code for local and S3, atomic writes, signed URLs, content sniffing.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i @nestjs/storage @nestjs/config
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Disks from configuration
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
export function createStorageOptions(config: ConfigService): StorageModuleOptions {
|
|
23
|
+
if (config.get('storage.driver') === 's3') {
|
|
24
|
+
const s3 = { endpoint: config.get<string>('storage.s3.endpoint'), region: config.getOrThrow<string>('storage.s3.region') };
|
|
25
|
+
return {
|
|
26
|
+
default: 'private',
|
|
27
|
+
disks: {
|
|
28
|
+
photos: new S3Disk({ ...s3, bucket: config.getOrThrow('storage.s3.photosBucket'), publicUrl: config.getOrThrow('storage.s3.photosUrl') }),
|
|
29
|
+
private: new S3Disk({ ...s3, bucket: config.getOrThrow('storage.s3.privateBucket') }),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const appUrl = config.getOrThrow<string>('appUrl');
|
|
34
|
+
return {
|
|
35
|
+
default: 'private',
|
|
36
|
+
disks: {
|
|
37
|
+
photos: new LocalDisk({ root: 'storage/photos', publicUrl: `${appUrl}/photos` }),
|
|
38
|
+
private: new LocalDisk({ root: 'storage/private', signedUrls: { baseUrl: `${appUrl}/files`, keys: [config.getOrThrow('storage.signingKey')] } }), // key ≥32 chars
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
StorageModule.forRootAsync({ inject: [ConfigService], useFactory: createStorageOptions }),
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- Separate disks (buckets) for public and private files — there is no per-file public flag.
|
|
47
|
+
- Inject: `StorageDisk` (default disk), `@InjectDisk('photos') photos: StorageDisk`, or `Storage` (`storage.disk(name)`). Misnamed disks fail at startup.
|
|
48
|
+
- `S3Disk` reads `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`/`AWS_SESSION_TOKEN` (or `credentials`). R2: `endpoint: 'https://<account>.r2.cloudflarestorage.com', region: 'auto'`; MinIO: `forcePathStyle: true`; GCS: `batchDelete: false`.
|
|
49
|
+
- Never `LocalDisk` in production (container disks vanish, instances don't share).
|
|
50
|
+
|
|
51
|
+
## Stream uploads onto a disk
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
@Post('products/:id/photo')
|
|
55
|
+
@UseInterceptors(FileInterceptor('photo', {
|
|
56
|
+
storage: uploadToDisk({ disk: 'photos', contentTypes: ['image/jpeg', 'image/png', 'image/webp'], cacheControl: 'public, max-age=31536000, immutable' }),
|
|
57
|
+
limits: { fileSize: 5 * 1024 * 1024, files: 1 },
|
|
58
|
+
}))
|
|
59
|
+
async upload(@Param('id') id: string, @UploadedFile() file: StoredUpload | undefined) {
|
|
60
|
+
if (!file) throw new BadRequestException('Attach the image as the "photo" field');
|
|
61
|
+
return this.photosService.replace(id, file.key); // delete file.key if the product doesn't exist
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- `uploadToDisk()` sniffs the real type from the bytes (415 if not allowed), enforces the size without leaving partial files (413), and picks a random UUID key with the detected extension. Use `file.contentType`, not the client's `mimetype`.
|
|
66
|
+
- `ParseFilePipe`/`FileTypeValidator` run after the file is stored — validate in the engine instead.
|
|
67
|
+
- Formats without a signature (CSV): `detectContentType: (bytes) => …` returning a type in `contentTypes`; still validate the whole file afterwards.
|
|
68
|
+
- Custom keys: `key: (file) => \`price-lists/${randomUUID()}${file.extension}\``. Build keys from ids you control.
|
|
69
|
+
|
|
70
|
+
## Private files and signed URLs
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const key = `invoices/${order.id}.pdf`;
|
|
74
|
+
if (!(await this.disk.exists(key))) await this.disk.put(key, pdfBuffer, { contentType: 'application/pdf', metadata: { orderId: order.id } });
|
|
75
|
+
return { url: await this.disk.signedUrl(key, { expiresIn: '5m', filename: `invoice-${order.id}.pdf` }) };
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- Check ownership/authorization before issuing links (nestjs-authorization). Signed URLs can't be revoked: keep them short (default 15 min, max 7 days).
|
|
79
|
+
- Local disks: add a `FilesController` with `@Get() serveSignedUrl(disk, { req, res })` and `@Put() receiveSignedUpload(disk, { req, res })` under the `signedUrls.baseUrl` route (404 with S3, which serves presigned URLs itself).
|
|
80
|
+
- Public files: `disk.url(key)` (under `publicUrl`, usually a CDN); dev route: `serveFile(disk, key, { req, res, disposition: 'inline' })`.
|
|
81
|
+
|
|
82
|
+
## Direct uploads from clients
|
|
83
|
+
|
|
84
|
+
1. `POST …/uploads` → `uploads.signedUpload(\`incoming/${productId}/${randomUUID()}\`, { contentType, contentLength: size, expiresIn: '10m' })` returns `{ url, method: 'PUT', headers, expiresAt }`.
|
|
85
|
+
2. Client `PUT`s the file.
|
|
86
|
+
3. `PUT …/photo { key }` → verify the key prefix you issued, `stat()` the size, ranged `get(key, { range: { start: 0, end: 15 } })` + `detectContentType()`, then stream `get()` → `put()` onto the public disk and delete the incoming file.
|
|
87
|
+
|
|
88
|
+
Expire `incoming/` and incomplete multipart uploads with bucket lifecycle rules; enable bucket CORS for browser PUTs.
|
|
89
|
+
|
|
90
|
+
## Testing
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const photos = new InMemoryDisk({ publicUrl: 'https://photos.example.com' });
|
|
94
|
+
const files = new InMemoryDisk({ signedUrls: { baseUrl: 'https://api.example.com/files', keys: ['test-signing-key-0123456789abcdef'] } });
|
|
95
|
+
// .overrideProvider(STORAGE_MODULE_OPTIONS).useValue({ default: 'private', disks: { photos, private: files } })
|
|
96
|
+
expect(photos.keys()).toHaveLength(1); // photos.getBuffer(key), photos.clear()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Workflow
|
|
100
|
+
|
|
101
|
+
- [ ] 1. Install; config file with driver/buckets/signing key; `createStorageOptions` factory; `StorageModule.forRootAsync`.
|
|
102
|
+
- [ ] 2. Uploads via `FileInterceptor` + `uploadToDisk()` with types, size and cache headers.
|
|
103
|
+
- [ ] 3. Private files with signed URLs after authorization; dev `FilesController`.
|
|
104
|
+
- [ ] 4. Direct uploads if clients send large files; lifecycle rules, CORS, least-privilege IAM (`GetObject`, `PutObject`, `DeleteObject`, `ListBucket`).
|
|
105
|
+
- [ ] 5. InMemoryDisk e2e tests. Build, lint, run tests, start the app.
|
|
106
|
+
|
|
107
|
+
## Reference files
|
|
108
|
+
|
|
109
|
+
- `references/storage-reference.md`: disk methods and key rules, disk options (S3 encryption, multipart, retries), HTTP helper options, errors with statuses, Fastify raw-body note. Read when using methods beyond the basics or mapping errors.
|
|
110
|
+
|
|
111
|
+
Sources: [File storage](https://docs.nestjs.com/application/file-storage)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# @nestjs/storage reference
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
- Module options
|
|
5
|
+
- Keys
|
|
6
|
+
- Disk methods
|
|
7
|
+
- Disks
|
|
8
|
+
- HTTP helpers
|
|
9
|
+
- Errors
|
|
10
|
+
- Notes
|
|
11
|
+
|
|
12
|
+
## Module options
|
|
13
|
+
|
|
14
|
+
`StorageModule.forRoot()` / `forRootAsync()` (factory returns `default` + disk instances; disk classes, `imports`, `isGlobal` beside `useFactory`). Token: `STORAGE_MODULE_OPTIONS`.
|
|
15
|
+
|
|
16
|
+
| Option | Default | Notes |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `disks` | required | name → instance or injectable `StorageDisk` class |
|
|
19
|
+
| `default` | the only disk | required with more than one disk |
|
|
20
|
+
| `imports` | none | for disk classes |
|
|
21
|
+
| `isGlobal` | `true` | registers `Storage`, `StorageDisk`, named disks |
|
|
22
|
+
|
|
23
|
+
## Keys
|
|
24
|
+
|
|
25
|
+
Relative `/`-separated paths ≤1024 UTF-8 bytes; no empty, `.` or `..` segments, backslashes or control characters. Enforced on every disk, so user input can't escape a disk — but it can name any file on it.
|
|
26
|
+
|
|
27
|
+
## Disk methods
|
|
28
|
+
|
|
29
|
+
| Method | Notes |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `put(key, body, { contentType, cacheControl, contentDisposition, metadata (≤2 KB strings), contentLength, signal })` | atomic; body: Buffer, string, Uint8Array, Node/web stream, async iterable → `{ key, size, contentType, etag }` |
|
|
32
|
+
| `get(key, { range: { start, end } \| { suffix } })` | metadata + `body` (Node stream) |
|
|
33
|
+
| `getBuffer(key)`, `getText(key)` | whole file |
|
|
34
|
+
| `stat(key)`, `exists(key)` | metadata only / boolean |
|
|
35
|
+
| `delete(key \| keys[])` | missing keys ignored |
|
|
36
|
+
| `list({ prefix, cursor, limit ≤1000 })`, `listAll(opts)` | pages of `key`, `size`, `lastModified`, `etag` |
|
|
37
|
+
| `copy(from, to)`, `move(from, to)` | within the disk, metadata included |
|
|
38
|
+
| `url(key)` | public URL; throws without `publicUrl` |
|
|
39
|
+
| `signedUrl(key, { expiresIn, filename, disposition })` | default 15 min, max 7 days |
|
|
40
|
+
| `signedUpload(key, { contentType, contentLength, expiresIn })` | `{ url, method, headers, expiresAt }`; signed for exact type and length |
|
|
41
|
+
|
|
42
|
+
## Disks
|
|
43
|
+
|
|
44
|
+
| Disk | Options |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `LocalDisk` | `root` (created at startup), `publicUrl`, `signedUrls`; `path(key)` |
|
|
47
|
+
| `S3Disk` | `bucket`, `region` (`AWS_REGION` → `AWS_DEFAULT_REGION` → `us-east-1`), `endpoint`, `forcePathStyle`, `credentials` (object or function), `prefix`, `serverSideEncryption` (`'aws:kms'`), `kmsKeyId`, `multipart`, `retry`, `timeout`, `batchDelete`, `fetch`, `publicUrl` |
|
|
48
|
+
| `InMemoryDisk` | `publicUrl`, `signedUrls`; `keys()`, `clear()` |
|
|
49
|
+
|
|
50
|
+
`signedUrls: { baseUrl, keys }` — keys ≥32 chars, first signs, all verify (rotate by prepending). S3 uploads >8 MiB (or unknown length) go multipart (8 MiB parts, 4 at a time), aborted on failure. SigV4 implemented on `node:crypto`.
|
|
51
|
+
|
|
52
|
+
## HTTP helpers
|
|
53
|
+
|
|
54
|
+
| Helper | Options |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `uploadToDisk(opts)` | `disk`, `contentTypes`, `detectContentType`, `contentTypeSampleBytes` (4 KiB default, 1 B–64 KiB), `cacheControl`, `key(file, req)`, `metadata(file, req)` → `StoredUpload` (`key`, `disk`, `contentType`, `etag` + multer fields) |
|
|
57
|
+
| `serveFile(disk, key, { res, req, disposition, filename, cacheControl })` | Range and If-None-Match with `req`; sets `X-Content-Type-Options: nosniff` |
|
|
58
|
+
| `serveSignedUrl(disk, { req, res, cacheControl })` | verifies signature, expiry, method |
|
|
59
|
+
| `receiveSignedUpload(disk, { req, res, maxSize })` | verifies and streams the PUT body |
|
|
60
|
+
| `detectContentType(bytes)` | JPEG, PNG, GIF, WebP, AVIF, HEIC, PDF from first 16 bytes |
|
|
61
|
+
|
|
62
|
+
Fastify: the upload body must reach `receiveSignedUpload` unparsed — register a catch-all content-type parser that leaves the body alone.
|
|
63
|
+
|
|
64
|
+
## Errors
|
|
65
|
+
|
|
66
|
+
All extend `StorageError` (not `HttpException` outside the helpers — map them yourself):
|
|
67
|
+
|
|
68
|
+
| Error | status |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `StorageFileNotFoundError` (`key`) | 404 |
|
|
71
|
+
| `StorageInvalidKeyError` (`reason`) | 400 |
|
|
72
|
+
| `StorageBodyLengthError` (`expected`, `received`) | 400 |
|
|
73
|
+
| `StorageRangeNotSatisfiableError` (`size`) | 416 |
|
|
74
|
+
| `StorageKeyConflictError` (local disk `a` vs `a/b`) | 409 |
|
|
75
|
+
| `StorageSignedUrlError` (`missing`, `invalid`, `expired`, `method`) | 403 |
|
|
76
|
+
| `StorageServiceError` (`code`, `upstreamStatus`, `requestId`, `operation`) | — |
|
|
77
|
+
|
|
78
|
+
## Notes
|
|
79
|
+
|
|
80
|
+
- IAM: keep `s3:ListBucket`, or missing files return 403 and `exists()` throws.
|
|
81
|
+
- Local disks keep temp files and metadata in `.nest-storage`.
|
|
82
|
+
- To test real S3 behaviour (presigned URLs, multipart) point `S3Disk` at a local MinIO.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nestjs-graphql
|
|
3
|
+
description: Builds GraphQL APIs with NestJS 12 and @nestjs/graphql v14 — Apollo Server 5 (@nestjs/apollo) or Mercurius drivers, code-first (@ObjectType, @Field, @Resolver, @Query, @Mutation, @ResolveField, @Args, @InputType, @ArgsType) or schema-first (typePaths, generated definitions), GraphiQL (playground removed), context with req/res, subscriptions over graphql-ws with PubSub, filters and onConnect auth, custom scalars, directives, interfaces, unions and enums, field middleware, mapped types, Apollo plugins and query complexity, extensions, the GraphQL CLI plugin, schema generation, sharing models, guards/interceptors/filters with GqlExecutionContext, fieldResolverEnhancers, and Apollo/Mercurius Federation 1 and 2 with subgraphs and gateways. Use when adding or debugging a GraphQL endpoint, resolvers, types, subscriptions, federation, or migrating from playground/subscriptions-transport-ws/Apollo Server 4. Not for REST/OpenAPI (nestjs-openapi) or WebSocket gateways (nestjs-websockets).
|
|
4
|
+
compatibility: NestJS 12.x; @nestjs/graphql v14, @nestjs/apollo v14 (Apollo Server v5) or @nestjs/mercurius; graphql
|
|
5
|
+
metadata:
|
|
6
|
+
version: "0.1.0"
|
|
7
|
+
nestjs-major: "12"
|
|
8
|
+
docs-crawled: "2026-11-05"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# NestJS GraphQL
|
|
12
|
+
|
|
13
|
+
## Install and register
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i @nestjs/graphql @nestjs/apollo @apollo/server @as-integrations/express5 graphql # Express/Node adapter
|
|
17
|
+
# Fastify + Apollo: @as-integrations/fastify instead; Fastify + Mercurius: @nestjs/graphql @nestjs/mercurius graphql mercurius
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
GraphQLModule.forRootAsync<ApolloDriverConfig>({
|
|
22
|
+
driver: ApolloDriver,
|
|
23
|
+
inject: [ConfigService],
|
|
24
|
+
useFactory: (config: ConfigService) => ({
|
|
25
|
+
autoSchemaFile: join(process.cwd(), 'src/schema.gql'), // code first; `true` = in memory
|
|
26
|
+
sortSchema: true,
|
|
27
|
+
graphiql: config.get('NODE_ENV') !== 'production', // GraphiQL (playground was removed in v14)
|
|
28
|
+
context: ({ req, res }) => ({ req, res }), // a FUNCTION: per-request context
|
|
29
|
+
subscriptions: { 'graphql-ws': true },
|
|
30
|
+
}),
|
|
31
|
+
}),
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- `@nestjs/apollo` v14 requires Apollo Server v5 (`@apollo/server`). `playground` is only a deprecated boolean alias for `graphiql`. `subscriptions-transport-ws` support is removed — use `graphql-ws`.
|
|
35
|
+
- Detect the project's approach (code first: `autoSchemaFile`; schema first: `typePaths`) and stay with it.
|
|
36
|
+
- Node adapter: use the Apollo driver; Mercurius requires Fastify.
|
|
37
|
+
|
|
38
|
+
## Code first essentials
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
@ObjectType({ description: 'Author' })
|
|
42
|
+
export class Author {
|
|
43
|
+
@Field(() => ID) id: string;
|
|
44
|
+
@Field({ nullable: true }) firstName?: string;
|
|
45
|
+
@Field(() => [Post]) posts: Post[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@InputType()
|
|
49
|
+
export class CreatePostInput {
|
|
50
|
+
@Field() @MinLength(3) title: string;
|
|
51
|
+
@Field(() => Int, { nullable: true }) votes?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@ArgsType()
|
|
55
|
+
export class GetAuthorArgs {
|
|
56
|
+
@Field(() => ID) id: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Resolver(() => Author)
|
|
60
|
+
export class AuthorsResolver {
|
|
61
|
+
constructor(private readonly authors: AuthorsService, private readonly posts: PostsService) {}
|
|
62
|
+
|
|
63
|
+
@Query(() => Author, { name: 'author', nullable: true })
|
|
64
|
+
author(@Args() { id }: GetAuthorArgs) { return this.authors.findOne(id); }
|
|
65
|
+
|
|
66
|
+
@Mutation(() => Post)
|
|
67
|
+
createPost(@Args('input') input: CreatePostInput) { return this.posts.create(input); }
|
|
68
|
+
|
|
69
|
+
@ResolveField('posts', () => [Post])
|
|
70
|
+
posts(@Parent() author: Author) { return this.posts.findByAuthor(author.id); } // batch with DataLoader to avoid N+1
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- Types need explicit `() => Int | Float | ID | [Type]` where TypeScript reflection can't tell (numbers, arrays, nullables); or enable the GraphQL CLI plugin (`"plugins": ["@nestjs/graphql"]`, files `*.input.ts`, `*.args.ts`, `*.entity.ts`, `*.model.ts`).
|
|
75
|
+
- Arrays/nullability: `nullable: 'items' | 'itemsAndList'`.
|
|
76
|
+
- Validation: a global `ValidationPipe` (or `StandardSchemaValidationPipe`) validates `@Args` classes.
|
|
77
|
+
- Derive input types with `PartialType`, `PickType`, `OmitType`, `IntersectionType` from **`@nestjs/graphql`**.
|
|
78
|
+
- `@Resolver` on methods isn't supported in code first. Register resolvers as providers.
|
|
79
|
+
- Scaffold with `nest g resource` (GraphQL code first option).
|
|
80
|
+
|
|
81
|
+
## Subscriptions
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
{ provide: PUB_SUB, useValue: new PubSub() } // graphql-subscriptions; in-memory — use a Redis-backed PubSub with several instances
|
|
85
|
+
|
|
86
|
+
@Subscription(() => Comment, { filter: (payload, vars) => payload.commentAdded.postId === vars.postId })
|
|
87
|
+
commentAdded(@Args('postId') postId: string) { return this.pubSub.asyncIterableIterator('commentAdded'); }
|
|
88
|
+
|
|
89
|
+
// in a mutation
|
|
90
|
+
await this.pubSub.publish('commentAdded', { commentAdded: comment }); // payload shape: { <subscriptionName>: value } unless `resolve` is used
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Authenticate in `subscriptions['graphql-ws'].onConnect(ctx)` (read `ctx.connectionParams`, store the user on `ctx.extra`) and build `context: ({ req, extra }) => …`. Custom server: pass `server: new WebSocketServer({ noServer: true, path })`.
|
|
94
|
+
|
|
95
|
+
## Guards, interceptors, filters
|
|
96
|
+
|
|
97
|
+
- Convert contexts: `GqlExecutionContext.create(context).getContext().req` in guards/interceptors/param decorators; `GqlArgumentsHost.create(host)` in filters implementing `GqlExceptionFilter` (return/throw the error; there is no response object).
|
|
98
|
+
- Global enhancers run on root resolvers (Query/Mutation/Subscription) only. `fieldResolverEnhancers: ['guards', 'interceptors', 'filters']` enables them for field resolvers — costly on large lists.
|
|
99
|
+
- `CacheInterceptor` doesn't work in GraphQL (runs per field).
|
|
100
|
+
- With `@nestjs/authentication` / `@nestjs/authorization`, see those skills for the context setup.
|
|
101
|
+
|
|
102
|
+
## Hardening
|
|
103
|
+
|
|
104
|
+
Limit query cost with an Apollo `@Plugin()` using `graphql-query-complexity` (`fieldExtensionsEstimator` + `simpleEstimator`, `complexity` on fields; see `references/schema-features.md`); disable GraphiQL on public non-production hosts.
|
|
105
|
+
|
|
106
|
+
## Workflow
|
|
107
|
+
|
|
108
|
+
- [ ] 1. Install the driver set for the adapter; register `GraphQLModule` with config-driven options.
|
|
109
|
+
- [ ] 2. Model types/inputs/args; resolvers + services per domain module; validation pipe.
|
|
110
|
+
- [ ] 3. Field resolvers with batching; subscriptions with a shared PubSub provider; auth in context/onConnect.
|
|
111
|
+
- [ ] 4. Complexity limits; error formatting; enhancers where needed.
|
|
112
|
+
- [ ] 5. E2E tests: `request(app.getHttpServer()).post('/graphql').send({ query, variables })` and assert `data`/`errors`. Build, lint, run tests, start the app and open GraphiQL.
|
|
113
|
+
|
|
114
|
+
## Reference files
|
|
115
|
+
|
|
116
|
+
- `references/schema-features.md`: schema first setup and typings generation, scalars (date/number modes, custom), directives, interfaces (`resolveType`, `inheritResolversFromInterfaces`), unions/enums, field middleware, extensions, mapped types, CLI plugin, SDL generation, sharing models, multiple endpoints, Mercurius. Read when using any of these.
|
|
117
|
+
- `references/federation.md`: Apollo and Mercurius federation (subgraph drivers, `@key`/`@ResolveReference`/`@extends`, Federation 2, gateway with `IntrospectAndCompose`, `@inaccessible`, subscriptions caveat). Read when building subgraphs or gateways.
|
|
118
|
+
|
|
119
|
+
Sources: [Quick start](https://docs.nestjs.com/graphql/quick-start) · [Resolvers](https://docs.nestjs.com/graphql/resolvers) · [Mutations](https://docs.nestjs.com/graphql/mutations) · [Subscriptions](https://docs.nestjs.com/graphql/subscriptions) · [Scalars](https://docs.nestjs.com/graphql/scalars) · [Directives](https://docs.nestjs.com/graphql/directives) · [Interfaces](https://docs.nestjs.com/graphql/interfaces) · [Unions and enums](https://docs.nestjs.com/graphql/unions-and-enums) · [Field middleware](https://docs.nestjs.com/graphql/field-middleware) · [Mapped types](https://docs.nestjs.com/graphql/mapped-types) · [Plugins](https://docs.nestjs.com/graphql/plugins) · [Complexity](https://docs.nestjs.com/graphql/complexity) · [Extensions](https://docs.nestjs.com/graphql/extensions) · [CLI plugin](https://docs.nestjs.com/graphql/cli-plugin) · [Generating SDL](https://docs.nestjs.com/graphql/generating-sdl) · [Sharing models](https://docs.nestjs.com/graphql/sharing-models) · [Other features](https://docs.nestjs.com/graphql/other-features) · [Federation](https://docs.nestjs.com/graphql/federation)
|