Web

GraphQL

Single endpoint, schema-driven, fetch exactly what you need — no over-fetching, no under-fetching

GraphQL is a query language and runtime for APIs developed by Facebook in 2012 (open-sourced 2015). Unlike REST's resource-per-endpoint model, GraphQL exposes a single endpoint and a strongly-typed schema; clients send queries specifying exactly which fields they want (e.g., { user(id: 1) { name email posts { title } } }). The server returns precisely that shape — no over-fetching unused data, no chains of N+1 round-trips. Solves classic REST pain points: aggregation across resources, mobile bandwidth, schema evolution. Performance trap: naive resolvers cause N+1 queries; DataLoader (Facebook 2016) batches them. Used by GitHub (2017 GA), Shopify, Netflix, Twitch. Tooling: Apollo, Relay, GraphQL.js, Hasura.

  • ReleasedFacebook 2015
  • TypeQuery language + runtime
  • EndpointSingle (typically /graphql)
  • SchemaSDL (Schema Definition Language)
  • DataLoaderN+1 mitigation
  • Major adoptersGitHub, Shopify, Netflix

Interactive visualization

Press play, or step through manually. The visualization is yours to drive — try it before reading on.

Open visualization fullscreen ↗

Watch the 60-second explainer

A condensed visual walkthrough — narrated, captioned, under a minute.

Why GraphQL matters

  • Mobile bandwidth. Mobile apps over flaky connections can't afford to download whole resource trees. GraphQL's "fetch only what you need" semantics are the original Facebook motivation — iPhone clients in 2012 were over-fetching Facebook objects badly.
  • GitHub API v4. GitHub's first major adoption (GA 2017) made GraphQL a credible enterprise pattern. Their REST v3 still exists, but GraphQL is what new dashboards and integrations use.
  • Microservice aggregation. Federated GraphQL lets dozens of microservices compose into a single coherent API surface for clients. Netflix uses it across hundreds of services.
  • Backend-for-Frontend (BFF) pattern. A GraphQL layer sits between thin clients and many downstream services, shaping responses per-screen without requiring backend changes per UI iteration.
  • Schema-first development. The SDL becomes a contract between frontend and backend teams. Tools like graphql-codegen generate TypeScript types from the schema, making client code type-safe end-to-end.
  • Introspection. A GraphQL server is self-documenting — clients can ask the server for its schema and discover all available fields. GraphiQL and Apollo Studio expose this beautifully.
  • Real-time via subscriptions. Built-in pub/sub semantics through subscription operations, usually transported over WebSocket. Live dashboards and chat are first-class.

How a GraphQL request works

  1. Client constructs a query. A query string in GraphQL syntax, optionally with named variables, plus an operation name. The client also typically sends an Authorization header.
  2. HTTP POST to /graphql. Body is JSON: { query, variables, operationName }. Persisted-query setups send a hash instead of the full query.
  3. Parse and validate. The server parses the query into an AST, validates it against the schema (do all fields exist? are arguments well-typed?), and returns errors before execution if invalid.
  4. Plan and execute. The runtime walks the AST and calls resolvers. Each field has a resolver function; the runtime calls them top-down, passing parent results as arguments.
  5. Batch and fetch. Resolvers either return immediate values or fetch from a database, REST API, or another service. DataLoader batches identical lookups within a single request tick.
  6. Compose response. The runtime stitches resolver results into a JSON object matching the query shape exactly. Errors are reported in a parallel errors array.
  7. Return. HTTP 200 with the response body. GraphQL deliberately uses 200 even for partial errors — the data and errors arrays carry per-field status.

Schema and type system

  • Object types. type User { id: ID! name: String! email: String posts: [Post!]! } — non-null with !, lists with [].
  • Scalar types. Built-in: Int, Float, String, Boolean, ID. Custom scalars (DateTime, JSON, BigInt) are common.
  • Enum, Interface, Union. Polymorphism via Interface (User implements Node) and Union (SearchResult = User | Post | Comment).
  • Input types. Mutations take input objects: input CreatePostInput { title: String! body: String! }.
  • Directives. @deprecated, @include(if: $cond), @skip, @auth. Schema directives extend the language.
  • Operations. Three top-level types: Query (read), Mutation (write, executed serially), Subscription (push events).

DataLoader and the N+1 problem

  • The bug. Resolver for User.posts issues a query per user. 100 users in the response → 100 round-trips.
  • The fix. Wrap the database call in a DataLoader. DataLoader collects every load() call within the current event-loop tick, calls the user-provided batch function once with all IDs, and resolves each promise from the batch result.
  • Per-request scope. Create a new DataLoader instance per request to avoid leaking cached data across users.
  • Caching. DataLoader memoizes within a request — second load(5) is free.
  • Multi-key loaders. For composite keys, use loader.load({ userId, postType }) with a hashing function.
  • Library variants. The original DataLoader (JavaScript, Facebook 2016) has ports for Python (aiodataloader), Java (java-dataloader), Go (dataloaden), Ruby (graphql-batch).

Federation and stitching

  • Apollo Federation 2. Each subgraph owns part of the schema and may extend types from others via @key, @external, @requires, @provides. The gateway is a router; subgraphs do the real work.
  • Query plan. Gateway parses incoming query, splits into subgraph queries, dispatches in parallel where possible, joins on @key fields.
  • Trade-offs. Strong organizational decoupling vs gateway becomes a critical infra component; any subgraph downtime affects the supergraph.
  • Schema stitching (older). Manually merge schemas at the gateway; less powerful than federation but simpler.
  • Alternatives. GraphQL Mesh, Hasura, WunderGraph, Cosmo Router, GraphQL Hive.

Caching strategies

  • Client cache. Apollo Client and Relay normalize responses by ID and serve subsequent reads from cache. Field-level policies control invalidation.
  • Persisted queries. Pre-register query strings on the server keyed by hash; clients send the hash + variables. Smaller payloads, easier CDN caching, security against arbitrary queries.
  • Automatic persisted queries (APQ). Apollo's runtime version — first request sends full query, subsequent requests send hash; server caches.
  • Server-side response cache. Cache by query hash + variable hash + auth. Apollo's @cacheControl, GraphCDN, Stellate.
  • Database cache. Memcached/Redis behind DataLoader; scope keys carefully to avoid leaking across tenants.

Error handling

  • HTTP 200 by convention. GraphQL returns 200 even for partial errors; the response payload carries the errors array.
  • Errors array. Each error includes message, path, locations, and an extensions object for code/details.
  • Partial responses. If one resolver fails, others can still return data — common pattern: nullable fields with errors attached.
  • Typed errors. Apollo Server lets you define typed error union types in the schema (Result = Success | NotFound | PermissionDenied) for client-side discriminated handling.

Performance and scaling

  • Query depth and complexity limits. Reject queries deeper than N levels or estimated cost above a budget. graphql-cost-analysis, graphql-depth-limit.
  • Rate limiting. Per-token, per-IP, per-operation; GitHub uses point-based rate limiting where each field carries a cost.
  • Field-level metrics. Apollo Studio, Hasura Pro, GraphQL Hive surface per-field latency p50/p95/p99 and resolver hot spots.
  • Compiled queries. graphql-jit and Apollo's experimental compiled query path generate optimized resolver chains, cutting per-query overhead 2-5×.
  • Subscriptions at scale. Move to Redis pub/sub or NATS for multi-instance deployments; long-lived WebSocket connections cost memory.

Common misconceptions

  • "GraphQL is always faster than REST." Depends entirely on resolvers and DataLoader use. A poorly written GraphQL server hammering the database is slower than a tight REST endpoint.
  • "GraphQL has no caching." Apollo client cache, persisted queries with GET, server response caches, and CDN integration via Stellate/GraphCDN all exist. Caching is harder than REST, not impossible.
  • "GraphQL replaces REST entirely." Coexistence is the norm. Many companies expose REST for simple resource access and GraphQL for aggregation, mobile, and partner APIs.
  • "GraphQL is only Facebook tech." The GraphQL Foundation hosts the spec; major contributors include GitHub, Apollo, Hasura, IBM, and Microsoft. The ecosystem is broad.
  • "You need a GraphQL framework." The reference implementation, graphql-js, is the core; frameworks like Apollo Server, Yoga, Mercurius, and Pothos add ergonomics. You can write a server with just graphql-js.
  • "Mutations are RPC." Mutations are typed schema operations that return strongly-typed result objects — usually shaped like the entity they modified, allowing the client cache to update without a refetch.

Frequently asked questions

How is GraphQL different from REST?

REST exposes resources at distinct URLs (GET /users/1, GET /users/1/posts) — clients hit multiple endpoints to assemble a view, often over-fetching fields they don't need. GraphQL exposes one endpoint and a typed schema; the client sends a query like { user(id: 1) { name posts { title } } } and the server returns exactly that shape. Pros for GraphQL: single round-trip, no over-fetching, schema introspection. Pros for REST: HTTP caching, simpler ops, no resolver complexity. Modern apps often use both.

What is the N+1 problem and how does DataLoader fix it?

Naive resolvers fetch each child entity in a separate database query. A query for 100 posts with their authors would issue 1 query for posts plus 100 queries for authors — N+1. DataLoader (Facebook 2016) batches and dedupes these calls within one request tick. It collects all author IDs requested during the resolution, issues one batched SELECT, and returns results to each resolver. N+1 collapses to 1+1. DataLoader is now standard practice in every production GraphQL server.

What is GraphQL Federation?

Apollo Federation (2019) lets multiple subgraphs (separate microservices, each owning part of the schema) compose into a single supergraph. Each subgraph defines types and extends types from other subgraphs via @key directives. A federated gateway plans queries: split incoming queries across subgraphs, gather responses, stitch them. Used by Netflix, Walmart, Atlassian. Alternatives: schema stitching (older), GraphQL Mesh, GraphQL Hive Gateway, and Cosmo Router.

How does GraphQL handle authentication?

GraphQL itself is transport-agnostic; auth is layered on top. Common pattern: client sends a Bearer JWT in the Authorization header (HTTP for queries/mutations, the connection_init payload for subscriptions over WebSocket). The server validates the token in middleware, stashes the user on context, and resolvers consult context.user for field-level checks. Schema directives like @auth or @hasRole codify rules. Tools like graphql-shield and Apollo Server's plugin API make permission rules declarative.

Is GraphQL slower than REST?

It depends. A naive GraphQL server with poor resolvers and no caching will lose to a tight REST endpoint that issues one SQL query. A well-tuned GraphQL server with DataLoader batching, persisted queries, and APQ (automatic persisted queries) can match or beat REST for complex aggregation patterns. The bottleneck is usually database access, not parsing or serialization. CDN caching is harder for GraphQL because most clients POST to a single URL — persisted queries with GET enable CDN caching.

What are GraphQL Subscriptions?

Subscriptions are real-time push events from server to client. The server holds a long-lived connection — usually WebSocket via graphql-ws or Server-Sent Events — and pushes events whenever the subscribed data changes. Clients write subscription { messageAdded(channelId: 5) { id text } } the same way they'd write a query. Backends typically wire subscriptions to a pub/sub layer (Redis, Kafka, Postgres LISTEN/NOTIFY). Live-data dashboards, chat, and collaborative editors use them.