Skip to content

fix(swagger-module): return reply from async route handlers#3911

Merged
kamilmysliwiec merged 1 commit into
nestjs:masterfrom
tibohaffner:fix/fastify-compress-async-handlers
May 14, 2026
Merged

fix(swagger-module): return reply from async route handlers#3911
kamilmysliwiec merged 1 commit into
nestjs:masterfrom
tibohaffner:fix/fastify-compress-async-handlers

Conversation

@tibohaffner

@tibohaffner tibohaffner commented May 14, 2026

Copy link
Copy Markdown
Contributor

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Other... Please describe:

What is the current behavior?

When @fastify/compress is registered on a NestFastifyApplication, Swagger UI renders blank because GET /docs-json, GET /docs-yaml, and GET /docs/swagger-ui-init.js all return HTTP 200 with an empty body as soon as the client sends an Accept-Encoding: br|gzip header (which every modern browser does by default).

In 11.3.0, the JSON, YAML and swagger-ui-init.js route handlers in lib/swagger-module.ts were converted from sync to async to support an async patchDocumentOnRequest hook, but the bare res.send(...) calls were left without a return. The async function therefore resolves to undefined. When @fastify/compress attaches its onSend hook to compress the response, it serializes undefined and emits an empty compressed body — even though reply.send() was called manually inside the handler.

The HTML page and static assets (served through @fastify/static) are unaffected, which is why the page loads but the UI never initializes.

Plain curl requests without Accept-Encoding look fine, which made the regression invisible to the existing test suite.

Closes #3908

What is the new behavior?

The 4 affected async handlers now return res.send(...), mirroring the pattern already used in the patchDocumentOnRequest branches. The async function resolves to the reply, the onSend hook receives the actual payload, and compression works correctly.

Affected handlers (line numbers from this PR):

  • swagger-module.ts:212swagger-ui-init.js (primary route)
  • swagger-module.ts:244swagger-ui-init.js (trailing-slash route)
  • swagger-module.ts:326/docs-json
  • swagger-module.ts:353/docs-yaml

The sync HTML handler (swaggerUiHtml, line 276) was left unchanged because it is not affected.

Tests

Added a new describe('with @fastify/compress registered', ...) block in e2e/fastify.e2e-spec.ts covering Accept-Encoding: br and gzip against /apidoc-json, /apidoc-yaml, and /apidoc/swagger-ui-init.js. Verified that all 4 new tests fail on master (each asserts body length > 0 — currently 0) and pass with this fix.

Added @fastify/compress as a devDependency (already a peer of @nestjs/platform-fastify consumers).

Full test suites green: 122 e2e passed, 368 unit passed, tsc -p tsconfig.build.json clean.

Does this PR introduce a breaking change?

  • Yes
  • No

The change preserves the existing behavior for callers not using @fastify/compress (since reply.send() already terminates the response) and is identical to what the patchDocumentOnRequest branches do today.

Other information

The fix was validated end-to-end in a real project (NestJS 11.1.20 + @nestjs/platform-fastify 11.1.20 + @fastify/compress 8.3.1 + fastify 5.8.5) by patching node_modules locally and confirming that:

  • curl -s -H 'Accept-Encoding: br' /api/docs-json | wc -c returns ~12 KB of brotli payload (was 0 before)
  • the decompressed body matches the full 198 KB OpenAPI document
  • Swagger UI renders normally in Safari, Chrome and Firefox

The JSON, YAML and swagger-ui-init.js handlers were converted to async
functions in 11.3.0 to support an async patchDocumentOnRequest hook, but
the bare `res.send(...)` calls were left without a `return`. When the
application registers @fastify/compress (or any module attaching an
onSend hook that inspects the resolved payload), the async function
resolves to `undefined` and Fastify emits an empty compressed body even
though `reply.send()` was invoked manually.

Returning the reply from each affected handler restores correct
serialization with brotli/gzip while keeping the original behavior in
all other configurations.

Closes #3908
@kamilmysliwiec kamilmysliwiec merged commit 7ae4f7c into nestjs:master May 14, 2026
1 check passed
@kamilmysliwiec

Copy link
Copy Markdown
Member

lgtm

@tibohaffner tibohaffner deleted the fix/fastify-compress-async-handlers branch May 14, 2026 10:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fastify: /docs-json returns 200 with empty body since 11.3.0 (async handler regression)

2 participants