GraphQL was created at Facebook to solve a specific problem: mobile clients on slow networks needed varied subsets of data, and REST forced them to either over-fetch or make many round trips. The solution was a typed query language and a single endpoint that lets each client describe exactly what it wants.
The Shape of a GraphQL API
One endpoint, one schema, three operation types:
- Query — read data.
- Mutation — change data.
- Subscription — receive real-time updates over WebSockets.
The Schema
type Order {
id: ID!
status: OrderStatus!
amount: Int!
currency: String!
customer: Customer!
lineItems: [LineItem!]!
createdAt: DateTime!
}
enum OrderStatus { OPEN PAID CANCELLED }
type Customer {
id: ID!
email: String!
orders(limit: Int = 25): [Order!]!
}
type Query {
order(id: ID!): Order
orders(status: OrderStatus, limit: Int = 25, cursor: String): OrderConnection!
}
type Mutation {
createOrder(input: CreateOrderInput!): Order!
cancelOrder(id: ID!): Order!
}
!means non-null.[Type!]!means a non-null list of non-null items.- Enums and input types are first-class.
A Client Query
query GetMyOrders {
orders(status: OPEN) {
edges {
node {
id
amount
customer { email }
}
}
}
}
The response mirrors the query exactly:
{
"data": {
"orders": {
"edges": [
{ "node": { "id": "ord_1", "amount": 4999, "customer": { "email": "a@b.com" } } }
]
}
}
}
Resolvers
Each field in the schema has a resolver function on the server:
const resolvers = {
Query: {
order: (_, { id }, ctx) => ctx.db.orders.byId(id),
orders: (_, args, ctx) => ctx.db.orders.list(args),
},
Order: {
customer: (order, _, ctx) => ctx.db.customers.byId(order.customerId),
lineItems: (order, _, ctx) => ctx.db.lineItems.byOrder(order.id),
},
Mutation: {
createOrder: (_, { input }, ctx) => ctx.svc.createOrder(input, ctx.user),
},
};
The runtime walks the query and calls resolvers; resolvers return data; data flows back into the JSON response.
The N+1 Problem
If a client asks for 100 orders and each order resolves customer with a separate database call, that is 101 queries. Solution: DataLoader — batch and cache resolver lookups within a single request.
const customerLoader = new DataLoader(async (ids: string[]) => {
const rows = await db.customers.byIds(ids);
return ids.map(id => rows.find(r => r.id === id));
});
// in resolver:
customer: (order, _, ctx) => ctx.loaders.customer.load(order.customerId)
DataLoader is part of every serious GraphQL server.
Pagination
The community-standard pattern is connections with cursors:
type OrderConnection {
edges: [OrderEdge!]!
pageInfo: PageInfo!
}
type OrderEdge {
node: Order!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
endCursor: String
}
Verbose, but it forces every list to be paginated by construction.
Errors
GraphQL responses can have both data and errors at once — partial success is a first-class concept:
{
"data": { "order": null },
"errors": [
{ "message": "Not found", "path": ["order"], "extensions": { "code": "NOT_FOUND" } }
]
}
Status codes are usually 200 even when errors occur. Clients must check errors.
Subscriptions
Real-time push over WebSockets:
subscription {
orderUpdated(customerId: "cus_42") {
id
status
}
}
Useful for live dashboards, chat, notifications. Adds significant operational complexity (connection state, scaling).
Tooling You Will Use
- Servers: Apollo Server, GraphQL Yoga, Mercurius, Hasura, PostGraphile.
- Clients: Apollo Client, urql, Relay (Meta).
- Codegen: GraphQL Code Generator — types from your schema, autocompleted everywhere.
- Federation: Apollo Federation / GraphQL Mesh — compose multiple subgraphs into one.
- Introspection: query the schema itself; powers playgrounds like Apollo Sandbox.
Strengths
- One round trip for data spread across resources.
- No over-fetching; clients pick fields.
- Strongly typed end to end.
- Self-documenting via introspection.
Costs
- HTTP caching is harder (one URL, varying queries).
- N+1 must be solved explicitly.
- Query complexity must be limited or attackers will craft expensive queries.
- Server-side authorisation must be enforced per field, not per endpoint.
Cert Mapping
| Cert | GraphQL scope |
|---|---|
| AWS SAA | AWS AppSync (managed GraphQL) |
| AWS Data Engineer | API exposure of curated data products |
The next lesson directly compares REST and GraphQL so you can pick well.