Coding Standards

GraphQL Conventions

Foundational

A GraphQL API is one shared schema that many clients depend on. The schema is the contract. Design it with care: clear types, safe nullability, consistent naming, sensible pagination, and limits that stop a single query from overloading the server. This is the concrete conventions reference that goes with the broader API & Contract Design guideline.

GraphQL moves a lot of power to the client. One request can ask for many resources at once and follow links between them. That is useful, but it also means a careless query can be slow, expensive, or unsafe. Design the schema and the resolvers so the common case is fast and the worst case is bounded.

All the security rules still apply: authenticate and authorise every field, validate input at the boundary, never return too much data, and never leak internals (see Authentication & Authorization, Trust Boundaries). Authorisation in GraphQL is per field, not per endpoint, so check it in the resolver, not just at the edge.

Schema design

Errors & responses

Unlike REST, GraphQL does not use HTTP status codes to report application errors. A request that reaches the server returns HTTP 200 even when the query fails, with the problems listed in the response's errors array. This is how the GraphQL specification works and we cannot change it, so clients must check the errors array, not the HTTP status. (For the REST approach, see HTTP Status Codes.)

Performance & pagination

Safety & evolution

A schema, end to end

Unbounded, leaky, N+1 type Query { customers: [Customer!]! } # no paging, no limit
type Customer { ssn: String! orders: [Order!]! } # leaks PII, N+1 on orders
# error body: stack trace and SQL

An unbounded list, sensitive data exposed by default, a field that triggers one query per row, and internal detail leaked on failure. Slow, expensive, and unsafe.

Paged, safe, batched type Query { customers(first: Int! = 20, after: String): CustomerConnection! }
type Customer { id: ID! name: String! orders: OrderConnection! } # batched via DataLoader
# errors: { extensions: { code: "FORBIDDEN" } }, no internals

A capped connection, only fields the caller is allowed to see, batched data loads, and a stable error code with no internal detail. Depth and cost are limited before execution.

Self-review checklist

Why it matters: In GraphQL the schema is the contract and the client controls the query. That power is the point, but it also means a single request can be slow, expensive, or unsafe if the schema and resolvers are not designed for it. Clear types, per-field authorisation, batched loads, paginated lists, and depth and cost limits are what keep a GraphQL API fast, safe, and cheap to change.