Skip to content

GraphQL Cost Analysis — Query Complexity and Field Weighting for API Protection

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Graphql Cost Analysis. We cover key concepts, practical examples, and best practices to help you master this topic.

GraphQL cost analysis assigns computational weights to each field in your schema and calculates the total cost of a query before execution, rejecting queries that exceed a configured budget to protect server resources.

What You'll Learn

  • Why query cost varies between GraphQL and REST
  • Assigning static and dynamic field weights
  • Implementing cost analysis with graphql-query-complexity
  • Handling list fields with pagination multipliers
  • Integrating cost limits with Apollo Server

Why It Matters

A deeply nested GraphQL query can trigger hundreds of database calls and return megabytes of data. Cost analysis catches these queries before they execute, preventing cascading failures. DodaTech's Durga Antivirus Pro uses cost analysis to budget 1000 points per query — fetching 50 devices with their latest scan results costs 120 points, but requesting all 10 million devices with nested user data costs over 5 million points and gets rejected.

Real-World Use

A developer accidentally writes a recursive query: devices { user { devices { user { devices } } } }. Cost analysis detects the complexity grows exponentially and rejects the query with a clear error message, preventing a database meltdown.

flowchart TB
    A["Incoming Query"] --> B["Parse AST"]
    B --> C["Walk All Selections"]
    C --> D["Lookup Field Weight"]
    D --> E["Apply Multipliers\n(list size, depth)"]
    E --> F["Sum Total Cost"]
    F --> G{Exceeds Budget?}
    G -->|No| H["Execute Query"]
    G -->|Yes| I["Reject with Error"]
    style G fill:#fef3c7,stroke:#d97706
    style I fill:#fecaca,stroke:#dc2626

Code Examples

Example 1: Static Field Weights with graphql-query-complexity

const { createComplexityLimitRule } = require('graphql-validation-complexity');

const complexityRule = createComplexityLimitRule(1000, {
  scalarCost: 1,
  objectCost: 5,
  listFactor: 10,
  formatErrorMessage: cost =>
    `Query complexity ${cost} exceeds maximum of 1000`,
});

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [complexityRule],
});

Example 2: Custom Field-Level Weights

const { createComplexityLimitRule } = require('graphql-validation-complexity');

const complexityRule = createComplexityLimitRule(1000, {
  onCost: cost => console.log(`Query cost: ${cost}`),
  estimators: [
    // Static field weights based on database query cost
    {
      type: 'field',
      fieldConfigs: {
        'Query.devices': { complexity: 10 },
        'Query.threats': { complexity: 15 },
        'Device.scanResults': { complexity: 20 },
        'User.paymentInfo': { complexity: 50 },
        'ThreatReport.rawLog': { complexity: 30 },
      },
    },
  ],
});

Example 3: Dynamic Cost with List Size Estimation

function createDynamicCostEstimator() {
  return (args) => {
    const { parentType, fieldName, childComplexity, args: fieldArgs } = args;
    
    let baseCost = 1;
    
    // Database-backed fields cost more
    if (fieldName === 'devices' || fieldName === 'threats') {
      baseCost = 10;
    }
    
    // List fields multiply by expected size
    if (fieldArgs.first) {
      return baseCost + (childComplexity * fieldArgs.first);
    }
    if (fieldArgs.limit) {
      return baseCost + (childComplexity * fieldArgs.limit);
    }
    
    // Default list size assumption
    if (parentType.name === 'Query' && childComplexity > 0) {
      return baseCost + (childComplexity * 20);
    }
    
    return baseCost + childComplexity;
  };
}

const server = new ApolloServer({
  validationRules: [
    createComplexityLimitRule(1000, {
      estimators: [createDynamicCostEstimator()],
    }),
  ],
});

Common Mistakes

  1. Using only query depth limiting — two queries at the same depth can have wildly different costs if one requests 1000 items and the other requests 10.
  2. Forgetting pagination multipliers — a field that returns a list must account for the page size, not just the base field cost.
  3. Applying the same weight to all fields — a username field costs almost nothing while a scanReport field may require a JOIN across three tables.
  4. Not Caching complexity calculations — computing cost for every request is wasteful. Cache validated queries by their hash.
  5. Ignoring mutation costs — mutations that write to multiple tables or trigger Background Jobs should have higher weights.

Practice Questions

  1. How does cost analysis differ from depth limiting?
  2. Why should list fields have dynamic multipliers?
  3. How do you assign weights to fields with different database costs?
  4. What happens when a query complexity exceeds the limit?
  5. How do you handle complexity for union and interface types?

Challenge: Design a cost analysis system for a GraphQL schema with 50+ types where User.devices costs 5 + 2 per device, Device.scanHistory costs 10 + 1 per scan, and the total budget per user per hour is 10,000 points.

Mini Project

Build a cost analysis middleware that logs every query's complexity score, stores daily usage per API key, and automatically increases complexity limits for premium tier users. Include a GraphQL introspection endpoint that returns field weights.

FAQ

What is a good default complexity limit?

Start at 500-1000 points. Monitor real query patterns for a week, then set limits based on the 95th percentile of actual usage.

Should I charge different costs for authenticated users?

Yes. Authenticated users typically get higher limits (e.g., 2000 points) while anonymous users get lower limits (e.g., 200 points).

How do I handle introspection queries in cost analysis?

Introspection queries can be expensive. Assign high weights to __schema and __type fields, or exclude introspection from normal costing.

Can cost analysis prevent DoS attacks?

Cost analysis is an effective layer against accidental expensive queries and basic DoS. Combine with rate limiting and WAF for comprehensive protection.

Does persisted query bypass cost analysis?

No. Persisted queries should still go through cost analysis. A bad persisted query is still expensive.

What's Next

Learn about rate limiting for GraphQL APIs

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro