REST Is Not CRUD
It’s a Constraint System

I'm a passionate backend dev
At some point, almost every backend engineer hears this explanation: “REST is just CRUD over HTTP.” It sounds practical. It sounds efficient. It’s also quietly misleading.
Because if REST were just CRUD, most REST APIs wouldn’t be so hard to design well.
REST is not about databases. It’s not about controllers. It’s not even about URLs.
REST is about constraints. And constraints are what make systems scale without constant coordination.
Let’s Start With the Tempting Shortcut
CRUD feels natural.
Create
Read
Update
Delete
Databases use it. ORMs use it. Frameworks encourage it. So we map it directly:
POST → Create
GET → Read
PUT/PATCH → Update
DELETE → Delete
And call it REST. Sometimes this works. Sometimes it creates APIs that feel… off.
The problem isn’t CRUD itself. The problem is stopping there.
REST Asks a Different Question
CRUD asks: “What can I do to this data?”
REST asks: “How should independent systems communicate safely over time?”
That’s a much harder question. And the answer is not a set of endpoints.
It’s a set of rules.
REST Is Defined by Constraints, Not Features
REST doesn’t say: “Use JSON”, “Use these URLs”, “Use these methods”
It says: “If you follow these constraints, your system will have certain properties.”
Things like: Scalability, Reliability, Evolvability, Simplicity.
REST is a tradeoff machine.
Constraint 1: Client–Server Separation
REST separates concerns very deliberately.
Clients handle user interaction
Servers handle data and logic
Why this matters:
Clients and servers can evolve independently
Teams can move faster without breaking each other
Responsibilities stay clear
This separation is why frontend and backend can scale as disciplines.
Constraint 2: Statelessness (Yes, Again)
Every request must contain all the information needed to process it.
No hidden server memory. No invisible session state.
We’ve already seen why this matters: Load balancing, Failures, Scaling.
REST doesn’t invent statelessness. It requires it.
Constraint 3: Uniform Interface
This is where many CRUD-based APIs quietly break REST.
A uniform interface means:
Consistent use of HTTP methods
Meaningful status codes
Predictable behavior across resources
When every endpoint behaves differently, clients become fragile.
Uniformity allows: Generic tooling, Caching, Intermediaries to help instead of guess.
This is about communication, not convenience.
Constraint 4: Cacheability
REST assumes responses can be cached unless stated otherwise.
This only works if:
GET requests are safe
Responses are deterministic
Semantics are respected
CRUD-focused APIs often break this accidentally.
Caching isn’t an optimization. It’s a design assumption.
Constraint 5: Layered System
In REST, clients don’t know:
Which server handled the request
Whether a proxy or cache was involved
How many layers exist in between
This allows: Load balancers, Gateways, CDNs, Security layers.
CRUD doesn’t think about layers. REST does.
Why CRUD APIs Feel Fine… Until They Don’t
CRUD-based APIs often: Work well early, Are easy to build, Feel productive.
Then scale arrives. Suddenly:
Clients depend on specific server behavior
Changes break consumers
Caching becomes impossible
Versioning becomes painful
The API becomes a coordination nightmare.
REST tries to prevent that future.
Why REST Feels “Strict”
Many engineers feel REST is restrictive. That’s true. Constraints always feel restrictive at first.
But constraints: Reduce choices, Prevent accidental complexity, Enable scale.
Just like traffic rules.
The Pattern Is Complete
Let’s zoom out one last time.
Networks are unreliable
TCP manages delivery
UDP accepts chaos
HTTP defines communication
Statelessness enables scale
Semantics enable cooperation
REST enables independence
This is not a stack of tools. It’s a stack of ideas.
What Comes Next
From here, we can go in two strong directions:
Building an HTTP Server and Designing APIs That Survive Scale
Authentication Over HTTP: Why It’s Weird and Why It Works
Both build naturally on REST.
This article is part of the Thinking in Backend series, where we learn backend engineering by focusing on how systems think, not just how code runs.




