How to Limit Query Complexity in GraphQL for Better Performance
To limit query complexity in GraphQL, use middleware or libraries like
graphql-query-complexity to analyze and restrict queries based on their depth or cost before execution. This prevents expensive queries from overloading your server by setting a maximum allowed complexity or depth.Syntax
Limiting query complexity typically involves adding a validation rule or middleware that inspects incoming queries. Key parts include:
maxComplexity: The maximum allowed complexity score for a query.complexityEstimator: A function to calculate complexity per field.validationRules: GraphQL server option to add complexity checks.
javascript
const { getComplexity, simpleEstimator } = require('graphql-query-complexity'); const complexityRule = (maxComplexity) => ({ validationRules: [ (context) => { const complexity = getComplexity({ schema: context.schema, query: context.document, estimators: [simpleEstimator({ defaultComplexity: 1 })], }); if (complexity > maxComplexity) { throw new Error(`Query is too complex: ${complexity}. Maximum allowed is ${maxComplexity}.`); } return null; }, ], });
Example
This example shows how to use the graphql-query-complexity library with an Apollo Server to limit queries to a maximum complexity of 100.
javascript
const { ApolloServer, gql } = require('apollo-server'); const { getComplexity, simpleEstimator } = require('graphql-query-complexity'); const typeDefs = gql` type Query { books: [Book] } type Book { title: String author: String } `; const resolvers = { Query: { books: () => [ { title: 'Book 1', author: 'Author A' }, { title: 'Book 2', author: 'Author B' }, ], }, }; const server = new ApolloServer({ typeDefs, resolvers, validationRules: [ (context) => { const complexity = getComplexity({ schema: context.schema, query: context.document, estimators: [simpleEstimator({ defaultComplexity: 1 })], }); if (complexity > 100) { throw new Error(`Query is too complex: ${complexity}. Maximum allowed is 100.`); } return null; }, ], }); server.listen().then(({ url }) => { console.log(`Server ready at ${url}`); });
Output
Server ready at http://localhost:4000/
Common Pitfalls
Common mistakes when limiting query complexity include:
- Not setting a complexity limit, which leaves the server vulnerable to expensive queries.
- Using only query depth limiting, which may not capture all costly queries.
- Failing to update complexity estimators when schema changes, causing inaccurate complexity calculations.
- Throwing generic errors without clear messages, making debugging harder.
javascript
/* Wrong: No complexity check, server vulnerable */ const server = new ApolloServer({ typeDefs, resolvers }); /* Right: Add complexity validation rule */ const serverWithLimit = new ApolloServer({ typeDefs, resolvers, validationRules: [complexityRule(100).validationRules[0]], });
Quick Reference
| Concept | Description |
|---|---|
| maxComplexity | Maximum allowed complexity score for queries |
| complexityEstimator | Function to assign cost to each field in a query |
| validationRules | GraphQL server option to add query complexity checks |
| graphql-query-complexity | Popular library to calculate and limit query complexity |
| Depth Limiting | Alternative method to limit query depth instead of complexity |
Key Takeaways
Use libraries like graphql-query-complexity to measure and limit query cost.
Set a reasonable maxComplexity to protect your server from expensive queries.
Combine complexity limiting with depth limiting for better protection.
Update complexity estimators when your schema changes to keep limits accurate.
Provide clear error messages when rejecting queries due to complexity.