8000 add guide for query complexity controls · sarahxsanders/graphql-js@479b719 · GitHub
[go: up one dir, main page]

8000 Skip to content

Commit 479b719

Browse files
committed
add guide for query complexity controls
1 parent 98eff7f commit 479b719

File tree

2 files changed

+231
-0
lines changed

2 files changed

+231
-0
lines changed

website/pages/docs/_meta.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const meta = {
2323
'cursor-based-pagination': '',
2424
'custom-scalars': '',
2525
'advanced-custom-scalars': '',
26+
'query-complexity-controls': '',
2627
'n1-dataloader': '',
2728
'resolver-anatomy': '',
2829
'graphql-errors': '',
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
---
2+
title: Query Complexity Controls
3+
---
4+
5+
# Query Complexity Controls
6+
7+
GraphQL gives clients a lot of flexibility to shape responses, but that
8+
flexibility can also introduce risk. Clients can request deeply nested fields or
9+
large volumes of data in a single query. Without controls, these operations can slow
10+
down your server or expose security vulnerabilities.
11+
12+
This guide explains how to measure and limit query complexity in GraphQL.js
13+
using static analysis. You'll learn how to estimate the cost
14+
of a query before execution and reject it if it exceeds a safe limit.
15+
16+
## Why complexity control matters
17+
18+
GraphQL lets clients choose exactly what data they want. That flexibility is powerful,
19+
but it also makes it hard to predict the runtime cost of a query just by looking
20+
at the schema.
21+
22+
Without safeguards, clients could:
23+
24+
- Request deeply nested object relationships
25+
- Use recursive fragments to multiply field resolution
26+
- Exploit pagination arguments to retrieve excessive data
27+
28+
Query complexity controls help prevent these issues. They allow you to:
29+
30+
- Protect your backend from denial-of-service attacks or accidental load
31+
- Enforce cost-based usage limits between clients or environments
32+
- Detect expensive queries early in development
33+
34+
## Estimating query cost
35+
36+
To measure a query's complexity, you typically:
37+
38+
1. Parse the incoming query into a GraphQL document.
39+
2. Walk the query's Abstract Syntax Tree (AST), which represents its structure.
40+
3. Assign a cost to each field, often using static heuristics or metadata.
41+
4. Reject or log the query if it exceeds a maximum allowed complexity.
42+
43+
You can do this using custom middleware or validation rules that run before execution.
44+
No resolvers are called unless the query passes these checks.
45+
46+
## Simple complexity calculation
47+
48+
The `graphql-query-complexity` package calculates query cost by walking the AST. Here's a
49+
simple example using `simpleEstimator`, which assigns a flat cost to every field:
50+
51+
```js
52+
import { parse } from 'graphql';
53+
import { getComplexity, simpleEstimator } from 'graphql-query-complexity';
54+
import { schema } from './schema.js';
55+
56+
const query = `
57+
query {
58+
users {
59+
id
60+
name
61+
posts {
62+
id
63+
title
64+
}
65+
}
66+
}
67+
`;
68+
69+
const complexity = getComplexity({
70+
schema,
71+
query: parse(query),
72+
estimators: [simpleEstimator({ defaultComplexity: 1 })],
73+
variables: {},
74+
});
75+
76+
if (complexity > 100) {
77+
throw new Error(`Query is too complex: ${complexity}`);
78+
}
79+
80+
console.log(`Query complexity: ${complexity}`);
81+
```
82+
83+
In this example, every field costs 1 point. The total complexity is the number of fields,
84+
adjusted for nesting and fragments. The complexity is calculated before execution begins,
85+
allowing you to reject costly queries early.
86+
87+
## Custom cost estimators
88+
89+
Some fields are more expensive than others. For example, a paginated list might be more
90+
costly than a scalar field. You can define per-field costs using
91+
`fieldExtensionsEstimator`.
92+
93+
This estimator reads cost metadata from the field's `extensions.complexity` function in
94+
your schema. For example:
95+
96+
```js
97+
import { GraphQLObjectType, GraphQLList, GraphQLInt } from 'graphql';
98+
import { PostType } from './post-type.js';
99+
100+
const UserType = new GraphQLObjectType({
101+
name: 'User',
102+
fields: {
103+
posts: {
104+
type: GraphQLList(PostType),
105+
args: {
106+
limit: { type: GraphQLInt },
107+
},
108+
extensions: {
109+
complexity: ({ args, childComplexity }) => {
110+
const limit = args.limit ?? 10;
111+
return childComplexity * limit;
112+
},
113+
},
114+
},
115+
},
116+
});
117+
```
118+
119+
In this example, the cost of `posts` depends on the number of items requested (`limit`) and the
120+
complexity of each child field.
121+
122+
To evaluate the cost before execution, you can combine estimators like this:
123+
124+
```js
125+
import { parse } from 'graphql';
126+
import {
127+
getComplexity,
128+
simpleEstimator,
129+
fieldExtensionsEstimator,
130+
} from 'graphql-query-complexity';
131+
import { schema } from './schema.js';
132+
133+
const query = `
134+
query {
135+
users {
136+
id
137+
posts(limit: 5) {
138+
id
139+
title
140+
}
141+
}
142+
}
143+
`;
144+
145+
const document = parse(query);
146+
147+
const complexity = getComplexity({
148+
schema,
149+
query: document,
150+
variables: {},
151+
estimators: [
152+
fieldExtensionsEstimator(),
153+
simpleEstimator({ defaultComplexity: 1 }),
154+
],
155+
});
156+
157+
console.log(`Query complexity: ${complexity}`);
158+
```
159+
160+
Estimators are evaluated in order. The first one to return a numeric value is used
161+
for a given field.
162+
163+
This fallback approach allows you to define detailed logic for specific fields and use
164+
a default cost for everything else.
165+
166+
## Enforcing limits in your server
167+
168+
To enforce complexity limits automatically, you can use `createComplexityRule` from
169+
the same package. This integrates with GraphQL.js validation and prevents execution of
170+
overly complex queries.
171+
172+
Here's how to include it in your server's execution flow:
173+
174+
```js
175+
import { graphql, specifiedRules, parse } from 'graphql';
176+
import { createComplexityRule, simpleEstimator } from 'graphql-query-complexity';
177+
import { schema } from './schema.js';
178+
179+
const source = `
180+
query {
181+
users {
182+
id
183+
posts {
184+
title
185+
}
186+
}
187+
}
188+
`;
189+
190+
const document = parse(source);
191+
192+
const result = await graphql({
193+
schema,
194+
source,
195+
validationRules: [
196+
...specifiedRules,
197+
createComplexityRule({
198+
estimators: [simpleEstimator({ defaultComplexity: 1 })],
199+
maximumComplexity: 50,
200+
onComplete: (complexity) => {
201+
console.log('Query complexity:', complexity);
202+
},
203+
}),
204+
],
205+
});
206+
207+
console.log(result);
208+
```
209+
210+
If the query exceeds the defined complexity limit, GraphQL.js will return a validation
211+
error and skip execution.
212+
213+
This approach is useful when you want to apply global complexity rules without needing
214+
to modify resolver logic or add separate middleware.
215+
216+
## Best practices
217+
218+
- Set conservative complexity limits at first, and adjust them based on observed usage.
219+
- Use field-level estimators to better reflect real backend cost.
220+
- Log query complexity in development and production to identify inefficiencies.
221+
- Apply stricter limits for public or unauthenticated clients.
222+
- Combine complexity limits with depth limits, persisted queries, or operation
223+
whitelisting for stronger control.
224+
225+
## Additional resources
226+
227+
- [`graphql-query-complexity`](https://github.com/slicknode/graphql-query-complexity): A static analysis tool for measuring query cost in GraphQL.js servers
228+
- [`graphql-depth-limit`](https://github.com/graphile/depth-limit): A lightweight tool to restrict the maximum query depth
229+
- [GraphQL Specification: Operations and execution](https://spec.graphql.org/draft/#sec-Language.Operations)
230+
- [GraphQL.org: Security best practices](https://graphql.org/learn/security/)

0 commit comments

Comments
 (0)
0