Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions docs/graphql-depth-limiter.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ The depth limiter is configured in the Apollo Server validation rules:

```typescript
validationRules: [
depthLimit(5),
depthLimit(4),
createComplexityRule({
maximumComplexity: 1000,
maximumComplexity: 300,
estimators: [
fieldExtensionsEstimator(),
simpleEstimator({ defaultComplexity: 1 }),
Expand All @@ -25,7 +25,8 @@ validationRules: [

## Configuration

- **Maximum Depth**: 5 levels
- **Maximum Depth**: 4 levels
- **Maximum Complexity**: 300 points per request
- **Package**: `graphql-depth-limit@1.1.0`

## How It Works
Expand Down Expand Up @@ -69,7 +70,7 @@ query {
}
```

**Error Response**: `"exceeds maximum operation depth of 5"`
**Error Response**: `"exceeds maximum operation depth of 4"`

## Testing

Expand All @@ -88,7 +89,7 @@ Comprehensive tests are located in `src/tests/graphql-depth-complexity.test.ts`:

## Related

- Query Complexity Limiting (max 1000 complexity points)
- Query Complexity Limiting (max 300 complexity points)
- Automatic Persisted Queries (APQ)
- GraphQL validation rules

Expand Down
9 changes: 6 additions & 3 deletions src/graphql/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const mergedResolvers = {
...subscriptionResolvers,
};

const MAX_GRAPHQL_DEPTH = 4;
const MAX_GRAPHQL_COMPLEXITY = 300;

export async function startApolloServer(
app: Application,
httpServer: Server,
Expand Down Expand Up @@ -55,10 +58,10 @@ export async function startApolloServer(
},

validationRules: [
depthLimit(5),
// Enforce strict query complexity limit of 500 points per request
depthLimit(MAX_GRAPHQL_DEPTH),
// Keep the API responsive for normal clients while blocking deep fan-out queries.
createComplexityRule({
maximumComplexity: 500,
maximumComplexity: MAX_GRAPHQL_COMPLEXITY,
estimators: [
fieldExtensionsEstimator(),
simpleEstimator({ defaultComplexity: 1 }),
Expand Down
71 changes: 40 additions & 31 deletions src/tests/graphql-depth-complexity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import {
fieldExtensionsEstimator,
} from "graphql-query-complexity";

const MAX_GRAPHQL_DEPTH = 4;
const MAX_GRAPHQL_COMPLEXITY = 300;

const typeDefs = `
type User {
id: ID!
Expand Down Expand Up @@ -67,7 +70,7 @@ function runValidation(
}

describe("GraphQL Depth Limit", () => {
it("should allow queries within the depth limit of 5", () => {
it(`should allow queries within the depth limit of ${MAX_GRAPHQL_DEPTH}`, () => {
const query = `
query GetDeep {
transaction(id: "1") {
Expand All @@ -83,11 +86,11 @@ describe("GraphQL Depth Limit", () => {
}
`;

const errors = runValidation(query, [depthLimit(5)]);
const errors = runValidation(query, [depthLimit(MAX_GRAPHQL_DEPTH)]);
expect(errors).toHaveLength(0);
});

it("should reject queries deeper than 5 levels", () => {
it(`should reject queries deeper than ${MAX_GRAPHQL_DEPTH} levels`, () => {
const query = `
query TooDeep {
me {
Expand All @@ -106,12 +109,14 @@ describe("GraphQL Depth Limit", () => {
}
`;

const errors = runValidation(query, [depthLimit(5)]);
const errors = runValidation(query, [depthLimit(MAX_GRAPHQL_DEPTH)]);
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].message).toContain("exceeds maximum operation depth of 5");
expect(errors[0].message).toContain(
`exceeds maximum operation depth of ${MAX_GRAPHQL_DEPTH}`,
);
});

it("should reject a query at exactly depth 6", () => {
it(`should reject a query at exactly depth ${MAX_GRAPHQL_DEPTH + 1}`, () => {
const query = `
query SixLevels {
me {
Expand All @@ -130,28 +135,24 @@ describe("GraphQL Depth Limit", () => {
}
`;

const errors = runValidation(query, [depthLimit(5)]);
const errors = runValidation(query, [depthLimit(MAX_GRAPHQL_DEPTH)]);
expect(errors.length).toBeGreaterThan(0);
});

it("should allow a query at exactly depth 5", () => {
it(`should allow a query at exactly depth ${MAX_GRAPHQL_DEPTH}`, () => {
const query = `
query FiveLevels {
query FourLevels {
me {
friends {
friends {
friends {
friends {
name
}
}
name
}
}
}
}
`;

const errors = runValidation(query, [depthLimit(5)]);
const errors = runValidation(query, [depthLimit(MAX_GRAPHQL_DEPTH)]);
expect(errors).toHaveLength(0);
});
});
Expand All @@ -169,7 +170,7 @@ describe("GraphQL Query Complexity", () => {

const errors = runValidation(query, [
createComplexityRule({
maximumComplexity: 500,
maximumComplexity: MAX_GRAPHQL_COMPLEXITY,
estimators: [
fieldExtensionsEstimator(),
simpleEstimator({ defaultComplexity: 1 }),
Expand Down Expand Up @@ -202,15 +203,15 @@ describe("GraphQL Query Complexity", () => {

const errors = runValidation(query, [
createComplexityRule({
maximumComplexity: 10,
maximumComplexity: 3,
estimators: [
fieldExtensionsEstimator(),
simpleEstimator({ defaultComplexity: 1 }),
],
}),
]);
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].message).toContain("too complex");
expect(errors[0].message).toContain("maximum complexity of 3");
});

it("should reject queries exceeding max query nodes", () => {
Expand All @@ -227,25 +228,33 @@ describe("GraphQL Query Complexity", () => {
}
`;

const errors = runValidation(query, [
createComplexityRule({
maximumComplexity: 10000,
maxQueryNodes: 2,
estimators: [
fieldExtensionsEstimator(),
simpleEstimator({ defaultComplexity: 1 }),
],
}),
]);
expect(errors.length).toBeGreaterThan(0);
let thrownError: Error | undefined;

try {
runValidation(query, [
createComplexityRule({
maximumComplexity: 10000,
maxQueryNodes: 2,
estimators: [
fieldExtensionsEstimator(),
simpleEstimator({ defaultComplexity: 1 }),
],
}),
]);
} catch (error) {
thrownError = error as Error;
}

expect(thrownError).toBeDefined();
expect(thrownError?.message).toContain("maximum allowed number of nodes");
});
});

describe("Combined Depth and Complexity Rules", () => {
const validationRules = [
depthLimit(5),
depthLimit(MAX_GRAPHQL_DEPTH),
createComplexityRule({
maximumComplexity: 1000,
maximumComplexity: MAX_GRAPHQL_COMPLEXITY,
estimators: [
fieldExtensionsEstimator(),
simpleEstimator({ defaultComplexity: 1 }),
Expand Down
Loading