GraphQL / Basic: language and response contracts / Lesson 6
Responses, errors and HTTP envelopes
Interpret data and errors together and keep language execution distinct from evolving HTTP transport conventions.
On this page
Lesson goals
- Classify request, field and network failures using available evidence.
- Design a client policy that recognizes partial data and uncertain write outcomes.
Read the whole response contract
A GraphQL result can contain data, errors and optional extensions. A request error that prevents execution differs from an execution field error that can coexist with partial data. The errors list is not a general substitute for every expected business result; an application may model those in typed payloads. Clients should preserve useful authorized data when their screen policy allows it and mark failed regions explicitly. Treating any HTTP success as complete application success or throwing away all partial data are both incomplete strategies.
Locate an execution failure by response path
Assume course is nullable and title and summary are independently nullable fields for this fixture. The summary resolver fails, while title succeeds. The path identifies the response location, including aliases if the operation used them. Locations, when provided, identify positions in the source document rather than database rows. The exact message and extension code below are illustrative application policy. Avoid exposing stack traces, raw database messages or credentials in public errors; preserve richer internal evidence through a controlled correlation identifier.
{
"data": {"course": {"title": "Graph foundations", "summary": null}},
"errors": [{"message": "Summary unavailable", "path": ["course", "summary"], "extensions": {"code": "SOURCE_UNAVAILABLE"}}]
}
The title remains usable under this fixture’s nullability. With a non-null summary, propagation could replace a parent instead.
Transport status is another layer of evidence
The GraphQL language specification is transport independent. HTTP guidance defines request media types, methods and response behavior separately, and the GraphQL-over-HTTP document remains a draft reference. Prefer application/graphql-response+json when supported and test the server/client pairing’s actual status policy. A query may use supported GET or POST; a mutation must not execute through GET. Authentication middleware, content negotiation and malformed JSON can reject a request before GraphQL execution, so the client may receive an ordinary HTTP error body rather than a GraphQL result envelope.
Separate a request body from an executed request
The JSON body below contains a document, an operation name and variables. It is a serializable fixture only; there is no endpoint or credential. An HTTP integration should pair it with an appropriate Content-Type and Accept header, enforce body bounds and apply its authentication policy before execution. Logging operation name and a safe document identifier is generally more controlled than logging the entire body. A client receiving a non-JSON proxy error must classify the transport failure rather than pretending its JSON parser exception was an application domain error.
{
"query": "query Card($id: ID!) { course(id: $id) { title } }",
"operationName": "Card",
"variables": {"id": "c1"}
}
This valid JSON selects Card and supplies id. It demonstrates envelope fields without transmitting anything.
Preserve uncertainty rather than inventing failure
If a connection disappears after a mutation was sent, absence of a response does not prove absence of a committed change. Read retries, write reconciliation and idempotency need distinct policies. Even queries may have expensive source effects if resolver design violates read-only expectations, so review implementation behavior rather than relying solely on the operation keyword. For partial data, define whether a view can render remaining regions, must refresh, or must block a sensitive decision. Record the error stage and path while keeping personal values out of telemetry.
Common errors and fixes
HTTP 200 is reported as complete success
- Why it happens
- The client ignored errors and domain payloads.
- How to fix it
- Evaluate transport, GraphQL result and application outcome separately.
- Prevent it next time
- Test partial data and expected rejection fixtures.
A network timeout triggers duplicate publication
- Why it happens
- No response was interpreted as no committed effect.
- How to fix it
- Reconcile by stable operation identity under an idempotency contract.
- Prevent it next time
- Test timeout-after-commit behavior.
A proxy HTML page becomes a domain error
- Why it happens
- The client assumed every body was GraphQL JSON.
- How to fix it
- Inspect media type and transport failure before decoding the result.
- Prevent it next time
- Cover non-JSON transport failures in client tests.
Check your understanding
1. A nullable summary fails but title succeeds. What should a client inspect before hiding the entire card?
Show worked solution
Inspect data, the errors path, schema nullability and the screen’s partial-data policy. It may render the authorized title and a clear unavailable state for summary. A sensitive workflow may instead require a complete refresh or block a decision. The decision belongs to the view’s correctness needs; do not infer that a partial result is either universally safe or universally useless.
2. Why must a client distinguish malformed JSON from a resolver error?
Show worked solution
Malformed transport JSON can prevent a GraphQL request from existing at all, while a resolver error happens after a valid operation begins execution. Their retry and debugging paths differ, and only the latter can have GraphQL partial data and response paths. Preserve media type, status and stage as safe metadata while avoiding raw private bodies in logs.
Key takeaways
- A response may combine useful data with field errors.
- Transport failures and uncertain writes require their own policy.