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, TypeGraphQL
provides the option of integrating with Query Complexity tools like graphql-query-complexity.
This cost analysis-based solution is very promising, since we can define a “cost” per field and then analyze the AST to estimate the total cost of the GraphQL query. Of course all the analysis is handled by graphql-query-complexity
.
All we must do is define our complexity cost for the fields, mutations or subscriptions in TypeGraphQL
and implement graphql-query-complexity
in whatever GraphQL server that is being used.
How to use
First, we need to pass complexity
as an option to the decorator on a field, query or mutation.
Example of complexity
@ObjectType()
class MyObject {
@Field({ complexity: 2 })
publicField: string;
@Field({ complexity: ({ args, childComplexity }) => childComplexity + 1 })
complexField: string;
}
The complexity
option may be omitted if the complexity value is 1.
Complexity can be passed as an option to any @Field
, @FieldResolver
, @Mutation
or @Subscription
decorator. If both @FieldResolver
and @Field
decorators of the same property have complexity defined, then the complexity passed to the field resolver decorator takes precedence.
In the next step, we will integrate graphql-query-complexity
with the GraphQL server.
// Create GraphQL server
const server = new GraphQLServer({ schema });
// Configure server options
const serverOptions: Options = {
port: 4000,
endpoint: "/graphql",
playground: "/playground",
validationRules: req => [
queryComplexity({
// The maximum allowed query complexity, queries above this threshold will be rejected
maximumComplexity: 8,
// The query variables. This is needed because the variables are not available
// in the visitor of the graphql-js library
variables: req.query.variables,
// Optional callback function to retrieve the determined query complexity
// Will be invoked whether the query is rejected or not
// This can be used for logging or to implement rate limiting
onComplete: (complexity: number) => {
console.log("Query Complexity:", complexity);
},
estimators: [
// Using fieldConfigEstimator is mandatory to make it work with type-graphql
fieldConfigEstimator(),
// This will assign each field a complexity of 1 if no other estimator
// returned a value. We can define the default value for fields not explicitly annotated
simpleEstimator({
defaultComplexity: 1,
}),
],
}),
],
};
// Start the server
server.start(serverOptions, ({ port, playground }) => {
console.log(
`Server is running, GraphQL Playground available at http://localhost:${port}${playground}`,
);
});
And it's done! 😉
For more info about how query complexity is computed, please visit graphql-query-complexity.
Example
See how this works in the simple query complexity example.