Skip to content

fix(http): parse body before guards and return parsed body synchronously - #208

Open
nicoske wants to merge 2 commits into
FOSSFORGE:mainfrom
nicoske:fix-http-body-before-guards
Open

fix(http): parse body before guards and return parsed body synchronously#208
nicoske wants to merge 2 commits into
FOSSFORGE:mainfrom
nicoske:fix-http-body-before-guards

Conversation

@nicoske

@nicoske nicoske commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Fixes #206

Moves body parsing ahead of guard execution in the route registry and stores the parsed body on the request right away, not only when pipes run. The body getter returns the parsed value synchronously once it exists; before parsing it still returns a Promise, and await req.body behaves the same in both states. Pipes keep overwriting the stored body as before, and streaming content types (multipart, octet-stream) are still skipped.

Motivation: guards commonly authorize against payload fields, and libraries like Nestia's @TypedBody read request.body without awaiting. Both get a pending Promise on this platform while working fine on Express.

The declared getter type changes from Promise<unknown> to unknown. The only pattern that could notice is calling .then() on it directly instead of awaiting, which nothing in the code base or docs does.

Malformed JSON keeps producing the same response it did before the reorder; the new e2e pins that down.

New e2e suite: test/http/guards-body-access.e2e.spec.ts (guard reads the body synchronously, allow/deny based on payload, synchronous read in the handler, malformed JSON). Lint, unit (975) and e2e (239) suites are green on Node 24.

Summary by CodeRabbit

  • New Features

    • Request bodies are parsed before guards execute, allowing guards to access parsed data synchronously.
    • Request handlers can access parsed request bodies directly without needing to await them.
  • Bug Fixes

    • Improved handling of malformed JSON requests, which are now rejected before reaching the handler.
    • Streaming request types continue to bypass automatic body parsing.
  • Tests

    • Added coverage for guard authorization, synchronous body access, handler behavior, and malformed payload rejection.

Guards ran before body parsing, and request.body kept returning a
Promise even after the pipeline had parsed the body. Any guard that
reads request.body (the usual shape of an auth guard that binds a token
to payload fields) saw a pending Promise instead of the payload, and
the same happened to schema validators that read request.body without
awaiting (e.g. Nestia's @TypedBody). On Express none of this is an
issue because body-parser middleware runs ahead of the guards.

Parse the body before guard execution and store it on the request, and
make the body getter return the parsed value directly once it is
available. Awaiting request.body keeps working in both states, and
pipes overwrite the stored body as before. Streaming content types
(multipart, octet-stream) are untouched.

The trade-off is that a request rejected by a guard now pays the body
parse cost first, which is the same behavior as Express with global
body-parser middleware.
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: dd2d738a-c091-497e-9646-81934a4bd1c7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/http/routing/route-registry.ts`:
- Around line 497-502: Update the body parsing condition in the route
registration flow to parse whenever the request is not streaming, even when
normalizedContentType is absent. Keep the existing normalizedContentType
requirement for the pipe-handling path, and continue storing the resolved body
through req._setTransformedBody so request.body remains synchronous.

In `@test/http/guards-body-access.e2e.spec.ts`:
- Around line 75-89: Update the beforeAll setup around the port constant and
adapter.listen call to use the shared get-port helper with port 0, then assign
baseUrl using the uWS-assigned port returned through that helper (via
uWS.us_socket_local_port). Remove the fixed 13391 value while preserving the
existing application initialization and error handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ec13a28e-42f4-4c64-b6f0-00333cc6ca5a

📥 Commits

Reviewing files that changed from the base of the PR and between 6b80b10 and f813e15.

📒 Files selected for processing (3)
  • src/http/core/request.ts
  • src/http/routing/route-registry.ts
  • test/http/guards-body-access.e2e.spec.ts

Comment thread src/http/routing/route-registry.ts Outdated
Comment on lines +497 to +502
if (normalizedContentType && !isStreamingContentType) {
body = await req.body;
// Store the parsed body so the request.body getter resolves
// synchronously from here on
req._setTransformedBody(body);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Parse non-streaming bodies even without Content-Type.

The normalizedContentType gate leaves valid body-bearing requests without that header unparsed; guards then receive the buffer() Promise instead of the synchronously stored body. Parse whenever the request is not a streaming type; retain the content-type condition for pipes.

Proposed fix
-      if (normalizedContentType && !isStreamingContentType) {
+      if (!isStreamingContentType) {
         body = await req.body;
         // Store the parsed body so the request.body getter resolves
         // synchronously from here on
         req._setTransformedBody(body);
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (normalizedContentType && !isStreamingContentType) {
body = await req.body;
// Store the parsed body so the request.body getter resolves
// synchronously from here on
req._setTransformedBody(body);
}
if (!isStreamingContentType) {
body = await req.body;
// Store the parsed body so the request.body getter resolves
// synchronously from here on
req._setTransformedBody(body);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/http/routing/route-registry.ts` around lines 497 - 502, Update the body
parsing condition in the route registration flow to parse whenever the request
is not streaming, even when normalizedContentType is absent. Keep the existing
normalizedContentType requirement for the pipe-handling path, and continue
storing the resolved body through req._setTransformedBody so request.body
remains synchronous.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicoske I feel like this is a good suggestion by coderabbit to go with.

Comment thread test/http/guards-body-access.e2e.spec.ts
@VikramAditya33
VikramAditya33 self-requested a review July 29, 2026 05:36
@nicoske

nicoske commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

@VikramAditya33 pushed the fix for the no Content-Type case (parse any non streaming request) with a regression test. the other note (fixed test port 13391) i can swap for a random port in the same PR if you want.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Guards run before body parsing, so request.body is a pending Promise inside canActivate

2 participants