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.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

REST API Design in Practice — The Decisions That Determine Developer Experience

REST APIs are built once and integrated against indefinitely. The design decisions made in the first hour — resource modeling, error shapes, versioning, pagination — determine how much friction every integration will carry forever.

Read more

The Real Cost of a Senior Backend Hire in Copenhagen — And What Smart Founders Do Instead

You thought a senior backend hire would cost DKK 70K a month. The real number — once Denmark's employer obligations are factored in — is closer to DKK 100K. And that's before the recruiter calls.

Read more

Java Thread Management — Why ExecutorService Exists and How to Use It Well

Creating threads directly is expensive, uncontrolled, and hard to shut down cleanly. ExecutorService solves all three problems — but its default configurations have tradeoffs that matter in production.

Read more

Munich Cannot Produce Backend Engineers Fast Enough — Here Is How Growing Teams Adapt

TU München graduated another strong class of computer scientists. Half of them had signed offers at BMW or Siemens before the ceremony started.

Read more