GraphQL with Apollo Server

0/3 in this phase0/48 across the roadmap

📖 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:

  1. Schema — defines types, queries, mutations, subscriptions
  2. Resolvers — functions that fetch data for each field
  3. Queries — read data
  4. Mutations — write/modify data
  5. 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

codeTap to expand ⛶
1// GraphQL with Apollo Server — Complete Example
2
3const { ApolloServer } = require("@apollo/server");
4const { startStandaloneServer } = require("@apollo/server/standalone");
5
6// 1. Type definitions (schema)
7const typeDefs = `#graphql
8 type User {
9 id: ID!
10 name: String!
11 email: String!
12 role: Role!
13 posts: [Post!]!
14 postCount: Int!
15 createdAt: String!
16 }
17
18 type Post {
19 id: ID!
20 title: String!
21 content: String
22 published: Boolean!
23 author: User!
24 tags: [String!]!
25 createdAt: String!
26 }
27
28 enum Role {
29 USER
30 ADMIN
31 MODERATOR
32 }
33
34 type Query {
35 users(role: Role, limit: Int, offset: Int): [User!]!
36 user(id: ID!): User
37 posts(published: Boolean, search: String, limit: Int): [Post!]!
38 post(id: ID!): Post
39 }
40
41 input CreatePostInput {
42 title: String!
43 content: String
44 tags: [String!]
45 published: Boolean
46 }
47
48 input UpdatePostInput {
49 title: String
50 content: String
51 tags: [String!]
52 published: Boolean
53 }
54
55 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 }
62
63 type AuthPayload {
64 token: String!
65 user: User!
66 }
67
68 type Query {
69 users(role: Role, limit: Int, offset: Int): [User!]!
70 user(id: ID!): User
71 posts(published: Boolean, search: String, limit: Int): [Post!]!
72 post(id: ID!): Post
73 me: User
74 }
75`;
76
77// Mock data
78const 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];
82
83const 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];
88
89// 2. Resolvers
90const 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 },
109
110 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 context
118 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 },
136
137 // 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 },
142
143 Post: {
144 author: (post) => users.find((u) => u.id === post.authorId),
145 },
146};
147
148// 3. Create and start server
149async 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}
154
155startServer();
156
157// 4. Example queries (for GraphQL playground)
158const exampleQueries = `
159# Get users with their posts
160query {
161 users {
162 id
163 name
164 posts {
165 title
166 published
167 }
168 postCount
169 }
170}
171
172# Create a post
173mutation {
174 createPost(input: {
175 title: "New Post"
176 content: "Content here"
177 tags: ["nodejs"]
178 published: true
179 }) {
180 id
181 title
182 author { name }
183 }
184}
185
186# Search posts
187query {
188 posts(search: "graphql", published: true) {
189 title
190 author { name email }
191 tags
192 }
193}
194`;

🏋️ Practice Exercise

Exercises:

  1. Build a GraphQL API with Users, Posts, and Comments — include nested queries for relationships
  2. Implement mutations for CRUD operations with input validation
  3. Add authentication context — protect mutations with auth middleware
  4. Implement the DataLoader pattern to solve the N+1 query problem
  5. Add cursor-based pagination using the Relay Connection spec
  6. 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 errors array in the response, not HTTP status codes

  • Building 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