GraphQL & gRPC

0/4 in this phase0/45 across the roadmap

📖 Concept

While REST dominates public APIs, two other paradigms are widely used in modern systems: GraphQL (for flexible client-driven queries) and gRPC (for high-performance service-to-service communication).

GraphQL

GraphQL is a query language for APIs developed by Facebook. Instead of the server deciding what data to return (REST), the client specifies exactly what it needs.

REST vs GraphQL

Aspect REST GraphQL
Endpoints Multiple (/users, /posts, /comments) Single (/graphql)
Data fetching Server decides response shape Client specifies exact fields
Over-fetching Returns all fields (wasteful) Returns only requested fields
Under-fetching Need multiple requests for related data Single request gets related data
Versioning URL versioning (/v1, /v2) Schema evolution (deprecate fields)
Caching HTTP caching works naturally Requires specialized caching

When to Use GraphQL

  • Mobile apps: Bandwidth is limited, need minimal payloads
  • Complex UIs: Dashboard that pulls data from many resources
  • Rapid iteration: Frontend team can change data requirements without backend changes
  • Multiple clients: Web, mobile, TV all need different data shapes

gRPC

gRPC is a high-performance RPC (Remote Procedure Call) framework developed by Google. It uses Protocol Buffers (protobuf) for serialization — a binary format that's 3-10x smaller and faster than JSON.

REST vs gRPC

Aspect REST gRPC
Protocol HTTP/1.1 or HTTP/2 HTTP/2 (always)
Data format JSON (text) Protocol Buffers (binary)
Payload size Larger (human-readable) 3-10x smaller
Speed Good Excellent (low latency)
Streaming Limited (SSE) Bidirectional streaming built-in
Browser support Native Requires gRPC-Web proxy
Code generation Manual or OpenAPI Automatic from .proto files

When to Use gRPC

  • Microservice-to-microservice communication (internal APIs)
  • Low latency required: Real-time systems, gaming backends
  • Streaming: Bidirectional streaming for live data
  • Polyglot systems: Auto-generated clients in any language

Rule of thumb: REST for public APIs and web clients → GraphQL when clients need flexible queries → gRPC for internal microservice communication where performance matters.

💻 Code Example

codeTap to expand ⛶
1// ============================================
2// GraphQL & gRPC — Comparison with REST
3// ============================================
4
5// ---------- The Over-Fetching Problem (REST) ----------
6
7// REST: Fetch user profile page data
8// Need: user name, avatar, last 5 posts, and follower count
9
10// Request 1: Get user
11// GET /api/v1/users/123
12// Returns ALL 20 fields: id, name, email, phone, address, bio, avatar, ...
13// You only need: name, avatar ← REST returns everything
14
15// Request 2: Get user's posts
16// GET /api/v1/users/123/posts?limit=5
17// Returns ALL post fields: id, content, media, tags, likes, comments, ...
18// You only need: title, preview ← Over-fetching again
19
20// Request 3: Get follower count
21// GET /api/v1/users/123/followers/count
22// Separate request just for a number ← Under-fetching problem
23
24// Total: 3 HTTP requests, 90% of data downloaded is unused!
25
26// ---------- GraphQL Solution ----------
27
28// ONE request to /graphql with exactly what you need:
29const graphqlQuery = `
30 query UserProfile($userId: ID!) {
31 user(id: $userId) {
32 name
33 avatar
34 followerCount
35 posts(limit: 5) {
36 title
37 preview
38 createdAt
39 }
40 }
41 }
42`;
43
44// Server implementation (Node.js + Apollo):
45const { ApolloServer, gql } = require('apollo-server');
46
47const typeDefs = gql`
48 type User {
49 id: ID!
50 name: String!
51 email: String!
52 avatar: String
53 followerCount: Int!
54 posts(limit: Int = 10): [Post!]!
55 }
56
57 type Post {
58 id: ID!
59 title: String!
60 content: String!
61 preview: String!
62 author: User!
63 createdAt: String!
64 likes: Int!
65 }
66
67 type Query {
68 user(id: ID!): User
69 users(limit: Int, cursor: String): UserConnection!
70 }
71
72 type Mutation {
73 createPost(input: CreatePostInput!): Post!
74 updateUser(id: ID!, input: UpdateUserInput!): User!
75 }
76
77 input CreatePostInput {
78 title: String!
79 content: String!
80 }
81
82 input UpdateUserInput {
83 name: String
84 avatar: String
85 }
86
87 type UserConnection {
88 edges: [User!]!
89 pageInfo: PageInfo!
90 }
91
92 type PageInfo {
93 hasNextPage: Boolean!
94 endCursor: String
95 }
96`;
97
98const resolvers = {
99 Query: {
100 user: async (_, { id }) => {
101 return await db.findUser(id);
102 },
103 users: async (_, { limit = 20, cursor }) => {
104 return await db.listUsers(limit, cursor);
105 },
106 },
107 User: {
108 // Field-level resolver — only runs if client requests this field
109 posts: async (user, { limit }) => {
110 return await db.getPostsByUser(user.id, limit);
111 },
112 followerCount: async (user) => {
113 return await db.countFollowers(user.id);
114 },
115 },
116 Mutation: {
117 createPost: async (_, { input }, context) => {
118 if (!context.user) throw new Error('Not authenticated');
119 return await db.createPost(context.user.id, input);
120 },
121 },
122};
123
124// ---------- gRPC Service Definition (.proto) ----------
125
126// user_service.proto
127const protoDefinition = `
128syntax = "proto3";
129
130package userservice;
131
132// Service definition
133service UserService {
134 // Unary RPC (request-response)
135 rpc GetUser (GetUserRequest) returns (User);
136 rpc CreateUser (CreateUserRequest) returns (User);
137
138 // Server streaming (server sends multiple responses)
139 rpc ListUsers (ListUsersRequest) returns (stream User);
140
141 // Client streaming (client sends multiple requests)
142 rpc BatchCreateUsers (stream CreateUserRequest) returns (BatchResult);
143
144 // Bidirectional streaming
145 rpc Chat (stream ChatMessage) returns (stream ChatMessage);
146}
147
148// Message definitions
149message User {
150 int64 id = 1;
151 string name = 2;
152 string email = 3;
153 string avatar = 4;
154 int64 created_at = 5;
155}
156
157message GetUserRequest {
158 int64 id = 1;
159}
160
161message CreateUserRequest {
162 string name = 1;
163 string email = 2;
164}
165
166message ListUsersRequest {
167 int32 limit = 1;
168 string cursor = 2;
169}
170
171message BatchResult {
172 int32 created = 1;
173 int32 failed = 2;
174}
175
176message ChatMessage {
177 string user_id = 1;
178 string content = 2;
179 int64 timestamp = 3;
180}
181`;
182
183// gRPC Node.js Client usage (auto-generated from .proto):
184const grpc = require('@grpc/grpc-js');
185const protoLoader = require('@grpc/proto-loader');
186
187async function grpcClientExample() {
188 // Load proto file (auto-generates typed client)
189 const packageDef = protoLoader.loadSync('user_service.proto');
190 const userProto = grpc.loadPackageDefinition(packageDef).userservice;
191
192 // Create client
193 const client = new userProto.UserService(
194 'localhost:50051',
195 grpc.credentials.createInsecure()
196 );
197
198 // Unary call (like REST GET)
199 client.GetUser({ id: 123 }, (err, user) => {
200 console.log('gRPC response:', user); // Binary → auto-decoded
201 });
202
203 // Server streaming (like SSE)
204 const stream = client.ListUsers({ limit: 100 });
205 stream.on('data', (user) => console.log('Got user:', user.name));
206 stream.on('end', () => console.log('Stream complete'));
207}
208
209// ---------- When to Use What — Decision Matrix ----------
210
211const decisionMatrix = {
212 'Public API (web/mobile clients)': 'REST or GraphQL',
213 'Internal microservices': 'gRPC',
214 'Mobile app with complex data needs': 'GraphQL',
215 'Simple CRUD API': 'REST',
216 'Real-time streaming between services': 'gRPC (bidirectional streaming)',
217 'Third-party developer API': 'REST (universally understood)',
218 'Browser-to-server real-time': 'WebSocket (not gRPC — browser support limited)',
219};
220
221console.log('API Decision Matrix:', decisionMatrix);

🏋️ Practice Exercise

  1. Migration Scenario: Your REST API has grown to 50+ endpoints and mobile clients are complaining about over-fetching. Design a migration plan from REST to GraphQL. What do you keep? What changes?

  2. GraphQL Schema Design: Design a complete GraphQL schema for an e-commerce application: products, categories, cart, orders, reviews. Include queries, mutations, and proper type relationships.

  3. gRPC vs REST Benchmark: If a REST endpoint returns a 2KB JSON response and the equivalent gRPC returns 600 bytes of protobuf, calculate the bandwidth savings for 1M requests per day.

  4. Choose the Right Protocol: For each scenario decide REST, GraphQL, or gRPC and justify: (a) Public developer API for a payment platform, (b) Internal communication between 15 microservices, (c) Mobile app dashboard showing data from 6 different services, (d) IoT device sending telemetry data.

  5. GraphQL N+1 Problem: Explain the N+1 query problem in GraphQL. If a client queries 20 users with their posts, how many database queries will naively execute? Implement a DataLoader-based solution.

⚠️ Common Mistakes

  • Using gRPC for browser-facing APIs — browsers don't natively support gRPC. You'd need a gRPC-Web proxy (like Envoy), adding infrastructure complexity. Use REST or GraphQL for web clients.

  • Not implementing DataLoader for GraphQL — without batching, a query for 20 users with posts triggers 1 + 20 = 21 database queries (N+1 problem). DataLoader batches these into 2 queries.

  • Choosing GraphQL for simple CRUD APIs — GraphQL adds complexity (schema definition, resolver structure, specialized caching). If your API is simple CRUD, REST is more appropriate.

  • Ignoring gRPC's streaming capabilities — many teams use gRPC like REST (unary calls only). gRPC's real power is in server streaming, client streaming, and bidirectional streaming for real-time data.

💼 Interview Questions

🎤 Mock Interview

Practice a live interview for GraphQL & gRPC