Query Complexity
Baeta provides a query complexity analysis system that helps protect your GraphQL API from resource-exhausting queries. It calculates the complexity of incoming queries and rejects those that exceed configured limits.
Key Features
- Automatic complexity calculation for all fields
- Customizable complexity per field
- Dynamic limits based on context
- List operation handling with multipliers
- Depth and breadth limitations
- Context-aware complexity rules
Installation
- yarn
- npm
- pnpm
- bun
yarn add @baeta/extension-complexity
npm install @baeta/extension-complexity
pnpm add @baeta/extension-complexity
bun add @baeta/extension-complexity
Basic Setup
- Create the Extension
import { complexityExtension } from "@baeta/extension-complexity";
import type { Context } from "../types/context.ts";
const complexity = complexityExtension<Context>({
// Default complexity score for fields
defaultComplexity: 1,
// Multiplier applied to list fields
defaultListMultiplier: 10,
// Dynamic limits based on context
async limit(ctx) {
return {
depth: 10, // Maximum query depth
breadth: 50, // Maximum number of fields at each level
complexity: 1000, // Maximum total complexity score
};
},
// Alternatively, use static limits
// limit: {
// depth: 10,
// breadth: 50,
// complexity: 1000,
// }
});
- Register it in
src/modules/extensions.ts
import { createExtensions } from "@baeta/core";
import { complexityExtension } from "@baeta/extension-complexity";
import type { Context } from "../types/context.ts";
export default createExtensions({
complexityExtension: complexityExtension<Context>({
defaultComplexity: 1,
defaultListMultiplier: 10,
async limit(_ctx) {
return {
depth: 10,
breadth: 50,
complexity: 1000,
};
},
}),
//... other extensions
});
tip
Consider placing complexity checks before authorization. This can prevent unnecessary permission checks on queries that would be rejected for being too complex anyway.
Customizing Complexity Rules
Field-Level Configuration
You can customize complexity rules for specific fields or types:
import { UserModule } from "./typedef.ts";
const { Query, User } = UserModule;
// Custom complexity for a type — chain into $fields
const userResolver = User
.$complexity(() => ({
complexity: 2,
}))
.$fields({
id: User.id.key("id"),
email: User.email.key("email"),
});
// Disable complexity for a specific field
const userQuery = Query.user
.$complexity(() => false)
.resolve(({ args }) => {
return findUser(args.where);
});
// Custom complexity score for a list field
const usersQuery = Query.users
.$complexity(({ args, ctx }) => ({
complexity: 1,
multiplier: 5,
}))
.resolve(() => {
return findUsers();
});
Dynamic Complexity Rules
Complexity rules can be determined dynamically based on context or arguments:
const usersQuery = Query.users
.$complexity(({ args, ctx }) => {
return {
complexity: 1,
multiplier: args.limit,
};
})
.resolve(() => {
return findUsers();
});
How Complexity is Calculated
- Each field has a base complexity (default: 1)
- List fields are multiplied by the list multiplier
- Nested fields add to the total complexity
- The total is compared against the configured limit
Example:
query {
users(first: 10) {
# complexity: 1 * 10 (multiplier)
name # complexity: 1 * 10 (inherited multiplier)
posts {
# complexity: 1 * 10 * 10 (nested list)
title # complexity: 1 * 10 * 10 (inherited multiplier)
}
}
}
# Total complexity: 210