Skip to content

feat(standard-server): support parsed body in node adapter#1001

Merged
dinwwwh merged 2 commits intomainfrom
feat/standard-server/support-parsed-body-in-node
Sep 29, 2025
Merged

feat(standard-server): support parsed body in node adapter#1001
dinwwwh merged 2 commits intomainfrom
feat/standard-server/support-parsed-body-in-node

Conversation

@dinwwwh
Copy link
Copy Markdown
Member

@dinwwwh dinwwwh commented Sep 15, 2025

Closes: https://github.com/unnoq/orpc/issues/958

Summary by CodeRabbit

  • Bug Fixes

    • Prefer an already-parsed request body when present to avoid double-parsing and preserve null values; consistent behavior across frameworks (including NestJS).
  • Documentation

    • Clarified Express and Next.js adapter warnings about parser behavior and limitations (e.g., no Bracket Notation; application/json file uploads may be treated as JSON) and middleware placement guidance.
  • Tests

    • Added test to verify preference for already-parsed request bodies.

@vercel
Copy link
Copy Markdown

vercel bot commented Sep 15, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
orpc Ready Ready Preview Comment Sep 15, 2025 8:57am

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Sep 15, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds support for preferring a pre-parsed request body: toStandardBody short-circuits and returns req.body if present; introduces optional body?: unknown on NodeHttpRequest; adds a test verifying the behavior; updates Express and Next.js docs to clarify oRPC will use pre-parsed bodies and list limitations; refines Nest adapter to preserve null bodies.

Changes

Cohort / File(s) Summary
Docs: adapter body parser warnings
apps/content/docs/adapters/express.md, apps/content/docs/adapters/next.md
Reword warnings to state oRPC will use pre-parsed req.body when available; add caveats (no Bracket Notation, application/json file uploads may be parsed as JSON); advise disabling or relocating body-parsing middleware to avoid conflicts.
Node standard-server: prefer parsed body
packages/standard-server-node/src/body.ts, packages/standard-server-node/src/body.test.ts, packages/standard-server-node/src/types.ts
Add early return in toStandardBody to return req.body when defined; add NodeHttpRequest.body?: unknown; add test "prefer parsed body" ensuring parsed body is used instead of stream parsing.
Framework integration: Nest adapter change
packages/nest/src/implement.ts
Change selection logic so the standard request body uses req.body except when it is strictly undefined (preserve null values); previously used req.body ?? fallback, now uses an explicit undefined check.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant A as Adapter (Express/Next)
  participant H as oRPC Handler
  participant B as toStandardBody
  participant P as Parser

  C->>A: HTTP Request
  A->>A: (Optional) Body-parsing middleware sets `req.body`
  A->>H: Call handler with `(req,res)`

  H->>B: toStandardBody(req)
  alt req.body defined
    B-->>H: Return `req.body` (no stream parsing)
  else req.body undefined
    B->>P: Parse incoming stream (JSON/multipart/urlencoded/text...)
    P-->>B: Standardized body
    B-->>H: Parsed body
  end

  H-->>A: Continue handling with body
  A-->>C: Response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • unnoq/orpc#445: Modifies server-side body handling to prefer/examine existing parsed bodies and adjust undefined/null semantics — closely related.
  • unnoq/orpc#533: Alters toStandardBody control flow and empty-body handling in the same standard-server package.
  • unnoq/orpc#650: Adjusts framework adapters to avoid consuming the body; related to interactions with external body parsers.

Suggested labels

size:M

Poem

I nibble bytes where bodies flow 🐇
If parsed already, I skip the tow
No stream to chew, I hop ahead —
Tests confirm the path we tread.
Docs now whisper: place parsers wise,
So handlers find the bodies' prize.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues Check ✅ Passed The PR implements the objective from linked issue #958 by preferring a parsed req.body in toStandardBody, adding an optional body property to NodeHttpRequest, adjusting Nest handling to preserve null, adding a test that covers the parsed-body case, and updating Express/Next docs; these code and doc changes directly address Firebase/Google Cloud Functions environments where the body may be pre-parsed. The added test and type change provide targeted coverage and the docs clarify middleware ordering and limitations. Overall, the coding objectives described in issue #958 are met.
Out of Scope Changes Check ✅ Passed All modified files and changes are focused on supporting pre-parsed request bodies (docs updates, toStandardBody early-return, type addition, test, and the Nest adapter tweak) and no unrelated features or files were altered. The only notable side-effect is that when req.body is present the existing parsing span is skipped, which is a related behavioral change and should be documented but is not an out-of-scope change. I therefore find no out-of-scope code in this PR.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly summarizes the main change by indicating that the standard-server Node adapter now supports a parsed body, matching the feature implemented across code, tests, and documentation, and it uses concise conventional commit styling.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/standard-server/support-parsed-body-in-node

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 and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @unnoq, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the oRPC Node.js adapter by allowing it to leverage pre-parsed request bodies from common frameworks, improving compatibility and integration. It introduces a mechanism to check for an existing req.body before performing its own parsing, and updates relevant documentation to guide users on best practices and potential considerations when using this feature alongside external body parsing middleware.

Highlights

  • Node.js Adapter Body Parsing: The Node.js adapter for oRPC now supports using pre-parsed request bodies, such as those provided by middleware in frameworks like Express.js or Next.js.
  • Documentation Updates: Documentation for Express.js and Next.js adapters has been updated to clarify how oRPC interacts with external body parsers, including warnings about potential limitations with features like Bracket Notation and file uploads.
  • Type Definition Enhancement: The NodeHttpRequest type has been extended to include an optional body property, reflecting the ability to receive pre-parsed request bodies.
  • New Test Case: A new test has been added to ensure that the toStandardBody function correctly prioritizes and utilizes an already parsed req.body.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@dosubot dosubot bot added the size:S This PR changes 10-29 lines, ignoring generated files. label Sep 15, 2025
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds support for using pre-parsed request bodies in the Node.js adapter, which is useful for integrations with frameworks like Express. The changes include updating the core body parsing logic, associated types, and tests. I've reviewed the changes and found a potential issue with how raw Buffer bodies are handled, which could lead to incorrect behavior for binary uploads. I've provided a suggestion to correctly wrap Buffer bodies into File objects. Additionally, I've suggested some minor improvements to the documentation for clarity. Overall, this is a valuable feature addition.


::: warning
oRPC uses its own request parser. To avoid conflicts, register any body-parsing middleware **after** your oRPC middleware or only on routes that don't use oRPC.
Express's [body-parser](https://expressjs.com/en/resources/middleware/body-parser.html) handles common request body types, and oRPC will use the parsed body if available. However, it doesn't support features like [Bracket Notation](/docs/openapi/bracket-notation), and in case you upload a file with `application/json`, it may be parsed as plain JSON instead of a `File`. To avoid these issues, register any body-parsing middleware **after** your oRPC middleware or only on routes that don't use oRPC.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This paragraph is a bit long and dense. For better readability, consider breaking down the information into a list of limitations.

Suggested change
Express's [body-parser](https://expressjs.com/en/resources/middleware/body-parser.html) handles common request body types, and oRPC will use the parsed body if available. However, it doesn't support features like [Bracket Notation](/docs/openapi/bracket-notation), and in case you upload a file with `application/json`, it may be parsed as plain JSON instead of a `File`. To avoid these issues, register any body-parsing middleware **after** your oRPC middleware or only on routes that don't use oRPC.
Express's [body-parser](https://expressjs.com/en/resources/middleware/body-parser.html) handles common request body types, and oRPC will use the parsed body if available. However, this approach has some limitations:
- It doesn't support features like [Bracket Notation](/docs/openapi/bracket-notation).
- A file uploaded with `application/json` may be parsed as a plain JSON object instead of a `File`.
To avoid these issues, register any body-parsing middleware **after** your oRPC middleware or only on routes that don't use oRPC.

::: warning

Next.js default [body parser](https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config) blocks oRPC raw‑request handling. Ensure `bodyParser` is disabled in your API route:
Next.js [body parser](https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config) may handle common request body types, and oRPC will use the parsed body if available. However, it doesn't support features like [Bracket Notation](/docs/openapi/bracket-notation), and in case you upload a file with `application/json`, it may be parsed as plain JSON instead of a `File`. To avoid these issues, disable the body parser:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This paragraph is a bit long and dense. For better readability, consider breaking down the information into a list of limitations.

Suggested change
Next.js [body parser](https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config) may handle common request body types, and oRPC will use the parsed body if available. However, it doesn't support features like [Bracket Notation](/docs/openapi/bracket-notation), and in case you upload a file with `application/json`, it may be parsed as plain JSON instead of a `File`. To avoid these issues, disable the body parser:
Next.js [body parser](https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config) may handle common request body types, and oRPC will use the parsed body if available. However, this approach has some limitations:
- It doesn't support features like [Bracket Notation](/docs/openapi/bracket-notation).
- A file uploaded with `application/json` may be parsed as a plain JSON object instead of a `File`.
To avoid these issues, disable the body parser:

@codecov
Copy link
Copy Markdown

codecov bot commented Sep 15, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Sep 15, 2025

More templates

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@1001

@orpc/client

npm i https://pkg.pr.new/@orpc/client@1001

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@1001

@orpc/experimental-durable-event-iterator

npm i https://pkg.pr.new/@orpc/experimental-durable-event-iterator@1001

@orpc/hey-api

npm i https://pkg.pr.new/@orpc/hey-api@1001

@orpc/interop

npm i https://pkg.pr.new/@orpc/interop@1001

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@1001

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@1001

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@1001

@orpc/openapi-client

npm i https://pkg.pr.new/@orpc/openapi-client@1001

@orpc/otel

npm i https://pkg.pr.new/@orpc/otel@1001

@orpc/react

npm i https://pkg.pr.new/@orpc/react@1001

@orpc/react-query

npm i https://pkg.pr.new/@orpc/react-query@1001

@orpc/experimental-react-swr

npm i https://pkg.pr.new/@orpc/experimental-react-swr@1001

@orpc/server

npm i https://pkg.pr.new/@orpc/server@1001

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@1001

@orpc/solid-query

npm i https://pkg.pr.new/@orpc/solid-query@1001

@orpc/standard-server

npm i https://pkg.pr.new/@orpc/standard-server@1001

@orpc/standard-server-aws-lambda

npm i https://pkg.pr.new/@orpc/standard-server-aws-lambda@1001

@orpc/standard-server-fetch

npm i https://pkg.pr.new/@orpc/standard-server-fetch@1001

@orpc/standard-server-node

npm i https://pkg.pr.new/@orpc/standard-server-node@1001

@orpc/standard-server-peer

npm i https://pkg.pr.new/@orpc/standard-server-peer@1001

@orpc/svelte-query

npm i https://pkg.pr.new/@orpc/svelte-query@1001

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@1001

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@1001

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@1001

@orpc/vue-colada

npm i https://pkg.pr.new/@orpc/vue-colada@1001

@orpc/vue-query

npm i https://pkg.pr.new/@orpc/vue-query@1001

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@1001

commit: bb9b636

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
apps/content/docs/adapters/next.md (1)

76-87: Clarify Pages Router scope and tighten wording.

This warning applies to the Pages Router only (not App Router). Minor phrasing tweak improves clarity.

-Next.js [body parser](https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config) may handle common request body types, and oRPC will use the parsed body if available. However, it doesn't support features like [Bracket Notation](/docs/openapi/bracket-notation), and in case you upload a file with `application/json`, it may be parsed as plain JSON instead of a `File`. To avoid these issues, disable the body parser:
+In the Pages Router, Next.js's [body parser](https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config) may pre‑parse request bodies, and oRPC will prefer `req.body` when present. However, it doesn't support features like [Bracket Notation](/docs/openapi/bracket-notation), and if you upload a file with `application/json`, it may be parsed as plain JSON instead of a `File`. To avoid these issues, disable the body parser for routes that use oRPC:
apps/content/docs/adapters/express.md (1)

11-11: Tiny wording nit.

Swap “in case you” with “if you” for concision.

-..., and in case you upload a file with `application/json`, it may be parsed as plain JSON instead of a `File`.
+..., and if you upload a file with `application/json`, it may be parsed as plain JSON instead of a `File`.
packages/standard-server-node/src/body.test.ts (1)

190-202: Avoid ts-expect-error by casting to NodeHttpRequest.

Keeps the test clean and future‑proof to type changes.

-    await request(async (req: IncomingMessage, res: ServerResponse) => {
-      // @ts-expect-error fake body is parsed
-      req.body = { value: 123 }
+    await request(async (req: IncomingMessage, res: ServerResponse) => {
+      ;(req as unknown as import('./types').NodeHttpRequest).body = { value: 123 }
       standardBody = await toStandardBody(req)
       res.end()
     }).post('/').send()

If you adopt the method-guard suggested in toStandardBody, consider adding a companion test to assert that GET/HEAD ignore req.body.

packages/standard-server-node/src/body.ts (1)

13-15: Guard early-exit by method and keep tracing span.

As written, any upstream req.body on GET/HEAD would change prior semantics (previously returned undefined) and the early return skips the parse_standard_body span. Guarding by method preserves behavior and wrapping inside runWithSpan retains observability.

-export function toStandardBody(req: NodeHttpRequest, options: ToStandardBodyOptions = {}): Promise<StandardBody> {
-  if (req.body !== undefined) {
-    return Promise.resolve(req.body)
-  }
-
-  return runWithSpan({ name: 'parse_standard_body', signal: options.signal }, async () => {
+export function toStandardBody(req: NodeHttpRequest, options: ToStandardBodyOptions = {}): Promise<StandardBody> {
+  return runWithSpan({ name: 'parse_standard_body', signal: options.signal }, async () => {
+    const method = req.method?.toUpperCase()
+    // Prefer pre-parsed bodies only for methods that conventionally carry a payload.
+    if (req.body !== undefined && method !== 'GET' && method !== 'HEAD') {
+      return req.body
+    }
     const contentDisposition = req.headers['content-disposition']
     const contentType = req.headers['content-type']

Optional: if you want to be extra defensive, also ignore req.body when content-type: text/event-stream.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd705fc and 2c8a448.

📒 Files selected for processing (5)
  • apps/content/docs/adapters/express.md (1 hunks)
  • apps/content/docs/adapters/next.md (1 hunks)
  • packages/standard-server-node/src/body.test.ts (1 hunks)
  • packages/standard-server-node/src/body.ts (1 hunks)
  • packages/standard-server-node/src/types.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/standard-server-node/src/body.test.ts (2)
packages/standard-server/src/types.ts (1)
  • StandardBody (5-11)
packages/standard-server-node/src/body.ts (1)
  • toStandardBody (12-51)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: publish-commit
  • GitHub Check: test
  • GitHub Check: lint
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
packages/standard-server-node/src/types.ts (1)

10-14: LGTM: typed hook for pre-parsed bodies.

body?: unknown on NodeHttpRequest makes the early-exit explicit without over-constraining types. No action needed.

@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:S This PR changes 10-29 lines, ignoring generated files. labels Sep 15, 2025
@dinwwwh dinwwwh force-pushed the feat/standard-server/support-parsed-body-in-node branch from fabd202 to 2c8a448 Compare September 15, 2025 08:50
@dosubot dosubot bot added size:S This PR changes 10-29 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Sep 15, 2025
@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:S This PR changes 10-29 lines, ignoring generated files. labels Sep 15, 2025
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/nest/src/implement.ts (1)

129-131: Tiny readability tweak: async instead of Promise.resolve and clearer comment

Functionally equivalent, but a bit clearer and fixes the comment grammar.

Apply this diff:

-        // Prefer NestJS parsed body (in nodejs body only allow parse once)
-        standardRequest.body = () => Promise.resolve(
-          req.body === undefined ? fallbackStandardBody() : req.body,
-        )
+        // Prefer NestJS‑parsed body; in Node.js the incoming stream can be read only once.
+        standardRequest.body = async () =>
+          req.body === undefined ? await fallbackStandardBody() : req.body
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fabd202 and bb9b636.

📒 Files selected for processing (1)
  • packages/nest/src/implement.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: publish-commit
  • GitHub Check: lint
  • GitHub Check: test
🔇 Additional comments (1)
packages/nest/src/implement.ts (1)

129-131: Prefer parsed body when defined — LGTM

The strict undefined check correctly preserves null bodies and falls back only when needed. This aligns with environments like Firebase/GCF where the body is pre‑parsed. No functional concerns.

Please sanity‑check with Nest on both Express and Fastify for:

  • application/json (parsed object)
  • text/plain (string)
  • no body (undefined vs null behavior)

If you want, I can add a tiny e2e test for Nest to cover these cases.

@dinwwwh dinwwwh added the lgtm This PR has been approved by a maintainer label Sep 27, 2025
@dinwwwh dinwwwh merged commit 4748dc5 into main Sep 29, 2025
11 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Oct 23, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Firebase / Google Cloud Functions Support

1 participant