GraphQL
A REST API is like ordering from a fixed menu where every dish comes with a preset side; you get what the chef decided, even if you only wanted the steak. GraphQL is like building your own plate: you tell the kitchen exactly which ingredients you want, in exactly the portions you need, and everything arrives in one trip. You write the query; the server fulfills it precisely, nothing more.
GraphQL is a query language for APIs and a server-side runtime for executing those queries, developed by Facebook in 2012 and open-sourced in 2015. Unlike REST, where the server defines fixed resource shapes at fixed URLs, GraphQL exposes a single endpoint and lets clients describe the exact shape of the data they need.
Core concepts:
Schema Definition Language (SDL): the contract between client and server.
type User {
id: ID!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
author: User!
}
type Query {
user(id: ID!): User
posts(limit: Int = 10): [Post!]!
}
type Mutation {
createPost(title: String!, authorId: ID!): Post!
}
type Subscription {
newPost: Post!
}Operation types:
| Type | Description | Analogy |
|---|---|---|
Query | Read data | HTTP GET |
Mutation | Write or modify data | HTTP POST/PUT/DELETE |
Subscription | Real-time event stream via WebSocket | SSE / WebSocket |
Resolvers: server-side functions that fulfill each field in the schema. Each field maps to a resolver; resolvers compose to build the response tree.
The N+1 problem: a common performance trap where fetching a list of users triggers a separate database query per user to fetch related posts. Solved by DataLoader (batching and caching resolver calls within a single request).
Introspection: clients can query the schema itself (__schema, __type) to discover available types and fields, enabling tooling like GraphiQL and Apollo Explorer.
Persisted queries: hash a query string and send only the hash in production, reducing payload size and enabling server-side query allowlisting.
GraphQL queries, mutations, and schema introspection
# Query: request only the fields the client needs
query GetUserWithPosts($userId: ID!) {
user(id: $userId) {
email
posts {
title
}
}
}
# Mutation: create a new resource
mutation CreatePost($title: String!, $authorId: ID!) {
createPost(title: $title, authorId: $authorId) {
id
title
}
}
# Subscription: receive real-time updates
subscription OnNewPost {
newPost {
id
title
author { email }
}
}
# Introspection: discover the schema
query IntrospectTypes {
__schema {
types {
name
kind
}
}
}# Send a GraphQL query via curl
$ curl -X POST https://api.example.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"query": "query { user(id: \"123\") { email posts { title } } }"
}'
# GitHub GraphQL API v4: get repo details
$ curl -X POST https://api.github.com/graphql \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-d '{
"query": "{ repository(owner: \"torvalds\", name: \"linux\") { stargazerCount forkCount } }"
}' GitHub (API v4), Shopify (Storefront and Admin APIs), Twitter, and Hasura run GraphQL at scale. Apollo GraphQL and The Guild’s tools dominate the ecosystem. The primary real-world tension is that GraphQL’s flexibility shifts complexity from server to client: the server must optimize queries it did not design for, and unlimited query depth can exhaust database connections. Production GraphQL requires query depth limiting, query cost analysis (limiting expensive joins), and DataLoader to prevent N+1 queries. REST remains the right tool for simple CRUD services, public APIs with caching requirements (REST responses cache naturally at the HTTP layer; GraphQL POST requests do not), and cases where the API consumer is external and unpredictable. GraphQL excels for frontend teams consuming multiple backend services (replacing multiple REST calls with one query) and for mobile apps that need to minimize data over expensive connections.