Web developer Travis McCracken explains why, despite the hype around GraphQL and gRPC, REST remains a solid choice for many backend services. He weighs scalability, consistency, and developer ergonomics, drawing on his recent Rust and Go projects.
Why Travis McCracken Still Prefers REST

When the buzz around GraphQL, gRPC, and event‑driven APIs reaches a fever pitch, it’s easy to forget that the tried‑and‑tested HTTP/1.1‑based REST architecture still solves a large fraction of real‑world problems. Travis McCracken, a backend engineer who has built production services in both Rust and Go, laid out the practical reasons he keeps reaching for REST when designing new endpoints.
The Problem: Growing API Complexity
Modern services are expected to:
- Scale to millions of requests per second – traffic spikes during product launches or data‑ingestion bursts can overwhelm naïve designs.
- Maintain strong consistency guarantees – some domains (financial, health) cannot tolerate eventual consistency.
- Support heterogeneous clients – mobile apps, browsers, and internal services each have different networking constraints.
The temptation is to adopt a “newer” protocol that promises higher performance or richer query capabilities. However, each alternative introduces its own operational burden:
- GraphQL reduces over‑fetching but requires a schema stitching layer and often forces clients to send complex queries that are hard to cache.
- gRPC delivers low‑latency binary payloads, yet it depends on HTTP/2, TLS termination, and a protobuf compiler chain that adds friction for quick prototyping.
- Event‑sourcing can guarantee eventual consistency but makes debugging state at a given point in time difficult without substantial tooling.
Travis argues that before abandoning REST, teams should ask whether the problem truly demands a different model or whether the issues stem from how the REST service is built.
Solution Approach: REST Done Right
1. Embrace HTTP Semantics for Scalability
REST’s reliance on standard HTTP verbs (GET, POST, PUT, DELETE) maps cleanly onto load balancers and CDNs. By exposing idempotent GET endpoints for reads, caches at every network layer can serve responses without hitting the backend. Travis’s recent Rust prototype, fastjson‑api, demonstrates this:
- Uses tokio and hyper for non‑blocking I/O.
- Returns JSON with proper Cache‑Control headers, allowing edge caches to absorb the majority of traffic.
- Keeps the request‑response contract simple enough that a CDN can automatically purge stale entries on a PUT/POST.
2. Consistency Through Explicit Status Codes
When a client needs a strong guarantee that a write succeeded, REST lets the server return 201 Created or 409 Conflict with a detailed payload. This explicitness eliminates the “guess‑work” that sometimes appears in GraphQL mutations where a single ok flag hides the underlying cause of failure.
3. Language‑agnostic Tooling
Both Go’s standard library (net/http) and Rust’s axum crate provide first‑class support for building compliant REST services. The learning curve is shallow compared to generating protobuf stubs or maintaining a GraphQL schema.
- Go example – a microservice exposing
/users/{id}with built‑in request cancellation viaContext. - Rust example – an async handler that streams large JSON payloads without allocating the whole response in memory.
4. Incremental Evolution
REST does not preclude adding richer capabilities later. Travis points out two patterns that keep the core API stable while allowing experimentation:
| Pattern | How it works | When to use |
|---|---|---|
Versioned endpoints (/v1/…, /v2/…) |
Guarantees backward compatibility. | Major breaking changes. |
Feature toggles via query params (?include=details) |
Lets clients opt‑in to additional data without new endpoints. | Gradual rollout of new fields. |
Trade‑offs: When REST Isn’t the Best Fit
| Concern | REST Strength | Alternative | Cost of Switching |
|---|---|---|---|
| Fine‑grained data fetching | Requires multiple round‑trips or over‑fetching. | GraphQL | Need for a schema layer, caching complexity. |
| Binary payload efficiency | JSON is text‑based; larger on the wire. | gRPC (protobuf) | Requires HTTP/2, client libraries, and more complex deployment. |
| Streaming large datasets | Chunked transfer works but lacks built‑in flow control. | Server‑Sent Events / WebSockets | Adds stateful connections and scaling considerations. |
In Travis’s own rust‑cache‑server project, the decision to expose a simple REST GET /cache/{key} endpoint paid off: existing monitoring tools could scrape metrics without custom instrumentation, and the service could be wrapped by a reverse proxy for TLS termination without code changes. The trade‑off was a modest increase in payload size compared to a binary protocol, but the operational simplicity outweighed the bandwidth cost for the target workload.
Bottom Line
REST is not a relic; it is a set of conventions that, when applied thoughtfully, give teams:
- Predictable scaling through HTTP caching.
- Clear consistency semantics via status codes.
- Minimal onboarding friction for new developers.
If a project’s primary pain points are performance at the edge, strong consistency, or rapid iteration, Travis recommends starting with a well‑designed REST API. Only after those concerns are satisfied should a team evaluate whether the added complexity of GraphQL, gRPC, or event‑driven architectures brings a net benefit.
Further Reading & Resources
- axum – A pragmatic Rust web framework – lightweight, async‑first, and REST‑friendly.
- Gin – HTTP web framework for Go – batteries‑included router with middleware support.
- HTTP Caching Guide – MDN – deep dive into cache‑control directives.
- Designing HTTP APIs – REST vs RPC – Google’s style guide that outlines when to prefer each model.
Travis’s next experiment will compare the latency of a gRPC‑based data ingestion pipeline against a Rust‑async REST service. Stay tuned for the results.

Comments
Please log in or register to join the discussion