GraphQL in Production · Capstone
Capstone: Design a Marketplace Graph
Design the schema, pagination and mutations for a two-sided marketplace.
Scenario
A marketplace connects sellers and buyers. The web app, iOS and Android apps need: product search with facets, product pages (seller info, reviews, related products), a cart and checkout, and a seller dashboard (orders, payouts). Data lives in four services: catalog, orders, payments and users.
Requirements
- Schema for products, sellers, reviews, cart, orders
- Paginated lists everywhere
- Checkout with explicit business outcomes
- Seller-only fields hidden from buyers
Constraints
- Product page p95 < 400 ms
- Mobile apps can't be force-updated for 6 months
- Four backend services owned by different teams
Deliverables
- Sketch the core types, queries and the checkout mutation.
- How will the server avoid N+1 queries and protect itself from expensive queries?
- Organize ownership across four teams and plan schema evolution for old mobile apps.
Hints
- List every client screen and the fields it needs before writing types.
- Unions must be extended carefully: old clients need a default branch.
Draft each deliverable below before revealing the model answer and rubric.
Further reading: GraphQL best practices
Deliverables
Sketch the core types, queries and the checkout mutation.
Model answer
interface Node { id: ID! }
type Product implements Node { id: ID! title: String! price: Money! seller: Seller! reviews(first: Int = 10, after: String): ReviewConnection! related(first: Int = 4): [Product!]! }
type Seller implements Node { id: ID! name: String! rating: Float payouts(first: Int = 20, after: String): PayoutConnection @auth(requires: SELLER_OWNER) }
type Query { node(id: ID!): Node search(query: String!, filters: SearchFilters, first: Int = 24, after: String): ProductSearchResult! viewer: User }
union CheckoutResult = CheckoutSuccess | PaymentDeclined | OutOfStock | PriceChanged
type Mutation { checkout(input: CheckoutInput!): CheckoutResult! addCartItem(input: AddCartItemInput!): AddCartItemPayload! }
Rubric
Strong answers: Node interface + global IDs, connections with capped first, Money scalar/type, checkout as a union of explicit outcomes, seller-only fields guarded. Misses: generic updateCart(JSON), unbounded lists, payouts exposed without authorization.
How will the server avoid N+1 queries and protect itself from expensive queries?
Model answer
- Per-request DataLoaders for sellers, reviews, products by id (batched across services)
- Max depth 8, cost analysis (list sizes from first/last), max first = 100, alias limits, timeouts
- Trusted documents for web/iOS/Android; public partner access (if any) via REST
- Cost-based rate limits per client
Rubric
Look for batching per request, cost budgets tied to pagination args, and trusted documents for first-party clients.
Organize ownership across four teams and plan schema evolution for old mobile apps.
Model answer
- Federation: catalog, orders, payments, users subgraphs; Product/Seller/Order as entities with @key(id); router composes; composition + usage checks in CI
- Evolution: additive changes only, @deprecated with dates, registry usage per client version, remove only when usage is zero; clients handle unknown enum values and union members (
__typenamedefault branch)
Rubric
Strong answers connect team boundaries to subgraphs and use real usage data before removing fields. Misses: versioned endpoints (/graphql/v2), breaking changes without usage checks.
Open in the app to mark lessons complete, take notes and track your progress.