BackendGraphQLNode.jsApollo ServerAPIBackend

GraphQL with Node.js and Apollo Server: Complete Tutorial

Build a production GraphQL API with Node.js and Apollo Server — schema design, resolvers, authentication, and N+1 query optimization.

Abdur Razzak

Abdur Razzak

Full-Stack Web Developer

March 5, 2025 11 min read

Why GraphQL Over REST?

GraphQL lets clients request exactly the data they need — no more, no less. Unlike REST where the server defines the response shape, GraphQL clients specify their own queries. This eliminates over-fetching (getting unused fields) and under-fetching (needing multiple requests for related data). For complex applications with many interconnected data types, GraphQL's flexibility is a significant advantage over REST.

Setting Up Apollo Server with Node.js

Install @apollo/server and graphql. Create an ApolloServer instance with your typeDefs (schema) and resolvers. In Node.js with Express, use expressMiddleware from @apollo/server/express4 to mount Apollo as an Express middleware. This gives you all of Express's middleware ecosystem (auth, logging, rate limiting) while using Apollo for GraphQL execution.

Designing Your GraphQL Schema

Your schema defines the types, queries, and mutations. Use the Schema Definition Language (SDL): define types with their fields, Query type for read operations, and Mutation type for writes. Relationships between types are expressed directly in the schema — a User type can have a posts field of type [Post!]!. Design your schema around what your clients need, not around your database structure.

Resolvers and Data Sources

Resolvers are functions that return the data for each field in your schema. The resolver for Query.posts fetches all posts from your database. The resolver for Post.author receives the parent Post object and fetches the related User. Context is passed to every resolver — put your authenticated user, database connection, and data loaders in context.

Solving the N+1 Problem with DataLoader

The N+1 problem occurs when fetching a list of N posts and then making N separate database queries for each post's author. DataLoader solves this by batching individual load calls into a single batch request. Instead of N queries, DataLoader makes one query for all requested user IDs. This optimization is critical for production GraphQL performance — without it, a simple query can trigger hundreds of database calls.

Authentication in GraphQL

Verify JWTs in Apollo's context function, which runs before every resolver. Extract the token from the Authorization header, verify it, and attach the decoded user to context. In resolvers that require authentication, check if context.user exists and throw an AuthenticationError if not. For field-level authorization, check permissions within the specific resolver rather than at the schema level.

Share this article

All posts
#GraphQL#Node.js#Apollo Server#API#Backend
Abdur Razzak — Full Stack Web Developer

Let's Connect

Follow My Developer Journey