@lenne.tech/nest-server 11.42.5 → 11.42.6

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.
@@ -569,7 +569,7 @@ Both use body-parser's default limit of **100 kB** unless `bodyParser` says othe
569
569
  bodyParser: { json: { limit: '2mb' } },
570
570
  ```
571
571
 
572
- `CoreBodyParserInitializer` applies it during module init by replacing the handle of the global parser layers NestJS created — in place, because NestJS registers them before any init hook runs and a parser appended later would sit behind the routes. Running during module init is what makes the limit hold under `Test.createTestingModule()`, which never runs `main.ts`. A path-scoped parser (`server.use('/upload', json({ limit }))` in `main.ts`) runs before the global one and is left untouched — that remains the way to give one path a different limit.
572
+ `CoreBodyParserInitializer` applies it during module init by replacing the handle of the global parser layers NestJS created — in place, because NestJS registers them before any init hook runs and a parser appended later would sit behind the routes. Running during module init is what makes the limit hold under `Test.createTestingModule()`, which never runs `main.ts`. A path-scoped parser (`server.use('/upload', json({ limit }))` in `main.ts`) runs before the global one and is left untouched — that remains the way to give one path a different limit. Keep `server.useBodyParser('json')` (or `'urlencoded'`) in `main.ts` next to it: NestJS registers its global parser only when no layer with the same function name exists, so a path-scoped one registered before `app.init()` suppresses it, and every other path receives an empty body. Since 11.42.6 `CoreBodyParserInitializer` warns at boot in that case.
573
573
 
574
574
  The parser runs **before authentication and routing**. Over the limit, the whole request is refused:
575
575
 
@@ -63,6 +63,14 @@ server.useBodyParser('json', { limit: '2mb' });
63
63
  bodyParser: { json: { limit: '2mb' } },
64
64
  ```
65
65
 
66
+ **Exception: keep `useBodyParser()` when `main.ts` also registers a path-scoped parser** such as
67
+ `server.use('/upload', json({ limit: '20mb' }))`. NestJS registers its own global parser only if no
68
+ layer with the same function name exists yet, and a path-scoped `json()` registered before
69
+ `app.init()` counts as one. Removing `useBodyParser()` then leaves the app without a global JSON
70
+ parser: every request outside that path reaches its handler with an empty body, and a sign-in fails
71
+ with a validation error. Keep `server.useBodyParser('json')` (the configured limit is applied to it)
72
+ and set the limit in the config as above. Since 11.42.6 the server warns at boot when it finds this.
73
+
66
74
  **Starter-based projects:** `config.env.ts` builds `local` / `e2e` / `ci` from `localConfig()` and the
67
75
  deployed environments from `deployedConfig()`. Put the limit where BOTH see it — the starter ships a
68
76
  shared `PROJECT_BODY_PARSER` constant for this since its 11.42.4 update — or set
@@ -75,7 +83,8 @@ The config is applied during module init, so it holds in every test that builds
75
83
  global parser your `main.ts` registered; keeping both is harmless, but the config wins.
76
84
 
77
85
  A **path-scoped** parser is left alone: `server.use('/upload', json({ limit: '20mb' }))` in `main.ts`
78
- still gives that one path its own limit. The parser runs before routing, so a per-route limit can only
86
+ still gives that one path its own limit, as long as the global parser stays registered next to it
87
+ (see the exception above). The parser runs before routing, so a per-route limit can only
79
88
  key on the path — and all GraphQL operations share one.
80
89
 
81
90
  **`rawBody: true`:** `req.rawBody` is kept for apps created with `NestFactory.create(…, { rawBody:
@@ -0,0 +1,97 @@
1
+ # Migration Guide: 11.42.5 → 11.42.6
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ | -------------------- | ------------------------------------------------------------------------------------------------------------ |
7
+ | **Correction** | The 11.42.3 → 11.42.4 guide told every project to drop `useBodyParser()` from `main.ts`. With a path-scoped parser in `main.ts` that removes JSON parsing everywhere else (§1) |
8
+ | **Improvement** | The server warns at boot when NestJS skipped its global body parser because of such a path-scoped one (§1) |
9
+ | **Tooling** | `check:overrides` no longer reports a failed audit as clean, and its probe follows `pnpm_config_registry` (§2) |
10
+ | **Breaking Changes** | None |
11
+ | **Migration Effort** | None for most projects. Projects that removed `useBodyParser()` while registering a path-scoped parser in `main.ts` must add it back (§1) |
12
+
13
+ ## Quick Migration
14
+
15
+ ```bash
16
+ pnpm update @lenne.tech/nest-server
17
+ ```
18
+
19
+ Then check `main.ts` as described in §1. Vendor-mode projects pick up the core update and need the
20
+ same check.
21
+
22
+ ## 1. Keep `useBodyParser()` next to a path-scoped parser
23
+
24
+ §1 of the [11.42.3 → 11.42.4 guide](./11.42.3-to-11.42.4.md) recommended moving the body limit from
25
+ `main.ts` into `bodyParser.json.limit` and deleting the `useBodyParser()` call. That is right, with one
26
+ exception the guide did not name.
27
+
28
+ NestJS registers its own global JSON and URL-encoded parsers inside `app.init()`, but only when no
29
+ layer with the same function name exists yet. It compares the NAME and nothing else. A path-scoped
30
+ parser registered in `main.ts` before `listen()` carries that name:
31
+
32
+ ```typescript
33
+ // main.ts
34
+ server.use('/diagnostics', json({ limit: '256kb' })); // a layer named `jsonParser`
35
+ ```
36
+
37
+ With `useBodyParser()` removed, NestJS treats the JSON parser as already applied and registers none.
38
+ Every request outside `/diagnostics` then reaches its handler with an empty body. A sign-in fails with
39
+ a validation error such as "Missing input", and before this release nothing was logged.
40
+
41
+ **Check `main.ts`.** If it registers `json(...)` or `urlencoded(...)` on a path, keep or restore the
42
+ global parser next to it:
43
+
44
+ ```typescript
45
+ // main.ts
46
+ server.use('/diagnostics', json({ limit: '256kb' }));
47
+ server.useBodyParser('json'); // the configured bodyParser.json.limit is applied to it
48
+ ```
49
+
50
+ Keep the limit itself in the config (`bodyParser.json.limit`), where it also holds under
51
+ `Test.createTestingModule()`. The e2e suite cannot catch the missing parser: the testing module never
52
+ runs `main.ts`, so it never registers the path-scoped parser either.
53
+
54
+ Since 11.42.6 the server logs a warning at boot in exactly this situation:
55
+
56
+ ```
57
+ No global json body parser is registered: NestJS skips its own when a layer named `jsonParser`
58
+ already exists, and a path-scoped `json()` parser registered before `app.init()` counts as one. …
59
+ ```
60
+
61
+ The framework only reports it. It does not register the missing parser itself: NestJS would have put
62
+ it after everything `main.ts` registered, and that position is no longer known during module init. A
63
+ raw-body webhook or a proxy registered after the path-scoped parser must keep receiving the unread
64
+ request stream.
65
+
66
+ ## 2. `check:overrides`: a failed audit is no longer reported as clean
67
+
68
+ Two fixes in `scripts/check-overrides.mjs` (and the shared registry block in `scripts/check.mjs`):
69
+
70
+ - When pnpm 11 cannot reach the registry, `pnpm audit --json` prints
71
+ `{"error": {"message": "fetch failed"}}` instead of a report. The guard read that as "0 advisories"
72
+ and printed `ok — N override(s) checked … none is failing`. It now prints
73
+ `could not obtain an audit report` (exit 0) on a live run, and fails (exit 1) when the failed report
74
+ was passed in with `--audit-file`, which is the CI path. The suppression checks still run in that
75
+ case, because they ask the GitHub Advisory API rather than the audit: a suppressed advisory that
76
+ gained a fix still fails with `FIX AVAILABLE`.
77
+ - The clean-tree probe reads `pnpm_config_registry`, the variable pnpm 11 audits against. It used to
78
+ read `npm_config_registry`, which pnpm 11 ignores, and could then ask a registry the audit never used.
79
+
80
+ `scripts/` is not part of the npm package. Projects created from nest-server-starter or lt-monorepo
81
+ receive these fixes with their template update.
82
+
83
+ ## Troubleshooting
84
+
85
+ | Symptom | Cause | Fix |
86
+ | ------- | ----- | --- |
87
+ | Boot warning "No global json body parser is registered" | `main.ts` registers a path-scoped `json()` and no global parser | Add `server.useBodyParser('json')` to `main.ts` (§1) |
88
+ | Requests arrive with an empty body outside one path; sign-in reports "Missing input" | Same cause, on a version before 11.42.6 | Same fix (§1) |
89
+ | `check:overrides` prints `could not obtain an audit report (fetch failed)` | The npm registry was not reachable during the audit | Re-run with network access, or pass a captured report with `--audit-file` |
90
+ | `check:overrides` fails with `records a failed audit, not a result` | The report passed with `--audit-file` is a pnpm error, not an audit | Capture the report again once the registry answers |
91
+
92
+ ## Module Documentation
93
+
94
+ - [REQUEST-LIFECYCLE.md — 0d. Body parsers](../docs/REQUEST-LIFECYCLE.md)
95
+ - [11.42.3 → 11.42.4 guide §1](./11.42.3-to-11.42.4.md)
96
+ - `.claude/rules/configurable-features.md` — Body Parser Limits row
97
+ - `.claude/rules/package-management.md` — the `check:overrides` guard
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.42.5",
3
+ "version": "11.42.6",
4
4
  "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
5
5
  "keywords": [
6
6
  "node",
@@ -2335,6 +2335,11 @@ export interface IServerOptions {
2335
2335
  * (`server.use('/upload', json({ limit }))`) is left alone, and stays the way to give ONE path a
2336
2336
  * different limit, since the parser runs before routing and GraphQL shares a single path anyway.
2337
2337
  *
2338
+ * Keep `server.useBodyParser('json')` in `main.ts` next to such a path-scoped parser. NestJS
2339
+ * registers its own global parser only if no layer with the same function name exists yet, so a
2340
+ * path-scoped `json()` registered before `app.init()` makes it skip the global one, and requests
2341
+ * outside that path arrive with an empty body. The initializer warns at boot when it finds that.
2342
+ *
2338
2343
  * One caveat: under the testing module the options passed to `createNestApplication()` are not
2339
2344
  * visible to providers, so a test app created with `{ rawBody: true }` gets no `req.rawBody` from
2340
2345
  * a CONFIGURED parser. Applications created with `NestFactory.create()` are not affected.
@@ -90,11 +90,8 @@ export class CoreBodyParserInitializer implements OnModuleInit {
90
90
  const limit = this.resolveLimit(type, config?.[type]?.limit);
91
91
  const layers = stack.filter((layer) => this.isGlobalParserLayer(layer, type));
92
92
 
93
- if (limit !== undefined && !layers.length) {
94
- this.logger.warn(
95
- `bodyParser.${type}.limit is configured, but no global ${type} body parser is registered ` +
96
- '(was the app created with `bodyParser: false`?) — the limit has no effect.',
97
- );
93
+ if (!layers.length) {
94
+ this.warnMissingGlobalParser(type, stack, limit);
98
95
  continue;
99
96
  }
100
97
 
@@ -147,6 +144,18 @@ export class CoreBodyParserInitializer implements OnModuleInit {
147
144
  return layer?.slash === true && typeof layer.handle === 'function' && layer.handle.name === PARSER_NAMES[type];
148
145
  }
149
146
 
147
+ /**
148
+ * Whether the application was created without `bodyParser: false`.
149
+ *
150
+ * Read from the same container options as `rawBody`. Under `Test.createTestingModule()` those
151
+ * options never reach the container, so the answer there is always "enabled".
152
+ */
153
+ protected isBodyParserEnabled(): boolean {
154
+ const container = (this.moduleRef as unknown as { container?: { contextOptions?: { bodyParser?: boolean } } })
155
+ ?.container;
156
+ return container?.contextOptions?.bodyParser !== false;
157
+ }
158
+
150
159
  /**
151
160
  * Whether the application was created with `rawBody: true`.
152
161
  *
@@ -210,6 +219,39 @@ export class CoreBodyParserInitializer implements OnModuleInit {
210
219
  );
211
220
  }
212
221
 
222
+ /**
223
+ * Explain why there is no global parser of this type, when there should be one.
224
+ *
225
+ * NestJS registers its own parsers inside `app.init()` only if no layer with the parser's function
226
+ * name exists yet (`ExpressAdapter.isMiddlewareApplied()` compares the NAME and nothing else). A
227
+ * path-scoped `server.use('/upload', json({ limit }))` in `main.ts` therefore counts as "applied",
228
+ * NestJS skips its global parser, and every request outside that path reaches its handler with an
229
+ * empty body — a sign-in fails with a validation error, and nothing says why. Restoring the parser
230
+ * from here is not safe: NestJS would have placed it after everything `main.ts` registered, and that
231
+ * position is no longer knowable during module init (a raw-body webhook or a proxy registered after
232
+ * the path-scoped parser must keep seeing the unread stream). So this reports, with the remedy.
233
+ */
234
+ protected warnMissingGlobalParser(type: CoreBodyParserType, stack: ExpressLayer[], limit: number | undefined): void {
235
+ const name = PARSER_NAMES[type];
236
+ const shadowed = stack.some((layer) => layer?.slash !== true && layer?.handle?.name === name);
237
+ if (shadowed && this.isBodyParserEnabled()) {
238
+ this.logger.warn(
239
+ `No global ${type} body parser is registered: NestJS skips its own when a layer named \`${name}\` ` +
240
+ `already exists, and a path-scoped \`${type}()\` parser registered before \`app.init()\` counts as one. ` +
241
+ `Every request outside that path reaches its handler with an EMPTY body. Register the global parser ` +
242
+ `in main.ts as well — \`server.useBodyParser('${type}')\` — a configured \`bodyParser.${type}.limit\` ` +
243
+ 'is applied to it.',
244
+ );
245
+ return;
246
+ }
247
+ if (limit !== undefined) {
248
+ this.logger.warn(
249
+ `bodyParser.${type}.limit is configured, but no global ${type} body parser is registered ` +
250
+ '(was the app created with `bodyParser: false`?) — the limit has no effect.',
251
+ );
252
+ }
253
+ }
254
+
213
255
  /**
214
256
  * Wrap a parser so its 413 is translated, keeping the name NestJS identifies the layer by.
215
257
  *