Part 3: Building a GraphQL API with TypeScript and Apollo Server
From Schema to Running Service
Part 2 designed the schema on paper. Now we implement it.
This part builds a production-style Apollo Server 4 application: Prisma 5 for database access, DataLoader to solve the N+1 query problem, Zod for input validation, and graphql-ws for subscriptions. The code is structured for a microservices context β this is one service among several that will federate in Part 6.
The context object is created per-request. I put database access, DataLoaders, and the authenticated user here.
DataLoaders: Solving the N+1 Problem
The N+1 query problem is the single most important performance issue in GraphQL. Consider:
Without DataLoader, fetching 20 products fires 1 query for products + 20 queries for their categories = 21 queries. DataLoader batches the category lookups into a single WHERE id IN (...) query.
Key points about DataLoader:
Each call to loader.load(key) returns a Promise
DataLoader batches all .load() calls from the same tick of the event loop into a single batch function call
The cache means repeated loads with the same key return the same Promise (per-request cache, not cross-request)
The context factory creates a new set of DataLoaders per request so the cache doesn't leak between requests
Resolvers
Product Resolvers
Category Resolvers
Subscriptions
Subscriptions use a pub/sub mechanism. Apollo Server v4 does not ship a built-in PubSub for production β the in-memory PubSub from graphql-subscriptions is only for development. For production I use Redis pub/sub via graphql-redis-subscriptions.
Publish an event from a mutation:
Merging Resolvers
Zod Input Validation
When Zod throws a ZodError, map it to UserInputError in an error-handling wrapper or in the resolver itself.
Custom Errors
Server Entry Point
What's Next
You now have a running Apollo Server with:
Prisma 5 database access
DataLoaders eliminating N+1 query problems
Cursor-based pagination
Zod input validation
Subscriptions via graphql-ws
Custom error classes with error codes
In Part 4, we consume this API β building a TypeScript client with Apollo Client and generated types, and a Python microservice that calls this GraphQL API using the gql client library.
// src/pubsub.ts
import { PubSub } from 'graphql-subscriptions';
// Development only β replace with graphql-redis-subscriptions in production
export const pubsub = new PubSub();
export const EVENTS = {
PRODUCT_STATUS_CHANGED: 'PRODUCT_STATUS_CHANGED',
STOCK_UPDATED: 'STOCK_UPDATED',
} as const;