Choose the right communication contract before you write a single line of code
Choose the right communication contract before you write a single line of code
Lesson outline
In 2014, Netflix was serving 40% of peak North American internet traffic through a single monolithic API. When they broke it apart, they had to choose: REST, the battle-tested standard? Or something new? They chose a custom solution that eventually influenced GraphQL.
The Protocol Trap
Changing your API protocol after clients are in production costs 6–18 months of migration work. At Uber in 2016, switching internal services from REST to gRPC took 2 years and 3 full engineering teams.
The three dominant protocols each solve a different fundamental problem:
| Protocol | Solves | Best For | Avoid When |
|---|---|---|---|
| REST | Resource exposure over HTTP | Public APIs, simple CRUD, mobile apps | High-throughput internal services |
| GraphQL | Over/under-fetching | Complex UIs needing flexible queries | Simple APIs, non-graph data models |
| gRPC | High-performance RPC | Internal microservices, streaming | Public-facing APIs, browser clients |
REST (Representational State Transfer) is not a protocol — it's an architectural style. Roy Fielding defined 6 constraints in his 2000 dissertation. Most "REST APIs" violate at least 3 of them.
The 6 REST Constraints (and which ones matter at FAANG)
Richardson Maturity Model
Level 0: RPC-style (POST /getUserData). Level 1: Resources (/users/123). Level 2: HTTP verbs + status codes. Level 3: HATEOAS links. Most production APIs are Level 2. Level 3 is theoretical.
1// REST: Resource-oriented design2// ✅ Good REST design — nouns, proper verbs, status codes3import express from 'express';45const router = express.Router();67// GET /users/:id — fetch single resource8router.get('/users/:id', async (req, res) => {9const user = await db.users.findById(req.params.id);Always return 404 for missing resources — not 200 with null body10if (!user) return res.status(404).json({ error: 'User not found' });11return res.status(200).json(user);12});1314// POST /users — create resource (returns 201 Created)15router.post('/users', async (req, res) => {201 Created signals to clients they can fetch the new resource16const user = await db.users.create(req.body);17return res.status(201).json(user); // 201, not 20018});1920// PATCH /users/:id — partial update (not PUT which replaces)PATCH for partial update; PUT replaces the entire resource21router.patch('/users/:id', async (req, res) => {22const user = await db.users.update(req.params.id, req.body);23return res.status(200).json(user);24});25204 No Content is idiomatic for successful DELETE26// DELETE /users/:id — idempotent deletion27router.delete('/users/:id', async (req, res) => {28await db.users.delete(req.params.id);29return res.status(204).send(); // 204 No Content30});3132// ❌ Common REST mistakes33// POST /getUser — verb in URL34// GET /users/delete/123 — action in URL35// DELETE /users with 200 — wrong status code
Facebook built GraphQL in 2012 because their mobile News Feed was making 6+ REST calls on startup, getting 10× more data than needed. GraphQL solved both problems simultaneously.
How a GraphQL Query Executes (what happens under the hood)
01
Client sends a query document to POST /graphql — a single endpoint
02
Server parses the query into an AST (abstract syntax tree)
03
Resolver functions execute for each field in the selection set
04
DataLoader batches N+1 database calls into single queries
05
Response returns only the exact fields requested — no more, no less
Client sends a query document to POST /graphql — a single endpoint
Server parses the query into an AST (abstract syntax tree)
Resolver functions execute for each field in the selection set
DataLoader batches N+1 database calls into single queries
Response returns only the exact fields requested — no more, no less
The N+1 Problem — GraphQL's Achilles Heel
Without DataLoader, querying 100 users and their posts makes 101 DB calls (1 for users + 1 for each user's posts). Always use DataLoader or a query batching library in production.
| Feature | REST | GraphQL |
|---|---|---|
| Endpoints | Many (/users, /posts, /comments) | One (/graphql) |
| Response shape | Fixed by server | Defined by client |
| Type system | Optional (OpenAPI) | Built-in (mandatory) |
| Real-time | Polling or SSE | Subscriptions (WebSocket) |
| Caching | HTTP cache works perfectly | Complex (POST by default) |
| Learning curve | Low | High (schema, resolvers, N+1) |
1// GraphQL: Schema-first design with resolvers2import { makeExecutableSchema } from '@graphql-tools/schema';3import DataLoader from 'dataloader';45// 1. Define the schema (this is your API contract)6const typeDefs = `Schema is the contract. Change it carefully — breaking changes break all clients7type User {8id: ID!9name: String!10email: String!11posts: [Post!]! # relationship resolved lazily12}1314type Post {15id: ID!16title: String!17author: User!18}1920type Query {21user(id: ID!): User22users(limit: Int = 20): [User!]!23}2425type Mutation {26createPost(title: String!, authorId: ID!): Post!27}28`;DataLoader batches all userLoader.load() calls within one tick into a single DB query2930// 2. DataLoader prevents N+1 queries31const userLoader = new DataLoader(async (userIds: readonly string[]) => {32const users = await db.users.findMany({ where: { id: { in: [...userIds] } } });33return userIds.map(id => users.find(u => u.id === id) ?? null);34});3536// 3. Resolvers — field-level functions37const resolvers = {38Query: {39user: (_: unknown, { id }: { id: string }) => db.users.findById(id),40users: (_: unknown, { limit }: { limit: number }) => db.users.findMany({ limit }),41},42User: {Without DataLoader here, 100 posts = 100 DB calls. With it = 1 batch query43// Called once per User, but DataLoader batches the DB calls44posts: (user: User) => db.posts.findByAuthorId(user.id),45},46Post: {47author: (post: Post) => userLoader.load(post.authorId), // batched!48},49};
Google uses gRPC internally for billions of requests per day. It's built on HTTP/2 + Protocol Buffers, giving it 5–10× better performance than REST+JSON for internal service communication.
Why gRPC Wins Internally at FAANG
1// 1. Define the contract in .proto file2syntax = "proto3";34package user;56service UserService {7rpc GetUser (GetUserRequest) returns (UserResponse);8rpc ListUsers (ListUsersRequest) returns (stream UserResponse); // server streamingstream keyword = server-side streaming. Client gets results as they arrive9rpc WatchUsers (stream UserFilter) returns (stream UserResponse); // bidirectionalBidirectional streaming — both sides send and receive independently10}1112message GetUserRequest {13string id = 1;14}1516message UserResponse {17string id = 1;18string name = 2;int64 for timestamps avoids timezone bugs. Proto3 has no Date type19string email = 3;20int64 created_at = 4; // epoch millis — no timezone ambiguity21}2223// Generated TypeScript client (from protoc):24// const client = new UserServiceClient('user-service:50051', credentials);25// const user = await client.getUser({ id: '123' });26// → Makes binary HTTP/2 call. No JSON parsing. ~10μs latency vs ~1ms REST.
API Protocol Decision Tree
The Netflix Pattern (and why it works)
Public API Gateway: REST. Internal services: gRPC. BFF (Backend for Frontend): GraphQL per product team. This is the pattern used at Netflix, Airbnb, and many large-scale companies. You don't have to pick one for everything.
| Company | Public API | Internal Services | Reason |
|---|---|---|---|
| Netflix | REST | gRPC | REST for partners, gRPC for 600+ microservices |
| GitHub | REST + GraphQL v4 | Internal RPC | GraphQL lets API users fetch exactly what they need |
| Stripe | REST | Internal gRPC | REST is easiest for payment integrations |
| REST (Cloud API) | gRPC everywhere | Invented gRPC for internal scale |
A mid-size e-commerce company (10M monthly users) decided to "modernize" by migrating from REST to GraphQL. Timeline estimate: 6 months. Actual: 18 months.
What Went Wrong
The Lesson
GraphQL solves real problems but adds new complexity. If your UI doesn't have extreme data fetching needs, REST is almost always the right choice. Don't migrate for modernization's sake.
Protocol questions test whether you understand real tradeoffs vs. hype. Don't say "GraphQL is better than REST" — that's a red flag. Say: "GraphQL solves X, REST solves Y, I'd choose based on..."
Common questions:
Strong answers include:
Red flags:
Quick check · API Protocols: REST vs GraphQL vs gRPC
1 / 3
Key takeaways
From the books
Designing Web APIs — Brenda Jin, Saurabh Sahni, Amir Shevat (2018)
Chapter 3: API Paradigms
API design is a product decision, not just a technical one. Your protocol choice defines your developer experience.
Building Microservices — Sam Newman (2021)
Chapter 4: Communication Styles
Synchronous vs asynchronous matters more than which synchronous protocol you pick. Most teams jump to REST/gRPC without first asking if async would be better.
Ready to see how this works in the cloud?
Switch to Career Paths for structured paths (e.g. Developer, DevOps) and provider-specific lessons.
View role-based pathsSign in to track your progress and mark lessons complete.
Questions? Discuss in the community or start a thread below.
Join DiscordSign in to start or join a thread.