GraphQL & gRPC
📖 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
1// ============================================2// GraphQL & gRPC — Comparison with REST3// ============================================45// ---------- The Over-Fetching Problem (REST) ----------67// REST: Fetch user profile page data8// Need: user name, avatar, last 5 posts, and follower count910// Request 1: Get user11// GET /api/v1/users/12312// Returns ALL 20 fields: id, name, email, phone, address, bio, avatar, ...13// You only need: name, avatar ← REST returns everything1415// Request 2: Get user's posts16// GET /api/v1/users/123/posts?limit=517// Returns ALL post fields: id, content, media, tags, likes, comments, ...18// You only need: title, preview ← Over-fetching again1920// Request 3: Get follower count21// GET /api/v1/users/123/followers/count22// Separate request just for a number ← Under-fetching problem2324// Total: 3 HTTP requests, 90% of data downloaded is unused!2526// ---------- GraphQL Solution ----------2728// ONE request to /graphql with exactly what you need:29const graphqlQuery = `30 query UserProfile($userId: ID!) {31 user(id: $userId) {32 name33 avatar34 followerCount35 posts(limit: 5) {36 title37 preview38 createdAt39 }40 }41 }42`;4344// Server implementation (Node.js + Apollo):45const { ApolloServer, gql } = require('apollo-server');4647const typeDefs = gql`48 type User {49 id: ID!50 name: String!51 email: String!52 avatar: String53 followerCount: Int!54 posts(limit: Int = 10): [Post!]!55 }5657 type Post {58 id: ID!59 title: String!60 content: String!61 preview: String!62 author: User!63 createdAt: String!64 likes: Int!65 }6667 type Query {68 user(id: ID!): User69 users(limit: Int, cursor: String): UserConnection!70 }7172 type Mutation {73 createPost(input: CreatePostInput!): Post!74 updateUser(id: ID!, input: UpdateUserInput!): User!75 }7677 input CreatePostInput {78 title: String!79 content: String!80 }8182 input UpdateUserInput {83 name: String84 avatar: String85 }8687 type UserConnection {88 edges: [User!]!89 pageInfo: PageInfo!90 }9192 type PageInfo {93 hasNextPage: Boolean!94 endCursor: String95 }96`;9798const 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 field109 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};123124// ---------- gRPC Service Definition (.proto) ----------125126// user_service.proto127const protoDefinition = `128syntax = "proto3";129130package userservice;131132// Service definition133service UserService {134 // Unary RPC (request-response)135 rpc GetUser (GetUserRequest) returns (User);136 rpc CreateUser (CreateUserRequest) returns (User);137138 // Server streaming (server sends multiple responses)139 rpc ListUsers (ListUsersRequest) returns (stream User);140141 // Client streaming (client sends multiple requests)142 rpc BatchCreateUsers (stream CreateUserRequest) returns (BatchResult);143144 // Bidirectional streaming145 rpc Chat (stream ChatMessage) returns (stream ChatMessage);146}147148// Message definitions149message User {150 int64 id = 1;151 string name = 2;152 string email = 3;153 string avatar = 4;154 int64 created_at = 5;155}156157message GetUserRequest {158 int64 id = 1;159}160161message CreateUserRequest {162 string name = 1;163 string email = 2;164}165166message ListUsersRequest {167 int32 limit = 1;168 string cursor = 2;169}170171message BatchResult {172 int32 created = 1;173 int32 failed = 2;174}175176message ChatMessage {177 string user_id = 1;178 string content = 2;179 int64 timestamp = 3;180}181`;182183// gRPC Node.js Client usage (auto-generated from .proto):184const grpc = require('@grpc/grpc-js');185const protoLoader = require('@grpc/proto-loader');186187async 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;191192 // Create client193 const client = new userProto.UserService(194 'localhost:50051',195 grpc.credentials.createInsecure()196 );197198 // Unary call (like REST GET)199 client.GetUser({ id: 123 }, (err, user) => {200 console.log('gRPC response:', user); // Binary → auto-decoded201 });202203 // 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}208209// ---------- When to Use What — Decision Matrix ----------210211const 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};220221console.log('API Decision Matrix:', decisionMatrix);
🏋️ Practice Exercise
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?
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.
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.
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.
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