Skip to main content
Version: Next (2.x)

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 add @baeta/extension-complexity

Basic Setup

  1. 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,
// }
});
  1. 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

  1. Each field has a base complexity (default: 1)
  2. List fields are multiplied by the list multiplier
  3. Nested fields add to the total complexity
  4. 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