How it works
The explanation, with examples you can run.
The shape of an error
{ "data": { "post": { "title": "Hello", "viewCount": null } }, "errors": [ { "message": "stats service timed out", "locations": [{ "line": 4, "column": 5 }], "path": ["post", "viewCount"], "extensions": { "code": "UPSTREAM_TIMEOUT" } } ]}path names the response position that failed, including list indexes (["posts", 3, "author"]). locations point into the query document. extensions is free-form; servers put machine-readable codes there. Keep messages in production generic and log the details server-side: stack traces and internal names in errors tell an attacker a lot.
How far a null travels
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
The error is recorded once, at the field that failed; what changes is how much of the response is lost. With viewCount nullable, only that number is missing. Marking it Int! costs the whole post, and marking post non-null too costs the entire response. Put ! where a value is truly guaranteed, and leave fields that depend on other services nullable.
Errors as data
Errors in errors are for things that went wrong. Outcomes the user can act on, such as a duplicate email, a declined card or a missing permission they could request, are usually better modelled in the schema:
type Mutation { signUp(input: SignUpInput!): SignUpResult! }union SignUpResult = SignUpSuccess | EmailTaken | InvalidInputThe client then handles each case with typed fields instead of parsing messages, and the schema documents every outcome.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.