API Protocols: REST vs GraphQL vs gRPC
Choose the right communication contract before you write a single line of code
API Protocols: REST vs GraphQL vs gRPC
Choose the right communication contract before you write a single line of code
What you'll learn
- REST is the default: stateless, HTTP-native, universally understood. Use it unless you have a specific reason not to.
- GraphQL solves over/under-fetching for complex UIs but adds schema management and N+1 complexity.
- gRPC wins on performance for internal microservice communication â 5â10Ã faster than REST with HTTP/2 + protobuf.
- The FAANG pattern: REST or GraphQL for public APIs, gRPC for internal services.
- Changing protocols after clients are in production costs 6â18 months. Choose deliberately upfront.
Lesson outline
Why Your Protocol Choice Ships With You Forever
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: The Universal Language of the Web
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)
- â Stateless â Every request contains all needed info. No session state on server. This enables horizontal scaling.
- â Uniform Interface â Resources = nouns (GET /users/123). Verbs = HTTP methods. This is the constraint teams actually follow.
- â Client-Server separation â UI and data storage are decoupled. Mobile app doesn't care if you switch databases.
- â ïļCacheable â Responses should declare cacheability. Most teams forget this, causing massive CDN misuse.
- ðŦLayered System â Client doesn't know if it's talking to origin or proxy. Often ignored.
- ðŦCode on Demand â Servers can send executable code. Almost never used.
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
GraphQL: Ask for Exactly What You Need
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};
gRPC: Built for Microservices at Scale
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
- âĄProtobuf serialization is 3â10à smaller than JSON â Binary format, no field name repetition. A 100KB JSON payload becomes 10KB protobuf.
- âĄHTTP/2 multiplexing â Multiple streams over one TCP connection. No head-of-line blocking.
- ðStrongly typed contracts (proto files) â Schema-first, code-generated clients in any language. No more "what does this field mean?"
- ðBi-directional streaming â Client streaming, server streaming, or full duplex. REST can't do this.
- ðŦPoor browser support â Browsers can't use HTTP/2 trailers required by gRPC. Use gRPC-Web or REST for public APIs.
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.
The Decision Framework: Which One Do You Pick?
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 |
War Story: The GraphQL Migration That Took 18 Months
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
- ðĨN+1 queries in production â Forgot DataLoader on the product-to-reviews resolver. 1 product page = 50 DB queries. Site crawled at 2Ã traffic.
- ðĨCaching broke completely â All GraphQL queries are POST requests. CDN caching stopped working. Had to implement Apollo persisted queries.
- ðĨMobile clients broke on schema changes â GraphQL is "versionless" in theory. In practice, removing a field broke 6-month-old app versions still in use.
- ðĨAuthorization was harder â In REST, you authorize by endpoint. In GraphQL, every field can expose different data â had to add field-level auth.
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.
How this might come up in interviews
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:
- How would you decide between REST and GraphQL for a new product?
- What is the N+1 problem and how do you solve it in GraphQL?
- When would you choose gRPC over REST for internal services?
- How do you version REST APIs without breaking existing clients?
Strong answers include:
- Discusses tradeoffs in context of specific use cases
- Mentions DataLoader unprompted when discussing GraphQL
- Knows gRPC is binary and why that matters for performance
Red flags:
- "GraphQL is always better than REST"
- Doesn't know what HTTP status codes mean
- Never heard of DataLoader or N+1 problem
Quick check · API Protocols: REST vs GraphQL vs gRPC
1 / 3
A mobile app team reports their home screen makes 5 API calls and receives too much unused data. Which protocol change would best address this?
Key takeaways
- REST is the default: stateless, HTTP-native, universally understood. Use it unless you have a specific reason not to.
- GraphQL solves over/under-fetching for complex UIs but adds schema management and N+1 complexity.
- gRPC wins on performance for internal microservice communication â 5â10Ã faster than REST with HTTP/2 + protobuf.
- The FAANG pattern: REST or GraphQL for public APIs, gRPC for internal services.
- Changing protocols after clients are in production costs 6â18 months. Choose deliberately upfront.
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.
Discussion
Questions? Discuss in the community or start a thread below.
Join DiscordIn-app Q&A
Sign in to start or join a thread.