GraphQL path · In production · Topic 9 of 12

Query cost, depth limits and introspection

Not started

In REST, the server decides how much work an endpoint does. In GraphQL, the client does: it chooses how deep to nest, how many items each list returns, and how many times to repeat a field under different aliases. Most GraphQL-specific attacks are therefore about demand: queries cheap to send and expensive to answer. The defence is to look at a query **after validation and before execution**, estimate its cost, and refuse it if it is over budget, combined with the usual API controls: authentication, timeouts, rate limits and careful error messages.

Why it matters: The client chooses the shape of the work. Without limits, one short query can ask for millions of objects, and a public schema explorer hands attackers a map. Demand control is what makes a GraphQL endpoint safe to expose.

  1. 1Limit depth and breadth

    Cap how deeply fields may nest, and how many fields, aliases and operations one request may contain.

  2. 2Estimate cost first

    Before execution, add up the objects a query could fetch, multiplying through list sizes. Reject queries over a budget, and rate-limit by cost, not by request count.

  3. 3Trusted documents for your own apps

    If only your clients call the API, register their operations at build time and execute only those hashes.

  4. 4Introspection and errors in production

    Disabling introspection and masking error details are common for private APIs. They reduce what you reveal, but they are not a substitute for limits and authorisation.

1

How it works

The explanation, with examples you can run.

Estimating cost before execution

⌘/Ctrl + Enter
OutputNot run yet

Press Run to see what your code prints, in order.

Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.

Real cost analysers work on the parsed document, use the schema to know which fields are lists, read page sizes from arguments or variables, and let you weight expensive fields (a search, a call to a slow service) more than cheap ones. The same number can drive rate limiting: give each client a cost budget per minute instead of a request count, and report the cost in extensions so client developers can see it.

The other limits

  • Paginate every list and enforce a maximum page size. Without that, cost analysis has nothing to multiply.
  • Depth: cap nesting, with a smaller cap for nested lists. Schemas with cycles (user.friends.friends...) need it most.
  • Breadth and batching: cap fields and aliases per request, and the number of operations if your server accepts batched requests. Aliases can multiply a login mutation into hundreds of password guesses in one request.
  • Timeouts and cancellation at the HTTP and resolver levels, so a query that slips through doesn't run forever.

Trusted documents

For APIs used only by your own apps, the strongest control is an allowlist. At build time, the client's operations are extracted, hashed and registered with the server; at runtime the client sends the hash and the server runs only documents it knows. Arbitrary queries become impossible, and requests get smaller and cacheable as a bonus. Public APIs can't do this, because they can't know their clients' queries in advance, so they rely on cost limits.

Introspection and error detail

Introspection lets anyone download the full schema. For private APIs it is common to disable it in production and keep it in development and for tooling. Similarly, mask internal error messages and stack traces, and disable "did you mean…" field suggestions, which can reveal the schema piece by piece. None of these replace authorisation and limits: a determined client can recover most of a schema from your app's network traffic.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.Which query is the most dangerous to an unprotected server?
    # A{ me { name } }# B{ posts(first: 100) { comments(first: 100) { author { posts(first: 100) { title } } } } }# C{ a: me { name } b: me { name } }
  2. 2.Your API serves only your web and mobile apps. Which control closes off arbitrary queries completely?
  3. 3.Why limit the number of aliases in one request?

0 of 3 right so far. Get all three to finish this topic.