Every GraphQL client, from fetch to Apollo, speaks this simple HTTP protocol.
GraphQL over HTTP is standardized by the GraphQL foundation's GraphQL-over-HTTP specification. A request is normally a POST with a JSON body:
{"query": "...", "operationName": "Book", "variables": {"id": "b1"}}
Send Content-Type: application/json and Accept: application/graphql-response+json, application/json. Queries (never mutations) may also be sent as GET with URL-encoded query and variables parameters, which makes them cacheable by CDNs.
Status codes: servers using the newer application/graphql-response+json media type return 4xx for requests that fail before execution (parse or validation errors), and 200 when execution started, even if some fields errored. With classic application/json, 200 is used for nearly everything, so clients must always inspect the errors array.
A plain fetch is enough for scripts. Client libraries add a lot:
- Normalized caching by
__typename+id(Apollo Client, urql's graphcache, Relay). - Code generation of TypeScript types from the schema and operations (GraphQL Code Generator).
- Fragment colocation, pagination helpers, retries, subscriptions.
Servers commonly expose GraphiQL or a similar IDE at the endpoint in development; turn it (and introspection for public APIs) off or restrict it in production if your schema is private.
Example
const res = await fetch("https://countries.trevorblades.com/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/graphql-response+json, application/json",
},
body: JSON.stringify({
query: `query Country($code: ID!) { country(code: $code) { name capital } }`,
operationName: "Country",
variables: { code: "AM" },
}),
});
const { data, errors } = await res.json();
if (errors) console.error(errors);
console.log(data.country); // { name: 'Armenia', capital: 'Yerevan' }Remember
- POST JSON with query, operationName and variables
- GET is allowed for queries and enables HTTP caching
- Always check
errors, even on HTTP 200
Watch out for
- Sending mutations over GET; servers must reject them to avoid CSRF-style side effects.