GraphQL with Apollo Server
📖 Concept
GraphQL is a query language for APIs that lets clients request exactly the data they need — no more, no less. It was created by Facebook in 2015 and is an alternative to REST.
GraphQL vs REST:
| Feature | REST | GraphQL |
|---|---|---|
| Endpoints | Multiple (/users, /posts) |
Single (/graphql) |
| Data fetching | Fixed response shape | Client specifies fields |
| Over-fetching | Common | ❌ Eliminated |
| Under-fetching | Multiple requests needed | ❌ Single query |
| Versioning | URL-based (/v1, /v2) |
Schema evolution |
| Real-time | WebSockets/SSE | Subscriptions (built-in) |
| Caching | HTTP caching easy | More complex |
GraphQL building blocks:
- Schema — defines types, queries, mutations, subscriptions
- Resolvers — functions that fetch data for each field
- Queries — read data
- Mutations — write/modify data
- Subscriptions — real-time updates via WebSockets
When to use GraphQL:
- Mobile apps needing bandwidth efficiency
- Complex data relationships with nested queries
- Multiple frontend clients needing different data shapes
- Rapid API iteration without versioning
When REST is better:
- Simple CRUD APIs
- File uploads
- Caching-heavy applications
- Small teams / simpler architecture
🏠 Real-world analogy: REST is like a fixed menu at a restaurant — you order Dish #3 and get everything on the plate (even items you don't want). GraphQL is like a buffet — you pick exactly what you want from the available options.
💻 Code Example
1// GraphQL with Apollo Server — Complete Example23const { ApolloServer } = require("@apollo/server");4const { startStandaloneServer } = require("@apollo/server/standalone");56// 1. Type definitions (schema)7const typeDefs = `#graphql8 type User {9 id: ID!10 name: String!11 email: String!12 role: Role!13 posts: [Post!]!14 postCount: Int!15 createdAt: String!16 }1718 type Post {19 id: ID!20 title: String!21 content: String22 published: Boolean!23 author: User!24 tags: [String!]!25 createdAt: String!26 }2728 enum Role {29 USER30 ADMIN31 MODERATOR32 }3334 type Query {35 users(role: Role, limit: Int, offset: Int): [User!]!36 user(id: ID!): User37 posts(published: Boolean, search: String, limit: Int): [Post!]!38 post(id: ID!): Post39 }4041 input CreatePostInput {42 title: String!43 content: String44 tags: [String!]45 published: Boolean46 }4748 input UpdatePostInput {49 title: String50 content: String51 tags: [String!]52 published: Boolean53 }5455 type Mutation {56 createPost(input: CreatePostInput!): Post!57 updatePost(id: ID!, input: UpdatePostInput!): Post!58 deletePost(id: ID!): Boolean!59 register(name: String!, email: String!, password: String!): AuthPayload!60 login(email: String!, password: String!): AuthPayload!61 }6263 type AuthPayload {64 token: String!65 user: User!66 }6768 type Query {69 users(role: Role, limit: Int, offset: Int): [User!]!70 user(id: ID!): User71 posts(published: Boolean, search: String, limit: Int): [Post!]!72 post(id: ID!): Post73 me: User74 }75`;7677// Mock data78const users = [79 { id: "1", name: "Alice", email: "alice@example.com", role: "ADMIN", createdAt: new Date().toISOString() },80 { id: "2", name: "Bob", email: "bob@example.com", role: "USER", createdAt: new Date().toISOString() },81];8283const posts = [84 { id: "1", title: "GraphQL Intro", content: "Learn GraphQL...", published: true, authorId: "1", tags: ["graphql"], createdAt: new Date().toISOString() },85 { id: "2", title: "Node.js Tips", content: "Advanced Node...", published: true, authorId: "1", tags: ["nodejs"], createdAt: new Date().toISOString() },86 { id: "3", title: "Draft", content: "WIP...", published: false, authorId: "2", tags: [], createdAt: new Date().toISOString() },87];8889// 2. Resolvers90const resolvers = {91 Query: {92 users: (_, { role, limit = 10, offset = 0 }) => {93 let result = [...users];94 if (role) result = result.filter((u) => u.role === role);95 return result.slice(offset, offset + limit);96 },97 user: (_, { id }) => users.find((u) => u.id === id),98 posts: (_, { published, search, limit = 10 }) => {99 let result = [...posts];100 if (published !== undefined) result = result.filter((p) => p.published === published);101 if (search) {102 const s = search.toLowerCase();103 result = result.filter((p) => p.title.toLowerCase().includes(s));104 }105 return result.slice(0, limit);106 },107 post: (_, { id }) => posts.find((p) => p.id === id),108 },109110 Mutation: {111 createPost: (_, { input }, context) => {112 const post = {113 id: String(posts.length + 1),114 ...input,115 published: input.published || false,116 tags: input.tags || [],117 authorId: "1", // From auth context118 createdAt: new Date().toISOString(),119 };120 posts.push(post);121 return post;122 },123 updatePost: (_, { id, input }) => {124 const index = posts.findIndex((p) => p.id === id);125 if (index === -1) throw new Error("Post not found");126 posts[index] = { ...posts[index], ...input };127 return posts[index];128 },129 deletePost: (_, { id }) => {130 const index = posts.findIndex((p) => p.id === id);131 if (index === -1) throw new Error("Post not found");132 posts.splice(index, 1);133 return true;134 },135 },136137 // Field resolvers (for relationships)138 User: {139 posts: (user) => posts.filter((p) => p.authorId === user.id),140 postCount: (user) => posts.filter((p) => p.authorId === user.id).length,141 },142143 Post: {144 author: (post) => users.find((u) => u.id === post.authorId),145 },146};147148// 3. Create and start server149async function startServer() {150 const server = new ApolloServer({ typeDefs, resolvers });151 const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });152 console.log(`GraphQL server at ${url}`);153}154155startServer();156157// 4. Example queries (for GraphQL playground)158const exampleQueries = `159# Get users with their posts160query {161 users {162 id163 name164 posts {165 title166 published167 }168 postCount169 }170}171172# Create a post173mutation {174 createPost(input: {175 title: "New Post"176 content: "Content here"177 tags: ["nodejs"]178 published: true179 }) {180 id181 title182 author { name }183 }184}185186# Search posts187query {188 posts(search: "graphql", published: true) {189 title190 author { name email }191 tags192 }193}194`;
🏋️ Practice Exercise
Exercises:
- Build a GraphQL API with Users, Posts, and Comments — include nested queries for relationships
- Implement mutations for CRUD operations with input validation
- Add authentication context — protect mutations with auth middleware
- Implement the DataLoader pattern to solve the N+1 query problem
- Add cursor-based pagination using the Relay Connection spec
- Compare the same feature implemented in REST vs GraphQL — measure request count and payload size
⚠️ Common Mistakes
N+1 query problem — fetching a list of users and then querying posts for EACH user; use DataLoader for batching
Not limiting query depth — malicious queries can nest deeply and DOS your server; set max depth and complexity limits
Exposing sensitive fields — unlike REST where you control the response shape, GraphQL lets clients query any field; use field-level authorization
Not handling errors properly — GraphQL returns 200 even for errors; use the
errorsarray in the response, not HTTP status codesBuilding GraphQL on top of REST APIs (REST-to-GraphQL proxy) without addressing N+1 — this creates worse performance than either alone
💼 Interview Questions
🎤 Mock Interview
Practice a live interview for GraphQL with Apollo Server