REST Is Not Just Using HTTP. Here Is What It Actually Means.
by Eric Hanson, Backend Developer at Clean Systems Consulting
“It’s REST because it uses HTTP” is how you get into trouble
Walk into most backend codebases and you’ll see APIs labeled as REST that look like this:
POST /createUser
POST /getOrders
POST /updateOrderStatus
It runs over HTTP. It uses JSON. Someone probably even said “REST API” in a design doc.
But this is RPC over HTTP. And that distinction matters more than people think.
The problem isn’t semantics—it’s that you miss out on the constraints that make REST scalable and evolvable in the first place. You end up reinventing half of HTTP poorly, and clients pay the price.
REST is a set of constraints, not a protocol
REST (Representational State Transfer), defined in Roy Fielding’s dissertation, is an architectural style. Not a spec, not a library, not a framework.
There are six constraints. If you’re not following most of them, you’re not building a RESTful system.
The ones that actually matter in practice:
1. Resource-based modeling
In REST, everything is a resource. Not actions.
Bad (RPC-style):
POST /approveInvoice
Better:
PATCH /invoices/{id}
{
"status": "approved"
}
The difference is subtle but important. You’re modeling state transitions on resources, not exposing verbs as endpoints.
This leads to more predictable APIs and better alignment with HTTP semantics.
2. Uniform interface
This is the core constraint most teams ignore.
It means:
- Standard methods (GET, POST, PUT, PATCH, DELETE) have consistent meaning
- Resources are identified by URIs
- Representations (usually JSON) describe state
- Clients interact through these standard operations
If your API requires a custom mental model per endpoint, you’ve broken the uniform interface.
Example of consistency:
GET /orders/{id}
DELETE /orders/{id}
PATCH /orders/{id}
Same resource, different operations. No surprises.
3. Statelessness
Every request must contain all the information needed to process it.
No server-side session state. No hidden context.
That means:
GET /orders
Authorization: Bearer <token>
Not:
GET /orders
# relies on server-side session
Statelessness is what allows horizontal scaling to work cleanly. Any instance can handle any request.
The tradeoff is that clients have to send more data (tokens, filters, context) on each request.
4. Cacheability
Responses should explicitly define whether they can be cached.
Most APIs ignore this completely, which is a missed opportunity.
Example:
HTTP/1.1 200 OK
Cache-Control: public, max-age=60
ETag: "abc123"
With proper caching, you reduce load and latency without touching application code.
But it requires discipline:
- Stable representations
- Correct invalidation strategies
- Awareness of stale data risks
5. Layered system
Clients shouldn’t need to know whether they’re talking to:
- a load balancer
- an API gateway
- a service mesh
- the origin server
This constraint is what enables architectures with reverse proxies (NGINX), CDNs, and gateways like Kong or Envoy.
If your API design leaks infrastructure details (custom headers, routing hacks), you’re working against this constraint.
The constraint most people skip: HATEOAS
Hypermedia as the Engine of Application State (HATEOAS) is where most “REST APIs” stop being REST.
The idea: clients discover actions dynamically through links in responses.
Example:
{
"id": "1234",
"status": "pending",
"_links": {
"approve": { "href": "/invoices/1234", "method": "PATCH" },
"cancel": { "href": "/invoices/1234", "method": "DELETE" }
}
}
In theory, this decouples clients from hardcoded workflows.
In practice, almost nobody implements this fully.
Why?
- It adds complexity to both server and client
- Tooling support is limited compared to OpenAPI
- Most teams prefer explicit contracts over runtime discovery
You can build perfectly good APIs without HATEOAS. But strictly speaking, you’re not fully RESTful.
Where REST works well—and where it doesn’t
REST shines when:
- You’re exposing CRUD-heavy resources
- You want strong caching semantics
- You need long-term evolvability with multiple clients
- You can commit to consistent patterns
It starts to break down when:
- You have complex workflows spanning multiple resources
- Clients need highly customized data shapes (GraphQL often wins here)
- Latency from multiple round trips becomes a bottleneck
- You’re modeling actions more than state
For example, a checkout flow involving inventory, payments, and fraud checks often ends up awkward in pure REST. You either:
- overload resource endpoints with implicit behavior, or
- fall back to RPC-style endpoints anyway
At that point, being honest about the style you’re using is better than forcing REST semantics where they don’t fit.
REST vs “REST-like”: be deliberate
Most production APIs land in a pragmatic middle ground:
- resource-oriented URLs
- standard HTTP methods
- stateless auth (JWT, OAuth2)
- JSON representations
- no HATEOAS
That’s fine.
The problem is when teams think they’re getting the benefits of REST while ignoring the constraints that provide those benefits.
If you’re not leveraging:
- caching headers
- consistent resource modeling
- proper use of HTTP methods
then calling it REST doesn’t buy you anything.
You’re just using HTTP as a transport.
What to do differently this week
Pick one endpoint in your API and evaluate it:
- Is it modeling a resource or an action?
- Does it use HTTP methods correctly?
- Could the response be cached safely?
- Is the behavior consistent with similar endpoints?
If the answer to any of those is “no,” fix that one endpoint.
You don’t need to rewrite your API to be “pure REST.” But tightening the constraints where it matters will make your system more predictable—and much easier to evolve.