Skip to content

Move application/json responses to Appendix A#379

Open
martinbonnin wants to merge 7 commits intomainfrom
appendix-a
Open

Move application/json responses to Appendix A#379
martinbonnin wants to merge 7 commits intomainfrom
appendix-a

Conversation

@martinbonnin
Copy link
Copy Markdown
Contributor

@martinbonnin martinbonnin commented Apr 2, 2026

Follow up from #370

Move the application/json responses to a separate appendix to keep the spec easy to parse and implement (see also #329).

The main normative change is the requirement to support application/graphql-response+json. In a nutshell:

- A GraphQL server MUST support responses using at least one of the official GraphQL response media types.
+ A GraphQL server MUST support responses using the `application/graphql-response+json` media type.

All the rest is moving legacy parts of the spec to an appendix for better readability.

Servers can (and probably should) support both media types for better compatibility.

This is our one opportunity at a clean slate, let's keep the entropy and LLM contexts low.

@martinbonnin martinbonnin changed the title Add Appendix A -- application/json responses Move application/json responses to Appendix A Apr 3, 2026
@benjie
Copy link
Copy Markdown
Member

benjie commented Apr 16, 2026

For most of the last 10 years, there's been an unofficial GraphQL-over-HTTP spec which very very many custom servers implemented. There are major servers such as Apollo Server, Yoga, etc that many people depend upon and have been updated to conform with the latest GraphQL-over-HTTP spec, but serving GraphQL over HTTP is typically so simple that a GraphQL server can actually be implemented in very few lines of code, and very very many organizations have gone this route rather than adding a dependency:

  1. JSON decode incoming POST request to /graphql
  2. Validate shape of request object (query is string, variables optional object, operationName optional string)
  3. Send to GraphQL for execution
  4. JSON encode the result and send along with Content-Type: application/json and 200 OK

This might look something like (scratched out of my head directly into GitHub editor, probably don't use this in production I guess?):

import { graphql } from "graphql";
import express from "express";
import schema from "./schema";

export const app = express();
app.use(express.json());
app.post("/graphql", async (req, res) => {
  if (typeof req.body !== "object" || req.body == null)
    return res.status(400).json({ error: "Invalid GraphQL request" });
  const { query, variables, operationName } = req.body;
  if (typeof query !== "string")
    return res.status(400).json({ error: "Missing query document" });
  if (variables != null && (typeof variables !== "object" || Array.isArray(variables)))
    return res.status(400).json({ error: "Invalid variables" });
  if (operationName != null && typeof operationName !== "string")
    return res.status(400).json({ error: "Invalid operation name" });
  res.json(
    await graphql({ schema, source: query, variableValues: variables, operationName }),
  );
});

This is all that's needed to serve GraphQL over HTTP, and has been fine for the vast majority of users. It is the opposite of complex: it's incredibly simple. The GraphQL-over-HTTP spec as it currently stands allows this kind of very simple server.


HTTP status codes are here to serve a very specific audience: namely intermediary services on the server side of the network that do not understand GraphQL or do not want to parse the response body, but still want to know what's going on. This can be reverse proxies, caches, logging, anomaly detection, intrusion detection, etc.

HTTP status codes do not exist to benefit GraphQL clients. Clients don't need them, they simply need to know:

  1. Is this a GraphQL response? (Yes if either HTTP 200 or application/graphql-response+json)
  2. Is there any data I can use? (Yes if data is present)
  3. What, if anything, went wrong? (See errors list)

They should not use HTTP status codes for any other purpose - they can determine everything else from the payload.

GraphQL's new HTTP status codes are only there for the backend folks: backend developers, operations, caching, SRE, etc. If a GraphQL adopter does not need these, then it feels wrong to thrust upon them the requirement that they use a pre-built GraphQL-over-HTTP package that complies with all the status codes in the spec when they could solve it with the few lines of code above.

This pattern has been demonstrated since the very introduction of GraphQL in 2015 (ref: https://github.com/graphql/express-graphql/blob/ef4cff8ebf6cda2280c086e5f88de4d04e9d90f2/src/index.js) and it's just as valid today as it has always been.

Your proposal is to effectively force GraphQL adopters to use a more complex implementation, one that honors the various status codes, even if they won't benefit from it, because if they don't then over time the GraphQL clients they depend upon may no longer work with their custom servers since support is optional.

In my opinion, this move forces additional complexity on GraphQL adopters.

GraphQL adopters should be encouraged to use status codes, because it will likely benefit them in the long term. GraphQL server libraries and frameworks should be encouraged to support these status codes out of the box. GraphQL client libraries and frameworks should be encouraged to send requests that are compatible with status codes even if their servers don't yet support them because it enables the server team to enable this additional functionality (at the cost of a more complex server). But we should not mark all existing servers as GraphQL-over-HTTP non-compliant as a way of "forcing" them to adjust to what we currently feel is the best practice. If they don't need it, they should not have to adopt that complexity.

@benjie
Copy link
Copy Markdown
Member

benjie commented Apr 16, 2026

IMO the golden path is a better way to "enforce" this. All "golden path" servers MUST support the new media type and status code. But the GraphQL-over-HTTP spec, like the GraphQL Spec itself, should remain flexible and widely compatible.

@martinbonnin
Copy link
Copy Markdown
Contributor Author

@benjie can you share examples of projects that would be negatively affected by this move?

Both Apollo server and Yoga have been supporting application/graphql-response+json for years now (Apollo Server PR, Yoga PR) so that change is typically transparent for them.

Copy link
Copy Markdown
Member

@benjie benjie left a comment

Choose a reason for hiding this comment

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

For the reasons outlined in #379 (comment) I do not think we should make this change.

@benjie
Copy link
Copy Markdown
Member

benjie commented Apr 16, 2026

can you share examples of projects that would be negatively affected by this move?

Any project that doesn't use an off-the-shelf GraphQL server and instead wrote their own GraphQL middleware will be impacted by this in the long run. I cannot share specific examples (I treat client engagements and private discussions with confidence) but I can tell you that I've seen quite a number of GraphQL adopters who write middleware like the above into their codebase rather than adding a dependency - who needs an extra dependency when it's so simple, that's just going to get you more dependabot alerts!

Both Apollo server and Yoga have been supporting application/graphql-response+json for years now

Indeed, I noted this above:

There are major servers such as Apollo Server, Yoga, etc that many people depend upon and have been updated to conform with the latest GraphQL-over-HTTP spec, but serving GraphQL over HTTP is typically so simple that a GraphQL server can actually be implemented in very few lines of code, and very very many organizations have gone this route"

Essentially, this move requires the use of a GraphQL-over-HTTP capable library because it increases the complexity of supporting GraphQL-over-HTTP from a few lines of code to a significantly larger undertaking that must break out parse/validate and be more precise with status codes. If you don't need that, the complexity is unnecessary, and I think mandating it would be a net negative for GraphQL. This would be akin to requiring a GraphQL client rather than just allowing window.fetch(); sure we think the best practice is to use one - to use fragment colocation, normalized caching, etc - but it should not be required to be compliant with the technology.

@Urigo
Copy link
Copy Markdown

Urigo commented Apr 16, 2026

Thanks for the discussion.

In general, I agree with what @benjie wrote but I would love to hear more and see these examples of the simple servers you are talking about - who are they? where do you see them?

Also, I would love to also add to the discussion the benefits of @martinbonnin's change - what it helps with and for whom?

Seems like we need to choose between people building custom implementations and people who use tools?
And maybe the majority of people are the latter?

@enisdenjo
Copy link
Copy Markdown
Member

My 2 cents is agreeing with this statement:

[...] who needs an extra dependency when it's so simple, that's just going to get you more dependabot alerts!

and

Essentially, this move requires the use of a GraphQL-over-HTTP capable library because it increases the complexity of supporting GraphQL-over-HTTP from a few lines of code to a significantly larger undertaking that must break out parse/validate and be more precise with status codes. If you don't need that, the complexity is unnecessary, and I think mandating it would be a net negative for GraphQL.

and

This would be akin to requiring a GraphQL client rather than just allowing window.fetch(); sure we think the best practice is to use one - to use fragment colocation, normalized caching, etc - but it should not be required to be compliant with the technology.

But still, I'd love to weigh the pros and cons. Why's having application/graphql-response+json a MUST necessarely a good thing?

@martinbonnin
Copy link
Copy Markdown
Contributor Author

Thanks all for the comments. I will need a bit of time to process everything wanted to give a few comments while we're looking at this:

First of, we live in a GraphQL bubble. We read specs all day long (or at least from time to time). But it's not the case for everyone, this issue is the most upvoted issue in this repo and is some indication that simplification would be welcome.

Reading all of this, I believe there are 2 different considerations at play:

  1. allowing simple implementations to return application/json in case a well formed response cannot be formed.
  2. in the long run, making sure that every well formed response comes with the new application/graphql-request+json

I'm mostly concerned about 2. 10 years from now, I don't want to receive my GraphQL response with application/json, have branches in my code and ask every time I debug something what branch we're in.

Clarity and good defaults are what make this spec valuable.

I think I'm down to rephrase this:

A GraphQL server MUST support responses using the `application/graphql-response+json` media type.

as:

If a well formed GraphQL response is returned, a GraphQL server MUST use the `application/graphql-response+json` media type.

Some parting thoughts:

who needs an extra dependency when it's so simple

I believe the past 10 years are proof that GraphQL observability is not that simple. Sure you can make a simple implementation but that might come back bit you down the road and then you come up on Twitter complaining about "200 OK".

Whether this is a spec thing or golden path thing is an open question. It comes down to how much opinionated we want to be at the end of the day.

@benjie
Copy link
Copy Markdown
Member

benjie commented Apr 16, 2026

I would love to hear more and see these examples of the simple servers you are talking about - who are they? where do you see them?

I can't find great public examples, because it's very hard to search for and these are the kinds of thing that people generally just put together in their own apps which aren't typically open source.

However, it's possible to find tutorials including copy-pasteable example handlers that don't use a server framework - I don't know how to enumerate the people who actually followed these tutorials though!

Our very own "serving over HTTP" page has walked people through the general concepts of doing this themselves since September 2015, so anyone who followed that in a language other than JS (where express-graphql/Apollo Server were recommended at the time) may well have rolled their own. I wouldn't know how to go about enumerating that either!


Seems like we need to choose between people building custom implementations and people who use tools? And maybe the majority of people are the latter?

The majority are almost certainly the latter indeed; but I don't think that means we should exclude the former... I don't think we need to choose between them at all. The current version of the spec states:

A server MUST support responses using at least one of the official GraphQL response media types.
For maximal compatibility, a server SHOULD support using both the application/json and the application/graphql-response+json media types for responses.
Each newly created or updated GraphQL server SHOULD support responses using the application/graphql-response+json media type.
A legacy server is a server that does not support responses using the application/graphql-response+json media type.

and:

The client MUST include the media type application/graphql-response+json in the Accept header.
If the client knows that the server supports application/graphql-response+json, it is RECOMMENDED that the client set the Accept header to application/graphql-response+json. Otherwise, to maximize compatibility the client SHOULD include the media type application/json in the Accept header and it is RECOMMENDED that the client set the Accept header to application/graphql-response+json, application/json;q=0.9.

Together these require clients to support at least the new version and they should1 support the legacy version - as mentioned above, it doesn't really make much odds to them, they just need to do the "is 200 or application/graphql-response+json" check when receiving a response and include both media types in their Accept header and they're golden.

Servers have the much harder time so they may choose which version they support. For maximal compatibility (i.e. if you're building a server library that others will use) they should support both, but they don't have to.

This allows server implementers, the people who benefit (or not) from HTTP status codes, to make the decision as to whether they want status codes (in which case they'd probably use an off the shelf library that offers this by default) or whether they want simplicity/minimal bundle size (in which case they might just add a few lines of code themselves, or have their LLM do it). It allows us to serve both people rolling their own and people who use tools.


If a well formed GraphQL response is returned, a GraphQL server MUST use the application/graphql-response+json media type.

This still requires servers to be more complex than is required today.


Sure you can make a simple implementation but that might come back bit you down the road

If and when that happens, you as the server developer can switch out your 10 line JS function for a GraphQL server that implements the HTTP status codes, it should be very simple to achieve when you need it - add the dependency, switch out your middleware, nothing else needs to change.

and then you come up on Twitter complaining about "200 OK".

And people can respond: "just use a decent GraphQL HTTP library, they handle this!" because that's what the spec will encourage of all HTTP libraries now. Hence the SHOULD. The historical problem has been that there has been no standard solution to this. There is now (or there will be as soon as we get this spec shipped).


It comes down to how much opinionated we want to be at the end of the day.

Agree, and we have precedent for this. The spec should focus on compatibility and interoperability. The documentation should focus on best practices. The golden path should focus on guiding people to the pit of success (which would, indeed, require usage of the new media type).

Footnotes

  1. This discussion is actually convincing me that we should change the client requirement to MUST support both unless they know the server will always support the new media type.

@benjie
Copy link
Copy Markdown
Member

benjie commented Apr 16, 2026

#329 is the most upvoted issue in this repo and is some indication that simplification would be welcome.

This was partially addressed by the removal of the watershed following the discussions; the spec is already simpler than it was when that issue was filed.

martinbonnin added a commit to graphql/graphql-wg that referenced this pull request Apr 16, 2026
* Removing Appendix A: My brain is fried and I won't have the time to process graphql/graphql-over-http#379 in time
* Add aim for other GraphQL over HTTP items
* Add Can I use.
martinbonnin added a commit to graphql/graphql-wg that referenced this pull request Apr 16, 2026
* Removing Appendix A: My brain is fried and I won't have the time to process graphql/graphql-over-http#379 in time
* Add aim for other GraphQL over HTTP items
* Add Can I use.
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.

4 participants