API Protocols Comparison — REST, GraphQL, gRPC, SOAP
Four ways to expose APIs. Different eras, different trade-offs. Interviewers commonly ask “when would you pick which?” — knowing what each is good and bad at is more important than knowing every detail.
The headline differences
| REST | GraphQL | gRPC | |
|---|---|---|---|
| Year | 2000 (Fielding) | 2015 (Facebook) | 2015 (Google) |
| Transport | HTTP/1.1 or HTTP/2 | HTTP (usually POST) | HTTP/2 |
| Payload | JSON (usually) | JSON | Protobuf (binary) |
| Schema | OpenAPI (optional) | SDL (built-in) | .proto (built-in) |
| Typing | weak unless OpenAPI | strong | strong |
| Browser-native | yes | yes | no (needs gRPC-Web) |
| Streaming | SSE / WebSocket on top | subscriptions | first-class (4 modes) |
| Versioning | URL or header | additive evolution | proto evolution |
| Discoverability | OpenAPI/Swagger | introspection | reflection (optional) |
| Error model | HTTP status codes | always 200 + errors array | gRPC status codes |
| Caching | HTTP cache (great) | bespoke | bespoke |
| Tooling | universal | rich (Apollo, Relay) | rich (.proto codegen) |
SOAP (1998, Microsoft), for comparison: XML over HTTP (also SMTP and TCP), a built-in and strongly typed WSDL schema, browser-capable but heavy, no streaming, WSDL for versioning and discovery, SOAP Fault for errors, bespoke caching, and dated but mature tooling.
When to pick which
REST — the default for public-facing HTTP APIs. Universal client support, HTTP caching, easy to understand. The right answer for ~80% of new B2B APIs.
GraphQL — when clients need flexible, varied views of the same data and over-fetching/under-fetching is costing you. Mobile apps with heterogeneous screens; aggregator UIs that combine many services. Cost: server complexity, N+1 risk, weaker HTTP caching.
gRPC — for service-to-service APIs in a controlled environment (microservices inside your VPC, or trusted partners). Tight schemas, fast binary protocol, streaming. Cost: not browser-native, less human-readable for debugging.
SOAP — only when you’re integrating with a system that requires it (legacy enterprise, banking, government). Don’t choose SOAP for new APIs. Use Zeep on the Python side to consume it.
Performance
Measured on a 20-item order message (Python 3.14, details in
RPC and gRPC): Protobuf is about 3x smaller than JSON
and about 10x faster than the standard json module, but under 2-3x faster
than orjson and only 1.5x smaller once both are gzipped. All of it is
microseconds per message.
GraphQL ≈ REST per request, fewer round trips for complex queries
SOAP slower than REST: XML parsing and envelope overheadBut network and database usually dominate. The protocol choice rarely is the bottleneck for typical CRUD APIs.
Schema and contract
| Contract source | Typed clients | |
|---|---|---|
| REST | OpenAPI / hand-written | generate from OpenAPI |
| GraphQL | SDL (schema.graphql) |
generate from SDL |
| gRPC | .proto file |
generate from .proto |
| SOAP | WSDL | generate from WSDL |
For all four, the schema is the contract. The difference is how “real” the schema is to the protocol: GraphQL/gRPC/SOAP enforce it at the wire level; REST + OpenAPI is contract-by-convention (the server can violate the OpenAPI doc without the framework noticing).
Wire format
A “get user” response in each:
REST (JSON):
{"id": 42, "name": "Alice", "email": "alice@example.com"}GraphQL (JSON, but client decides shape):
{"data": {"user": {"name": "Alice"}}}gRPC (Protobuf binary):
0x08 0x2a 0x12 0x05 0x41 0x6c 0x69 0x63 0x65 ...SOAP (XML):
<soap:Envelope>
<soap:Body>
<GetUserResponse xmlns="...">
<User><Id>42</Id><Name>Alice</Name></User>
</GetUserResponse>
</soap:Body>
</soap:Envelope>JSON is the universal denominator. Protobuf is fastest. XML is verbose. GraphQL’s payloads are JSON but request-shaped.
How errors work
REST: HTTP status code + JSON body. Convention: 4xx for client errors, 5xx for server. RFC 7807 (Problem Details) standardizes the body shape.
GraphQL: HTTP 200 even on errors. errors array in response. Partial data possible ({"data": {...partial...}, "errors": [...]}).
gRPC: status codes (OK, NOT_FOUND, INVALID_ARGUMENT, etc.) — 17 codes, OK (0) to UNAUTHENTICATED (16), returned in HTTP/2 trailers.
SOAP: <soap:Fault> element in the response body. HTTP 500 typically.
Client-server fit
| Client | Best fit | Poor or only OK |
|---|---|---|
| Browser (JS) | REST, GraphQL | gRPC (needs gRPC-Web), SOAP (heavy) |
| Mobile (iOS/Android) | REST, GraphQL, gRPC | SOAP is only OK |
| Server-to-server | gRPC (best), REST | GraphQL and SOAP are OK |
| CLI | REST | SOAP is poor; GraphQL, gRPC OK |
| Embedded / IoT | gRPC | GraphQL, SOAP poor; REST OK |
Browsers can do GraphQL natively (it’s HTTP+JSON), need a proxy/transcoding layer for gRPC, and chew through SOAP at significant cost. For browser-facing APIs: REST or GraphQL.
Combine them
It’s common to mix:
- REST at the edge, gRPC internally: clients talk REST/JSON; backend microservices talk gRPC.
- GraphQL gateway in front of REST/gRPC backends: clients get GraphQL flexibility; services stay simple.
- SOAP adapter to a modern API: wrap a legacy SOAP system behind a REST or GraphQL facade.
The “best protocol” debate is mostly moot — modern stacks use each where it fits.
Related
Interview angle 6
- “REST vs GraphQL — when each?” — REST for simple resource-shaped APIs, public-facing, where HTTP caching matters. GraphQL when clients have heterogeneous data needs (mobile + web + partner integrations all hitting the same backend with different views) and over-fetching is real.
- “REST vs gRPC?” — REST for browser-facing or public-partner APIs (HTTP/JSON, universal). gRPC for service-to-service in a controlled env: binary, streaming, schema-enforced, deadlines; faster per message, though the network usually dominates.
- “When would you still use SOAP?” — only when a system you must integrate with requires it (banking, legacy enterprise, government). Don’t choose SOAP for new APIs.
- “GraphQL vs gRPC — both have schemas, both are typed; what’s the diff?” — GraphQL is client-driven query shape over JSON; gRPC is fixed RPC over binary. GraphQL is for flexible read-mostly APIs; gRPC is for fast service-to-service calls with stable interfaces.
- “Why doesn’t GraphQL cache as well as REST?” — REST URLs are cache keys; GET on
/users/42is cacheable in any HTTP cache. GraphQL almost always uses POST with query in body — same URL for every query, no cache key. You can do persisted queries with GET to recover HTTP caching, but it’s not the default. - “How do errors differ between REST and GraphQL?” — REST uses HTTP status codes (4xx/5xx). GraphQL almost always returns 200 with
errorsarray, optionally with partialdata. The “always 200” model lets multi-resource queries succeed for some fields while failing others.