JSON:API's fields/include parameters solve over-fetching by letting you narrow a fixed response shape. GraphQL solves the same problem differently: instead of narrowing a shape someone else defined, you describe the exact shape you want and the server builds precisely that — nothing more. This final lesson is about writing GraphQL queries in Drupal that actually deliver on that promise, rather than accidentally re-creating the over-fetching problem inside a GraphQL wrapper.
What you'll learn in this lesson
- Installing and enabling the GraphQL module
- Writing a query that requests only the fields a component actually renders
- Why nested queries are GraphQL's biggest performance risk, and how depth limiting protects against it
- Enabling GraphQL's own response caching
Installing the GraphQL module
composer require drupal/graphql
drush en graphql -y
Once enabled, Configuration → GraphQL → Settings lets you define a schema — the contract describing what queries and fields are actually available. Getting the schema right up front (only exposing entities and fields your frontend genuinely needs) is itself a performance decision: a smaller, deliberate schema is easier to reason about and harder to accidentally query into an expensive corner.
Requesting exactly what you need
This is GraphQL's core performance advantage over a fixed REST/JSON:API shape — the query itself declares the response shape:
query {
nodeArticles(first: 5) {
nodes {
title
body {
summary
}
author {
name
}
}
}
}
Notice what's absent: no created timestamp, no revision info, no unused fields — because nobody asked for them. Compare this to a REST endpoint returning a fixed entity representation regardless of what the caller actually uses; GraphQL's response is exactly as large as this query, no larger.
The risk hiding in nested queries
The same flexibility that makes GraphQL efficient also makes it dangerous without guardrails. A query can nest arbitrarily deep — articles, each with an author, each with their other articles, each with their authors — and a naive resolver will happily walk that entire graph, turning one HTTP request into potentially thousands of database queries server-side.
# A pathological, unbounded query — don't let clients send this
query {
nodeArticles {
nodes {
author {
articles {
nodes {
author {
articles { nodes { title } }
}
}
}
}
}
}
}
Drupal's GraphQL module supports query depth limiting and complexity analysis specifically to reject queries like this before they ever reach a resolver — configure a maximum depth in the module's settings rather than trusting every client to write well-behaved queries.
Quick check: why is an unbounded nested GraphQL query more dangerous than a similarly complex JSON:API request with several
includeparameters? (Because JSON:API'sincludedepth is naturally bounded by how many parameters you're willing to type — GraphQL's nesting is defined by the query's own shape and can recurse arbitrarily deep in a single, compact query string, which is exactly why depth limiting isn't optional in production.)
Key takeaways
- GraphQL's core performance advantage is that the query itself defines the response shape — no over-fetching by design, if the query is written well.
- Keep your exposed schema deliberately narrow — only what the frontend genuinely needs.
- Configure query depth/complexity limits — GraphQL's flexibility is also its biggest risk without them.
- Use persisted queries in production to restrict execution to a pre-approved set rather than trusting arbitrary client-supplied query strings.
Coming up next
That's the full Performance Optimization path — caching strategies and backends, database and frontend tuning, entity and Views optimization, server-side configuration, debugging tools, and finally headless-specific techniques like this one. You now have a genuinely complete toolkit for finding and fixing the slow parts of a Drupal site, from the database up through the API layer a headless frontend actually talks to. Well done getting through all of it.