Query Complexity

A single GraphQL query can potentially generate a huge workload for a server, like thousands of database operations which can be used to cause DDoS attacks. In order to limit and keep track of what each GraphQL operation can do, the query complexity plugin allows defining field-level complexity values that works with the graphql-query-complexity library.

To install, add the queryComplexityPlugin to the makeSchema.plugins array, along with any other plugins you'd like to include:

1import { makeSchema, queryComplexityPlugin } from '@nexus/schema'
2
3const schema = makeSchema({
4 // ... types, etc,
5 plugins: [
6 // ... other plugins
7 queryComplexityPlugin(),
8 ],
9})

The plugin will install a complexity property on the output field config:

1export const User = objectType({
2 name: 'User',
3 definition(t) {
4 t.id('id', {
5 complexity: 2,
6 })
7 },
8})

And of course, integrate graphql-query-complexity with your GraphQL server. You can setup with express-graphql as in the library's example.

Complexity Value

There are two ways to define the complexity:

  • A number
  • A complexity estimator

The easiest way to specify the complexity is to just provide a number, as in the example code above. The complexity property can be omitted if its value is 1, provided that you have a simple estimator of 1 when configuring graphql-query-complexity like below:

1const complexity = getComplexity({
2 // ... other configurations
3 estimators: [
4 // All undefined complexity values will fallback to 1
5 simpleEstimator({ defaultComplexity: 1 }),
6 ],
7})

Another way is with the complexity estimator, which is a function that returns a number, but also provides arguments to compute the final value. The query complexity plugin augments graphql-query-complexity's default complexity estimator by providing its corresponding nexus types to ensure type-safety. No additional arguments are introduced so the function declaration is still syntactically equal.

Augmented complexity estimator function signature:

1type QueryComplexityEstimatorArgs<TypeName extends string, FieldName extends string> = {
2 // The root type the field belongs too
3 type: RootValue<TypeName>
4
5 // The GraphQLField that is being evaluated
6 field: GraphQLField<RootValue<TypeName>, GetGen<'context'>, ArgsValue<TypeName, FieldName>>
7
8 // The input arguments of the field
9 args: ArgsValue<TypeName, FieldName>
10
11 // The complexity of all child selections for that field
12 childComplexity: number
13}
14
15type QueryComplexityEstimator = (options: QueryComplexityEstimatorArgs) => number | void

And you can use it like so:

1export const users = queryField('users', {
2 type: 'User',
3 list: true,
4 args: {
5 count: intArg({ nullable: false }),
6 },
7 // This will calculate the complexity based on the count and child complexity.
8 // This is useful to prevent clients from querying mass amount of data.
9 complexity: ({ args, childComplexity }) => args.count * childComplexity,
10 resolve: () => [{ id: '1' }],
11})

For more info about how query complexity is computed, please visit graphql-query-complexity.

Edit this page on Github