GraphQL validates queries against the schema for free — and returns partial data with a structured errors array. Learn to read it and raise clean, typed errors.
Why: before any resolver runs, GraphQL checks the query against the schema — unknown fields, wrong argument types, and missing required arguments are rejected with a clear message. You get this for free; a whole class of bad requests never reaches your code.
// Querying a field that doesn't exist returns:
{
"errors": [
{
"message": "Cannot query field \"titel\" on type \"Book\". Did you mean \"title\"?"
}
]
}Why: unlike REST's single status code, a GraphQL response can carry BOTH data and errors. If one field fails but others succeed, you get the partial data plus an errors array — each error pointing at the field path that failed. Always check for errors even when data is present.
{
"data": {
"book": { "title": "Dune", "author": null }
},
"errors": [
{
"message": "Failed to load author",
"path": ["book", "author"]
}
]
}Why: when a resolver hits a real problem — not found, forbidden, bad input — throw a GraphQLError with an extensions.code so clients can branch on a stable machine-readable code instead of parsing message strings. This is the GraphQL equivalent of meaningful HTTP status codes.
import { GraphQLError } from 'graphql'
const resolvers = {
Query: {
book: (_p, { id }) => {
const book = books.find((b) => b.id === id)
if (!book) {
throw new GraphQLError('Book not found', {
extensions: { code: 'NOT_FOUND' },
})
}
return book
},
},
}Note: in production, never send raw exception messages or stack traces to clients — they leak implementation details. Disable stack traces in production, return safe messages with stable codes, and log the real error server-side. Treat the errors array as a client-facing API, not a debug dump.
✓ stable extensions.code (NOT_FOUND, FORBIDDEN, BAD_USER_INPUT)
✓ safe, user-facing message
✓ log the real error + stack server-side
✗ no raw stack traces / DB errors in the response (prod)