Building a GraphQL Server with Node.js
🏗️ Building a GraphQL Server with Node.js
Choosing a Server Library
Two common options: Apollo Server (batteries-included, the most widely adopted) and graphql-yoga (lighter, built closely on the spec's reference implementation). This chapter walks through Apollo Server, but every concept here — typeDefs, resolvers, context — transfers directly to graphql-yoga or any other spec-compliant server.
Basic Setup
🧳 The Context Function, in Practice
This is exactly where Chapter 5's per-request DataLoader instances get created, and where authentication info will go in Chapter 8 — the context function runs once per incoming request and returns whatever object every resolver's third parameter will see.
Wiring Resolvers to a Real Data Source
The db.users.findById(...)-style pseudocode from earlier chapters becomes a real database client — the same mysql2 patterns from Node.js Course 2:
🔗 Attaching to Express
In a real deployment, GraphQL usually sits inside an existing Express app rather than standing alone — mounted at one path:
A Complete Minimal Server
Built-In Playground
Apollo Server ships a browser-based GraphiQL-style explorer during development — writing and running queries by hand against the live schema, with autocomplete powered by introspection from Chapter 2.
Splitting typeDefs as Schemas Grow
A schema doesn't have to live in one string — larger APIs typically split typeDefs across multiple files by domain (users, posts, comments), merged together at startup.
Where the Routing Actually Lives
REST
Routing is scattered across many app.get/post/put/delete calls, often spread across multiple route files.
GraphQL
One app.use("/graphql", ...) mount point — the schema and resolvers themselves are the real "routing," not Express routes.
💻 Coding Challenges
Challenge 1: Add a Second Loader to Context
Extend the Express-mounted server's context function to also create and return a postsByAuthorLoader (per Chapter 5's batching pattern), alongside the existing userLoader.
Goal: Practice building out a context function that carries multiple DataLoaders at once.
Challenge 2: Mount at a Custom Path With a Health Check
Modify the Express server example so GraphQL is mounted at /api/graphql instead of /graphql, and add a separate plain REST endpoint GET /health returning { status: "ok" }.
Goal: Practice combining a GraphQL endpoint with an ordinary REST route in the same Express app.
Challenge 3: Find the Bug in a Context Function
A developer writes const userLoader = createUserLoader(pool); at the TOP of the server file (outside any function), then references that same userLoader variable inside the context callback instead of calling createUserLoader(pool) there. Explain the bug this reintroduces.
Goal: Practice spotting Chapter 5's gotcha in realistic server-setup code, not just in isolated loader snippets.
This is Chapter 5's gotcha resurfacing in a very concrete place: it's tempting, while wiring up a real server, to create a DataLoader once near the top of the file — outside the context callback — to "avoid recreating it on every request." That's precisely the shared-instance mistake Chapter 5 warned about, just easier to accidentally write here because the context function is only a few lines and the loader creation looks like it could reasonably live outside it. Every DataLoader must be constructed inside the context callback itself, so a genuinely fresh instance — and fresh cache — exists for every single request.
🎯 What's Next
With a real server running, the next chapter covers what happens when things go wrong: Error Handling & Validation — GraphQL's error format compared to HTTP status codes, and custom error types.