GraphQL Overview
GraphQL is a query language for APIs, created by Facebook (2012, open-sourced 2015). Instead of fixed endpoints returning fixed shapes, clients send a query specifying exactly what they want — and the server returns exactly that.
For comparison with REST/gRPC/SOAP see ../00_api_protocols_comparison.md. This folder is the deep dive.
The defining example
REST — many endpoints, fixed shapes:
GET /api/users/42 → user object
GET /api/users/42/posts → list of posts
GET /api/users/42/followers → list of followers
Three round trips. Each returns more than the screen needs (over-fetching).
GraphQL — one endpoint, client-specified shape:
query {
user(id: 42) {
name
avatar
posts(limit: 5) { title, excerpt }
followers(limit: 10) { name, avatar }
}
}
One round trip. Server returns exactly the requested fields:
{
"data": {
"user": {
"name": "Alice",
"avatar": "https://...",
"posts": [{"title": "...", "excerpt": "..."}],
"followers": [{"name": "...", "avatar": "..."}]
}
}
}
What GraphQL is and isn’t
| GraphQL is | GraphQL isn’t | |
|---|---|---|
| a query language for APIs | a database query language (despite the name) | |
| typed schema | a transport (it’s usually HTTP, but the spec is transport-agnostic) | |
| client-driven shape | always JSON (binary GraphQL exists; rare) | |
| introspectable | “REST replacement” — they solve different problems |
GraphQL is HTTP+JSON+a schema+a query language. The transport is mundane (POST to /graphql). The interesting stuff is the schema and resolver model.
The three operations
| Operation | Purpose |
|---|---|
query |
read data (idempotent, like GET) |
mutation |
write data (POST/PUT/DELETE) |
subscription |
stream data (WebSocket / SSE) |
query GetUser {
user(id: 42) { name email }
}
mutation CreateUser {
createUser(input: {name: "Alice", email: "a@b.com"}) {
id
createdAt
}
}
subscription OnNewMessage {
messageAdded(roomId: "abc") { id, content, author }
}
See 03_queries_mutations_subscriptions.md.
When GraphQL fits
- Many clients with different needs — mobile, web, partner integrations all hitting the same data with different field requirements.
- Heterogeneous screens — one product page needs user+posts+follows; another needs just user+posts.
- Aggregator UIs — combining multiple backend services into one response, avoiding waterfall round-trips from the client.
- Rapid frontend iteration — adding new fields doesn’t need a backend deploy if the schema already has them.
When GraphQL doesn’t fit
- Public-facing APIs with unknown clients — REST’s HTTP caching wins; rate limiting is easier.
- Simple CRUD — GraphQL’s schema + resolver overhead isn’t justified.
- Heavy file uploads/downloads — GraphQL multipart is awkward; REST is fine.
- APIs where each “query” is its own product — Stripe, Twilio, SendGrid all stay REST.
The rule: GraphQL pays off when clients have diverse data needs against the same backend.
The architecture
┌──────────────────────┐
│ Client │ ← sends query string
└──────────────────────┘
↓ HTTP POST /graphql
┌──────────────────────┐
│ GraphQL server │
│ ┌─────────────────┐ │
│ │ Schema (SDL) │ │ ← types, fields
│ ├─────────────────┤ │
│ │ Query parser │ │
│ ├─────────────────┤ │
│ │ Validator │ │ ← against schema
│ ├─────────────────┤ │
│ │ Executor │ │ ← calls resolvers in order
│ └─────────────────┘ │
│ ┌─────────────────┐ │
│ │ Resolvers │ │ ← per-field code
│ └─────────────────┘ │
└──────────────────────┘
↓
┌──────────────────────┐
│ Data sources │ ← DB, REST APIs, gRPC services
└──────────────────────┘
The schema is the contract. The resolvers are the implementation.
Resolvers — one function per field
async def resolve_user(parent, info, id):
return await db.get_user(id)
async def resolve_posts(user, info):
return await db.get_posts_by_author(user.id)
Each field has a resolver. The executor walks the query tree, calling resolvers as it goes. This is where N+1 happens — one query fetches 100 users, then the posts resolver runs 100 times, each hitting the DB.
The fix: DataLoader (batches and caches resolver calls within a single request). See 04_resolvers_n_plus_1_dataloader.md.
The “everything in one endpoint” question
GraphQL apps almost always use:
POST /graphql
Content-Type: application/json
{"query": "...", "variables": {...}, "operationName": "..."}
Same URL for every request. Implications:
- HTTP caching doesn’t work (same URL).
- Rate limiting needs to inspect the query.
- Per-endpoint metrics don’t apply — instrument resolvers individually.
The “always 200” question
GraphQL returns HTTP 200 for “the server received and processed the query” regardless of whether business logic succeeded.
{
"data": {"user": null},
"errors": [
{"message": "User not found", "path": ["user"], "extensions": {"code": "NOT_FOUND"}}
]
}
Partial responses are normal: some fields succeed, others fail. The errors array reports failures; data contains whatever succeeded.
Files in this folder
| # | Topic |
|---|---|
| 02 | Schema and types |
| 03 | Queries, mutations, subscriptions |
| 04 | Resolvers, N+1, DataLoader |
| 05 | Pagination and connections |
| 06 | Errors and versioning |
| 07 | Security |
| 08 | Python libraries |
Common interview confusions
- “GraphQL is a database.” — no. It’s an API query language.
- “GraphQL replaces REST.” — they solve overlapping but different problems. Many teams use both.
- “GraphQL eliminates over-fetching.” — under-fetching, yes. Over-fetching at the DB layer can be worse than REST if resolvers are naive.
- “GraphQL is always faster than REST.” — single-roundtrip wins; per-query CPU often worse. Wash for simple cases; depends on workload.
Interview angle
- “What is GraphQL?” — query language for APIs where the client specifies what fields it wants and the server returns exactly that. Single endpoint, typed schema, three operation types.
- “Why use GraphQL instead of REST?” — clients with heterogeneous needs, aggregator UIs that combine many sources, rapid frontend iteration without backend deploys. Best when many different frontends hit the same backend.
- “Why NOT use GraphQL?” — HTTP caching breaks (single URL), rate limiting needs query inspection, simple CRUD doesn’t justify the overhead, public APIs are easier as REST.
- “What’s a resolver?” — function per field that fetches the value. Server walks the query tree, calling resolvers. Where N+1 problems live — fix with DataLoader.
- “Why does GraphQL always return HTTP 200?” — distinguishes “server received your query” (200) from “query succeeded fully” (look at
errors). Allows partial responses.