Skip to content

Conversation

@anatolzak
Copy link
Contributor

@anatolzak anatolzak commented Aug 25, 2025

Closes #6913

🎯 Changes

Omit the content-type request header when sending a GET request. This prevents the GET request from triggering pre-flight requests that would potentially not occur otherwise.

✅ Checklist

  • I have followed the steps listed in the Contributing guide.
  • If necessary, I have added documentation related to the changes made.
  • I have added or updated the tests related to the changes made.

Summary by CodeRabbit

  • Bug Fixes

    • Ensures HTTP method is determined consistently.
    • GET requests no longer send a Content-Type header.
    • POST/mutation requests include Content-Type: application/json.
    • Queries using an allowed method override set to POST send POST with the correct Content-Type header.
  • Tests

    • Added end-to-end tests validating request method and Content-Type header behavior for queries, mutations, and method-override scenarios.

@anatolzak anatolzak requested a review from a team as a code owner August 25, 2025 20:09
@vercel
Copy link

vercel bot commented Aug 25, 2025

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

Project Deployment Preview Comments Updated (UTC)
www Ready Ready Preview Comment Sep 5, 2025 2:45pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 25, 2025

Walkthrough

Implements conditional omission of the Content-Type header for GET requests in HTTP link utilities and adds end-to-end tests verifying header behavior for GET, POST, and methodOverride scenarios.

Changes

Cohort / File(s) Summary
HTTP utils: header/method logic
packages/client/src/links/internals/httpUtils.ts
Compute HTTP method via methodOverride or type mapping; apply Content-Type header only when contentTypeHeader is defined and computed method is not GET; use computed method in fetch config.
Tests: content-type behavior
packages/tests/server/contentTypeHeaders.test.ts
New E2E tests confirming: no Content-Type for GET, Content-Type for POST, and Content-Type present when query uses methodOverride: 'POST'.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant HU as httpUtils
  participant F as fetch
  participant S as Server

  C->>HU: makeRequest(opts)
  Note over HU: method = opts.methodOverride ?? METHOD[opts.type]
  alt method == GET
    Note over HU: omit Content-Type header
  else
    Note over HU: set Content-Type: application/json (if provided)
  end
  HU->>F: fetch(url, { method, headers, body })
  F->>S: HTTP request
  S-->>F: Response
  F-->>C: Result
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Assessment against linked issues

Objective Addressed Explanation
GET requests should not include the Content-Type request header (#6913)

Poem

I hop in code, a careful sprite,
GETs go lean — no header in sight.
POST puts on its JSON hat,
Overrides dance — we handle that.
A carrot patch, courteous and bright. 🥕🐇


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7a0ff63 and e11b947.

📒 Files selected for processing (1)
  • packages/tests/server/contentTypeHeaders.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/tests/server/contentTypeHeaders.test.ts
⏰ 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). (20)
  • GitHub Check: E2E-tests (next-prisma-websockets-starter)
  • GitHub Check: E2E-tests (standalone-server)
  • GitHub Check: E2E-tests (next-prisma-todomvc)
  • GitHub Check: E2E-tests (soa)
  • GitHub Check: E2E-tests (fastify-server)
  • GitHub Check: E2E-tests (express-server)
  • GitHub Check: E2E-tests (minimal-react)
  • GitHub Check: E2E-tests (express-minimal)
  • GitHub Check: E2E-tests (.test/ssg)
  • GitHub Check: E2E-tests (.test/internal-types-export)
  • GitHub Check: e2e-legacy-node (next-prisma-starter, 18.x)
  • GitHub Check: e2e-legacy-node (next-prisma-todomvc, 20.x)
  • GitHub Check: e2e-legacy-node (next-prisma-todomvc, 18.x)
  • GitHub Check: Release using pkg.pr.new
  • GitHub Check: e2e-legacy-node (next-prisma-starter, 20.x)
  • GitHub Check: e2e-legacy-node (next-prisma-websockets-starter, 18.x)
  • GitHub Check: e2e-legacy-node (next-prisma-websockets-starter, 20.x)
  • GitHub Check: E2E-tests (Bun) (bun, ubuntu-latest)
  • GitHub Check: Lint and auto-fix
  • GitHub Check: test
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@vercel
Copy link

vercel bot commented Aug 25, 2025

@anatolzak is attempting to deploy a commit to the trpc Team on Vercel.

A member of the Team first needs to authorize it.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Aug 25, 2025

Open in StackBlitz

@trpc/client

npm i https://pkg.pr.new/trpc/trpc/@trpc/client@6914

@trpc/next

npm i https://pkg.pr.new/trpc/trpc/@trpc/next@6914

@trpc/react-query

npm i https://pkg.pr.new/trpc/trpc/@trpc/react-query@6914

@trpc/server

npm i https://pkg.pr.new/trpc/trpc/@trpc/server@6914

@trpc/tanstack-react-query

npm i https://pkg.pr.new/trpc/trpc/@trpc/tanstack-react-query@6914

@trpc/upgrade

npm i https://pkg.pr.new/trpc/trpc/@trpc/upgrade@6914

commit: e11b947

Copy link
Contributor

@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)
packages/client/src/links/internals/httpUtils.ts (1)

209-216: Optional: Skip empty-body Content-Type and normalize headers in fetchHTTPResponse

Verified that the only explicit “content-type” header is set in fetchHTTPResponse (packages/client/src/links/internals/httpUtils.ts lines 209–216), and that all JSON requests (via jsonHttpRequester) and batch streams funnel through this function. Because getBody returns undefined for queries (and any mutation with input === undefined), we can safely skip setting content-type whenever the body is empty to avoid needless CORS preflights. Also, user-provided headers from resolvedHeaders are merged as-is, so normalizing keys to lowercase prevents duplicates like Content-Type vs content-type.

Files to update:

  • packages/client/src/links/internals/httpUtils.ts

Proposed diff at ~lines 205–215:

@@ export async function fetchHTTPResponse(opts: HTTPRequestOptions) {
-  const method = opts.methodOverride ?? METHOD[opts.type];
+  const method = opts.methodOverride ?? METHOD[opts.type];
+  const hasBody = body != null;
@@
-  const headers = {
-    ...(opts.contentTypeHeader && method !== 'GET'
-      ? { 'content-type': opts.contentTypeHeader }
-      : {}),
-    ...(opts.trpcAcceptHeader
-      ? { 'trpc-accept': opts.trpcAcceptHeader }
-      : undefined),
-    ...resolvedHeaders,
-  };
+  const headers = {
+    // Only set Content-Type when not GET and there’s a body
+    ...(opts.contentTypeHeader && method !== 'GET' && hasBody
+      ? { 'content-type': opts.contentTypeHeader }
+      : {}),
+    // Always include tRPC accept header if provided
+    ...(opts.trpcAcceptHeader
+      ? { 'trpc-accept': opts.trpcAcceptHeader }
+      : {}),
+    // Normalize user headers to lowercase keys to avoid duplicates
+    ...Object.fromEntries(
+      Object.entries(resolvedHeaders).map(([k, v]) => [k.toLowerCase(), v]),
+    ),
+  };

You may also consider guarding trpc-accept the same way (e.g. && method !== 'GET') if you want to avoid preflights when streaming GET batches.

packages/tests/server/contentTypeHeaders.test.ts (3)

15-41: Strengthen assertions: also assert no body and that input is in the URL for GET.

Both checks help catch regressions (e.g., accidentally sending a body or dropping URL-encoded input).

   const [url, options] = mockFetch.mock.calls[0]!;
 
   expect(options.method).toBe('GET');
 
   expect(options.headers).not.toHaveProperty('content-type');
+  // No payload with GET
+  expect(options.body).toBeUndefined();
+  // Input should be URL-encoded on GET
+  expect(String(url)).toContain('input=');
 
   await close();

43-69: Add body assertion to ensure POST carries JSON payload.

Verifies we’re actually sending a JSON string when posting.

   const [url, options] = mockFetch.mock.calls[0]!;
 
   expect(options.method).toBe('POST');
 
   expect(options.headers).toHaveProperty('content-type', 'application/json');
+  expect(typeof options.body).toBe('string');
+  expect(options.body).toBe(JSON.stringify('test'));

71-105: Method override test: also assert no URL-encoded input and body presence.

Confirms the query was truly POSTed (payload in body, not query string).

   const [url, options] = mockFetch.mock.calls[0]!;
 
   expect(options.method).toBe('POST');
 
   expect(options.headers).toHaveProperty('content-type', 'application/json');
+  expect(String(url)).not.toContain('input=');
+  expect(options.body).toBe(JSON.stringify('test'));
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e6fc309 and 7a0ff63.

📒 Files selected for processing (2)
  • packages/client/src/links/internals/httpUtils.ts (3 hunks)
  • packages/tests/server/contentTypeHeaders.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/tests/server/contentTypeHeaders.test.ts (2)
packages/tests/server/___testHelpers.ts (1)
  • routerToServerAndClientNew (36-74)
packages/client/src/links/httpLink.ts (1)
  • httpLink (75-141)
⏰ 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). (19)
  • GitHub Check: E2E-tests (nuxt)
  • GitHub Check: E2E-tests (minimal-react)
  • GitHub Check: E2E-tests (fastify-server)
  • GitHub Check: E2E-tests (express-server)
  • GitHub Check: E2E-tests (cloudflare-workers)
  • GitHub Check: E2E-tests (.test/ssg)
  • GitHub Check: E2E-tests (.test/internal-types-export)
  • GitHub Check: e2e-legacy-node (next-prisma-websockets-starter, 20.x)
  • GitHub Check: e2e-legacy-node (next-prisma-todomvc, 20.x)
  • GitHub Check: e2e-legacy-node (next-prisma-websockets-starter, 18.x)
  • GitHub Check: e2e-legacy-node (next-prisma-todomvc, 18.x)
  • GitHub Check: e2e-legacy-node (next-prisma-starter, 20.x)
  • GitHub Check: e2e-legacy-node (next-prisma-starter, 18.x)
  • GitHub Check: Release using pkg.pr.new
  • GitHub Check: Test a monorepo using built declaration files
  • GitHub Check: E2E-tests (Bun) (bun, ubuntu-latest)
  • GitHub Check: E2E-tests (Deno) (deno-deploy)
  • GitHub Check: test
  • GitHub Check: Lint and auto-fix
🔇 Additional comments (3)
packages/client/src/links/internals/httpUtils.ts (2)

200-200: Correct precedence: single computed HTTP method (incl. methodOverride).

Deriving the method once with opts.methodOverride ?? METHOD[opts.type] is clean and avoids recomputing. This also makes downstream checks (like header gating) consistent.


219-223: Consistent method usage in fetch call.

Using the computed method here ensures consistency with header gating and URL/body construction logic.

packages/tests/server/contentTypeHeaders.test.ts (1)

1-14: Test harness looks solid.

Lightweight mockFetch wrapper keeps assertions simple and doesn’t mutate global fetch. Good call.

@israelguardoc
Copy link

Good idea @anatolzak!
This fix will also save the OPTIONS request before any GET

@KATT KATT enabled auto-merge (squash) September 5, 2025 14:41
@KATT KATT changed the title fix(#6913): omit content type header in GET requests fix(server): omit content type header in GET requests Sep 5, 2025
@KATT KATT changed the title fix(server): omit content type header in GET requests fix(client): omit content type header in GET requests Sep 5, 2025
@KATT KATT disabled auto-merge September 5, 2025 14:47
@KATT KATT enabled auto-merge (squash) September 5, 2025 14:47
@KATT KATT merged commit f3ddc3a into trpc:main Sep 5, 2025
42 of 46 checks passed
@github-actions
Copy link
Contributor

github-actions bot commented Sep 6, 2025

This pull request has been locked because we are very unlikely to see comments on closed issues. If you think, this PR is still necessary, create a new one with the same branch. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 6, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: GET requests should not include the Content-Type request header

3 participants