Skip to content

fix(apollo): preserve HTTP 200 for execution-level GraphQL errors#3940

Merged
kamilmysliwiec merged 1 commit intonestjs:masterfrom
maruthang:fix/issue-2940-apollo-error-http-status
Apr 20, 2026
Merged

fix(apollo): preserve HTTP 200 for execution-level GraphQL errors#3940
kamilmysliwiec merged 1 commit intonestjs:masterfrom
maruthang:fix/issue-2940-apollo-error-http-status

Conversation

@maruthang
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
  • Documentation content changes
  • Other... Please describe:

(Selected: Bugfix.)

What is the current behavior?

Issue Number: #2940

When a resolver throws a raw GraphQLError whose extensions.http.status is set (or a NestJS HTTP exception that maps onto Apollo's HTTP-status semantics), the GraphQL endpoint returns the resolver-derived HTTP 4xx/5xx status. Some GraphQL clients and UIs (Apollo Studio, Sandbox) then refuse to parse the response as {data, errors} and wrap it as a transport-level { error: ... } envelope, which is what the issue's reporter sees. The bug is Apollo-specific because Mercurius does not honour extensions.http.status.

What is the new behavior?

Adds a tiny Apollo Server plugin (createPreserveHttpStatusPlugin) registered in ApolloBaseDriver.mergeDefaultOptions. The plugin runs in willSendResponse after Apollo has finished merging error-derived HTTP status onto the response and resets the status to 200 when the request reached execution (detected by the presence of a data key on singleResult). Request-level failures (parse, validate) are untouched because their body has no data key. The plugin is gated on the existing autoTransformHttpErrors flag — users who want Apollo's default behaviour can opt out with autoTransformHttpErrors: false. Regression spec at packages/apollo/tests/e2e/issue-2940-http-error-status.spec.ts covers four cases: NestJS exception → 200, raw GraphQLError with http.status: 400 → 200, parse failure → 400 preserved, opt-out → 400 preserved.

Does this PR introduce a breaking change?

  • Yes
  • No

Users currently throwing raw GraphQLError with extensions.http.status set deliberately and expecting the HTTP status to leak into the response will now see HTTP 200 unless they set autoTransformHttpErrors: false. The flag already exists in the public driver config and is exactly the knob for this behaviour.

Other information

The plugin reuses the existing public flag autoTransformHttpErrors. Users who explicitly relied on extensions.http.status mapping to a non-2xx response can opt back in by setting autoTransformHttpErrors: false. Verified with the full apollo suite (yarn test:e2e:apollo: 133 passed / 3 skipped) including existing pipes.spec.ts and guards-filters.spec.ts error-handling tests, plus yarn test:e2e:graphql (only the unrelated pre-existing Windows CRLF snapshot failures in tests/plugin/model-class-visitor.spec.ts es5-eager-imports and tests/plugin/readonly-visitor.spec.ts appear; both reproducible on clean master). Prettier (v3 binary) and oxlint clean on touched files.

Apollo Server consumes `extensions.http.status` on a GraphQLError and uses it as the
response HTTP status before the user's formatError hook runs. Some GraphQL clients and UIs
(Apollo Studio, Sandbox) refuse to parse non-2xx responses as `{data, errors}` and surface
them as transport errors instead. NestJS HttpExceptions were already normalised by
wrapFormatErrorFn but a raw GraphQLError carrying `http.status` slipped through.

Register a willSendResponse plugin that resets the HTTP status to 200 once execution has
run (detected by the presence of `data` on the single result body). Request-level
failures (parse, validate) keep their non-200 status because their body has no `data` key.
The plugin reuses the existing public `autoTransformHttpErrors` flag, so users who want
Apollo's default semantics can opt out with `autoTransformHttpErrors: false`.

Closes nestjs#2940
@kamilmysliwiec kamilmysliwiec merged commit 3bd9895 into nestjs:master Apr 20, 2026
1 check passed
@kamilmysliwiec
Copy link
Copy Markdown
Member

lgtm

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.

2 participants