Capstone: Building a Small GraphQL API
🏁 Capstone: Building a Small GraphQL API
User, Post, Comment — with authentication, batched resolvers, and structured errors. Nothing new is introduced here, only assembly.
What's Being Built
- Read posts and their authors/comments in one nested query
- Create and delete posts, restricted to authenticated owners
- Batched, N+1-safe resolvers for every relationship
- Categorized errors the client can act on, not just generic failures
📐 The Schema
🖥️ Server: Everything Combined
Every Field Auth-Protected Individually
User.email is checked at the FIELD, not at whatever query happened to reach it — exactly the fix for Chapter 8's "unprotected path" gotcha, since this same resolver runs whether the User came from a direct query or via Post.author.
What's Simplified Here
No real database, no pagination, no rate limiting — this capstone stays honest about scope, focusing on how the pieces from Chapters 2–8 fit together, not on production hardening.
Was GraphQL Worth It for This API?
Honestly — Yes, Here
Posts/authors/comments is a genuinely nested, multi-shaped read pattern — exactly Chapter 1's under-fetching problem.
But Not Automatically
A flatter API with one client and no nested reads (Chapter 1's webhook example) would gain nothing from this — the decision was never "GraphQL is better," only "it fits this shape."
💻 Coding Challenges
Challenge 1: Add a createComment Mutation
Add a CreateCommentInput { postId: ID!, text: String! }, a createComment mutation returning Comment!, and a resolver requiring authentication before creating the comment.
Goal: Practice extending the capstone's schema and resolvers with a new, consistent operation.
Challenge 2: Verify the Auth Fix Actually Works
Trace the query { posts { author { email } } } against this chapter's resolvers, for a caller who isn't the author, and confirm email comes back null rather than leaking the value — citing exactly which resolver prevents it.
Goal: Practice verifying Chapter 8's gotcha is genuinely closed, not just described.
Challenge 3: Trace a Full Mutation Lifecycle
Narrate, step by step, everything that happens from an authenticated client calling createPost(input: { title: "" }) to the client receiving its response — naming which chapter's mechanism handles each step.
Goal: Practice holding the entire course's material together as one coherent system.
A REST API's tests map cleanly onto its endpoints — test GET /posts, test POST /posts, done. A GraphQL API has exactly one endpoint but effectively unlimited query shapes, and Chapter 8's capstone made this concrete: the SAME User.email resolver behaves differently depending on whether the caller is the owner, an admin, or neither — and it's reachable through multiple different query paths (Query.user, Post.author, Comment.author). Testing this API properly means testing representative query shapes and permission combinations, not just "does the endpoint respond" — a single resolver can be correct in isolation and still leak data through a query shape nobody thought to test.
🎓 Course Complete
This closes out the GraphQL course — from the specific REST problems it solves, through the schema and type system, resolvers and mutations, the N+1 problem and DataLoader, a real Node.js server, structured error handling, authentication and authorization, and finally this capstone tying every piece into one working API.