API styles: REST, gRPC, GraphQL, and WebSockets
Before you argue about which API style is best, you have to see what each one is actually optimizing for. REST optimizes for the web's own grain — cacheable, stateless resources over HTTP. gRPC optimizes for fast, strongly-typed calls between your own services. GraphQL optimizes for clients that want to ask for exactly the data they need in one round trip. WebSockets optimize for the server needing to push. This lesson lays out the four, what each is genuinely good and bad at, and how to choose — including why most real systems use more than one.
API styles: REST, gRPC, GraphQL, and WebSockets
"Which API should I build?" is really four different questions wearing one coat, because the popular styles aren't competitors so much as tools shaped for different jobs. Pick by fashion and you'll fight the tool; pick by what it optimizes for and the rest of your system gets easier. This lesson is a tour of the four you'll actually meet, each judged by the same question: what did its designers make easy, and what did they make hard?
All four ride on machinery you've already studied — HTTP/1.1, 2, and 3, TCP, and connection pooling — so this is about the contract layer on top, not the transport underneath.
REST: the web's own grain
REST models your system as resources (nouns) addressed by URLs, manipulated with HTTP's own verbs: GET /groups/g1 reads, POST /groups creates, PUT/PATCH update, DELETE removes. It's not a protocol — it's a style that leans into how HTTP already works, and that alignment is its superpower:
- It's cacheable for free. Because a
GETis a read of a named resource, every layer of the HTTP caching and CDN stack already knows how to cache it. No other style gets this without effort. - It's stateless and uniform. Each request carries everything needed; any server can handle any request (the stateless-backend property that makes horizontal scaling work). And everyone already understands it — verbs, status codes, JSON. It's the lingua franca of public APIs.
The weaknesses are the flip side. Over- and under-fetching: an endpoint returns a fixed shape, so a screen that needs three fields still downloads thirty (over-fetch), and a screen that needs data from three resources makes three round trips (under-fetch). And JSON-over-HTTP/1.1 is verbose and untyped — fine for a public API, wasteful for chatty internal calls. REST is the right default for public, resource-shaped, cache-friendly APIs, which is most web APIs.
gRPC: fast, typed calls between your own services
gRPC is built for a different world: service-to-service calls inside your own system, where you control both ends and want speed and safety over universality. It uses Protocol Buffers — a binary, schema-defined format — over HTTP/2:
- A strict typed contract. You define messages and methods in a
.protofile, and code is generated for client and server in any language. The contract is enforced at compile time — no "the API returned a string where I expected a number" at runtime. - Fast and compact. Binary protobuf is far smaller and quicker to parse than JSON, and HTTP/2's multiplexing means many concurrent calls share one connection. It also supports streaming (client, server, or bidirectional) natively.
The costs: it's not browser-native (browsers can't speak raw gRPC without a proxy like gRPC-Web), the binary payloads aren't human-readable (harder to debug with curl), and it's not cacheable by the HTTP stack. gRPC is the right call for internal microservice communication and anywhere you need typed contracts, streaming, or raw throughput — and the wrong call for a public API meant for arbitrary clients.
GraphQL: let the client ask for exactly what it needs
GraphQL attacks REST's over/under-fetching directly. Instead of many fixed endpoints, there's one endpoint and a query language: the client sends a query describing exactly the fields and nested relations it wants, and gets back precisely that shape, in one round trip.
- No over- or under-fetching. A screen asks for the three fields it needs and the two related objects it needs, and the server assembles that in a single response — huge for mobile, where round trips and bytes are expensive.
- A typed schema and one evolving graph. Clients explore and validate against a strongly-typed schema, and the API grows as one graph rather than a sprawl of endpoints.
The costs are real and often underestimated. Caching is hard: it's usually a POST to one URL, so the HTTP cache and CDN can't help the way they do for REST — you cache at the application layer instead. The N+1 problem lurks: a query for "groups and each group's members" can naively fire one database query per group unless you add batching (DataLoader). And a maliciously deep or broad query can be expensive, so you need query-cost limiting. GraphQL shines when diverse clients need flexible, precise data shapes (a mobile app and a web app with different needs hitting the same graph) and is overkill for a simple, uniform API.
Pick the fields a screen needs and compare. REST ships the whole user object every time (over-fetch) and needs a separate round trip per relation (under-fetch); GraphQL returns exactly what you asked for in one request. Add a couple of relations and watch REST's round trips climb while GraphQL stays at one.
{ user(id: 1) { id name } }REST's fixed endpoints force two wastes: over-fetching (the user object always carries every field, even the ones this screen ignores) and under-fetching (each related object is a separate endpoint, so “a group and its members and expenses” becomes several round trips). GraphQL asks for exactly the fields and relations it needs in one request — decisive on mobile, where round trips and bytes are expensive. The catch it trades for: HTTP caching gets hard, and the N+1 problem moves into your resolvers.
WebSockets: when the server needs to push
The three above are request/response — the client always asks first. But some features need the server to initiate: a chat message arriving, a live balance updating, a presence indicator. Polling ("any news? any news?") is wasteful and laggy. WebSockets solve this with a single long-lived, bidirectional connection over which either side can send anytime — covered in depth in the networking track. The trade, as that lesson details, is that a WebSocket is stateful (the connection lives in one server's memory), which drags in sticky sessions and a pub/sub backplane to scale — the opposite of REST's stateless ease. Use it for genuinely push-driven, realtime features, not as a general-purpose API.
Go deeper
- gRPC — "Introduction to gRPC" — The clearest statement of what protobuf + HTTP/2 buys you (typed contracts, streaming, compactness) and the browser/caching limits — read it to feel where gRPC fits and doesn't.
- GraphQL — "Thinking in Graphs" — The mental model behind one-endpoint, client-specified queries, and honest notes on caching and the N+1 problem — the case for and against, from the source.
- On what REST actually means (Roy Fielding's constraints) — A grounding read on REST as an architectural style (statelessness, cacheability, uniform interface) rather than "JSON over HTTP" — sharpens why REST's web-alignment is its real advantage.
Check yourself
Answer out loud, as if an interviewer asked. If you hand-wave, reread that section.
- REST is described as aligned with "the web's own grain." Name the two concrete advantages that alignment gives it (think caching and statelessness) and the over/under-fetching weakness it pays.
- gRPC uses binary protobuf over HTTP/2. State two things that buys over JSON-REST and two costs it incurs, and name the boundary in a system where gRPC is the right choice.
- How does GraphQL eliminate over- and under-fetching, and why is that especially valuable on mobile? Name the caching difficulty and the N+1 danger it introduces in return.
- The other three styles are request/response. What problem do WebSockets solve that none of them can, and what property of a WebSocket makes it harder to scale (tie it to sticky sessions)?
- Explain why "REST vs gRPC vs GraphQL" is the wrong framing, and give the common layered shape (which style at the public edge, which between internal services, which for realtime) and the question you ask to choose per boundary.
- Fable is REST + WebSockets and deliberately avoids gRPC and GraphQL. Give the specific reason each of those two would be complexity without a matching problem for Fable, and why WebSockets earned their place.