02. GraphQL Design — let each client pick exactly what lands on the plate¶
~17 min read. GraphQL shines when many clients want different slices of the same data.
Built on the ELI5 in 00-eli5.md. The menu — where customers choose exactly which dishes they want — becomes a typed schema, flexible queries, and resolvers that fetch only what was asked.
1) Start schema-first so everyone sees the same shape¶
GraphQL begins with a schema. That schema is the contract. It names types, fields, arguments, and legal operations. See. Clients and servers argue less when the schema is explicit.
A small ecommerce schema may start like this:
type Product {
id: ID!
name: String!
pricePaise: Int!
inStock: Boolean!
}
type Query {
product(id: ID!): Product
}
The schema-first habit helps because frontend, backend, and platform teams align early. A React app knows field names. Backend knows resolver responsibilities. Documentation tools can render the menu immediately.
A simple flow looks like this:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ schema.graphql│ ──▶ │ resolvers │ ──▶ │ databases │
└──────────────┘ └──────────────┘ └──────────────┘
▲
└──── clients read the same typed contract
Why not skip the schema and just code quickly? Because unchecked flexibility becomes chaos. One team
may call a field price. Another may expose amount. A third may forget nullability. The schema
forces one shared answer.
Worked example with numbers. Your web app needs 8 product fields. Your Android app needs 5. Your seller dashboard needs 11. With a good schema, all three clients still point at one endpoint. They simply ask for different field sets.
2) Queries, mutations, and subscriptions each solve a different ask¶
GraphQL operations come in three main kinds. Do not mix their jobs casually. Simple, no? Each one maps to a distinct product need.
Queries read data. Mutations change data. Subscriptions stream updates over time.
Example query:
Example mutation:
mutation AddToCart {
addItemToCart(cartId: "91", productId: "302", quantity: 2) {
cartId
itemCount
totalPaise
}
}
Example subscription:
Subscriptions help when clients would otherwise poll every 5 seconds. Suppose 40,000 users track order status tonight. Polling every 5 seconds means 8,000 requests each second. A subscription model can push only real changes instead. That saves pointless traffic.
GraphQL is not "one endpoint means one behavior." The payload still expresses intent clearly. Query names, mutation names, and field selections carry the meaning. Treat them carefully.
3) Resolvers give flexibility, but careless resolvers create the N+1 trap¶
A resolver fetches data for a field. That sounds small. The cost can become huge. See this classic mistake.
Say a query asks for 50 orders, and for each order, its customer name. A naive server may do this:
- 1 query to fetch 50 orders.
- 50 extra queries to fetch 50 customers.
That is the N+1 problem. One parent fetch, then one child fetch per row. Latency rises, and databases work harder for no reason.
Diagram:
Client query
│
▼
Orders resolver ──▶ 1 SQL call for 50 orders
│
▼
Customer resolver ──▶ 50 SQL calls for 50 customer ids
DataLoader fixes this by batching and caching within one request. Instead of 50 customer queries, it
collects customer ids like [12, 17, 19, 12, 21]. Then it issues one batched fetch, for example
SELECT * FROM customers WHERE id IN (12,17,19,21). That turns 51 queries into 2.
Worked example. Without batching, assume each customer lookup takes 4 ms. Fifty lookups cost about 200 ms before overhead. With one batch call taking 12 ms, you save roughly 188 ms. That is visible to users.
Resolvers should stay thin. Business rules belong in services. Resolvers should map arguments, call the right service, and shape the response. When resolvers contain duplicated joins, audit checks, and fallback rules, your GraphQL layer becomes hard to reason about.
4) GraphQL beats REST when clients differ a lot, not when teams want novelty¶
Use GraphQL when clients need many custom read shapes. This happens often with consumer apps, dashboards, and super-app style surfaces. A single menu can serve many diners with different appetites.
GraphQL usually beats REST when:
- Mobile wants tiny payloads on weak networks.
- Web wants nested objects for one screen.
- Many frontend teams share one backend domain.
- Product iterations change field needs every sprint.
REST often stays simpler when:
- The workflow is straightforward CRUD.
- Caching by URL is valuable.
- Public APIs need easy curl-level usability.
- Teams want infrastructure that every tool understands instantly.
Worked comparison. A product details page needs product, merchant, inventory, ratings, and recommended items. REST may need 4 or 5 endpoints. GraphQL can ask in one round trip. If the mobile app only needs 9 fields, it asks for 9 fields. No overfetching. No underfetching dance.
But GraphQL adds operational work too. You need query cost limits. You need resolver observability. You need schema governance. You need to prevent one giant query from touching 12 backends. So what to do? Choose it for real client flexibility, not because one endpoint sounds elegant.
Mini worked example. A homepage card needs only id, name, and pricePaise for 24 products. REST
may return reviews, seller bio, and warehouse fields the card never renders. A GraphQL query can
skip all of that. That is real payload discipline.
One more guardrail. Persisted queries help when you want stable operation names and safer caching. They also reduce abuse from arbitrary giant query text.
For teams with 15 frontend squads, schema review becomes product review as well. A renamed field can break many pages at once. So deprecate first, measure usage, then remove later. See. Flexible queries still need boring discipline. That is the job.
Where this lives in the wild¶
- A Shopify Storefront GraphQL engineer designs typed product and checkout fields so many storefronts fetch only what each page needs.
- A GitHub GraphQL platform engineer exposes repositories, pull requests, and checks through one schema for automation-heavy developer workflows.
- A Netflix client-platform engineer can shape mobile playback metadata queries differently from TV app queries without multiplying REST endpoints.
- An Apollo GraphOS platform engineer builds schema governance and resolver performance tooling for teams running large GraphQL estates.
- A Flipkart consumer experience engineer can tune homepage and product detail payloads per client while staying on one graph contract.
Pause and recall¶
- Why does schema-first design reduce team confusion?
- What is the N+1 problem in one sentence?
- When do subscriptions beat polling clearly?
- Name one case where REST still stays simpler than GraphQL.
Interview Q&A¶
When is GraphQL a better fit than REST?¶
It fits best when different clients need different read shapes from the same domain model. It reduces overfetching and endpoint explosion in such cases. Common wrong answer to avoid: "GraphQL is always better because one endpoint is modern."
How do you explain the N+1 problem to an interviewer?¶
A parent list fetch triggers one extra child fetch per row, so database calls grow linearly with result size. Batching tools like DataLoader collapse many child lookups into one request-scoped fetch. Common wrong answer to avoid: "N+1 means GraphQL itself is slow by design."
Why keep resolvers thin?¶
Thin resolvers preserve separation between transport concerns and business logic. That makes reuse, testing, and performance debugging much easier. Common wrong answer to avoid: "Put all business rules in resolvers because every field is independent."
What new risks appear with GraphQL?¶
Large nested queries, expensive resolver fan-out, and weak schema governance can hurt performance and safety. Teams need depth limits, cost controls, and tracing. Common wrong answer to avoid: "One endpoint means security and performance become simpler automatically."
Apply now (5 min)¶
Exercise. Design a graph for a food delivery order tracking page. Write one query for order summary, one mutation for address change, and one subscription for status updates. Then mark which resolvers would hit cart, payments, and delivery services.
Sketch from memory. Draw one schema box, one resolver box, and one database box. Then trace where the N+1 problem appears if order items each fetch product data separately.
Bridge. GraphQL still rides on readable JSON most of the time. gRPC goes the other direction and sends compact binary contracts for service-to-service speed. → 03-grpc-and-protobuf.md