Audience
This guide is for developers who want to understand what REST really means:
- Backend developers building APIs who want to make informed architectural decisions
- Frontend developers consuming APIs who want to understand why APIs behave certain ways
- API designers who need to explain REST constraints to their teams
- Anyone confused about what makes an API “RESTful” (and tired of hearing contradictory definitions)
You should be familiar with basic HTTP concepts. If not, start with HTTP for REST APIs.
Goal
After reading this guide, you’ll understand:
- What REST actually is (an architectural style, not a protocol)
- The six constraints Roy Fielding defined and why each matters
- The difference between resources and representations
- Why statelessness is non-negotiable and what it enables
- What HATEOAS means and why almost nobody implements it
- How to assess whether an API is truly RESTful
- Why “REST API” is often a misnomer (and why that might be okay)
This guide builds criteria—the ability to evaluate REST compliance and make conscious trade-offs.
1. The REST Misconception
Let’s start with an uncomfortable truth: most “REST APIs” aren’t REST.
When developers say “REST API,” they usually mean:
- Uses HTTP
- Returns JSON
- Has URLs like
/users/123 - Uses GET, POST, PUT, DELETE
That’s not REST. That’s “HTTP with JSON.”
REST (Representational State Transfer) is an architectural style defined by Roy Fielding in his 2000 doctoral dissertation. It’s a set of constraints that, when applied together, create systems with specific properties: scalability, simplicity, portability, and evolvability.
graph TB
subgraph "What People Think REST Is"
A[HTTP + JSON] --> B[CRUD Endpoints]
B --> C[/users, /products]
end
subgraph "What REST Actually Is"
D[Architectural Style] --> E[6 Constraints]
E --> F[Emergent Properties]
F --> G[Scalability]
F --> H[Evolvability]
F --> I[Simplicity]
end
style A fill:#ffccbc
style D fill:#c8e6c9Why does this matter? Because understanding the real REST helps you:
- Make better API design decisions
- Know when to follow REST and when to consciously break it
- Communicate precisely with your team
- Stop cargo-culting patterns without understanding why
2. Where REST Came From
Roy Fielding didn’t invent REST in a vacuum. He was one of the principal authors of HTTP/1.1 and co-founded the Apache HTTP Server project. REST emerged from his experience building the web.
The Web as the Ultimate Distributed System
The World Wide Web is arguably the most successful distributed system ever created:
- Billions of users
- Millions of servers
- Decades of evolution without breaking backward compatibility
- Built on open standards anyone can implement
Fielding asked: What architectural principles make the web work?
His dissertation was an analysis of what made the web successful. REST is the architectural style he extracted from that analysis.
REST Is a Set of Constraints
Here’s the key insight: REST isn’t a specification you implement. It’s a set of constraints you apply to your architecture. Each constraint provides specific benefits, and applying them together creates emergent properties.
Think of it like building construction:
- A “foundation” isn’t a specific product—it’s a constraint that buildings need stable bases
- Different buildings implement foundations differently (concrete slab, pier and beam, basement)
- The constraint guides design without dictating implementation
REST works the same way. It tells you what properties your system should have, not how to implement them.
3. The Six REST Constraints
Fielding defined six constraints. Let’s examine each one and what happens when you violate it.
Constraint 1: Client-Server
The constraint: Separate user interface concerns from data storage concerns.
graph LR
subgraph "Client"
A[User Interface]
B[User Experience]
end
subgraph "Server"
C[Data Storage]
D[Business Logic]
end
A <-->|HTTP| C
style A fill:#e3f2fd
style C fill:#fff3e0Why it matters:
- Clients and servers can evolve independently
- You can have multiple clients (web, mobile, CLI) for one server
- Server can scale without affecting client code
- Teams can work in parallel
What breaks when violated: If your “API” requires specific client behavior or embeds client-specific logic, you lose portability. Every new client needs special handling.
Constraint 2: Stateless
The constraint: Each request must contain all information necessary to understand and process it. The server stores no client context between requests.
sequenceDiagram
participant C as Client
participant S as Server
Note over S: Server knows nothing about Client
C->>S: GET /cart (with auth token)
Note over S: Reads token, fetches cart, forgets client
S->>C: Cart contents
C->>S: POST /cart/items (with auth token + item data)
Note over S: Reads token, adds item, forgets client
S->>C: Updated cart
Note over S: Still knows nothing about ClientWhy it matters:
- Any server can handle any request (horizontal scaling)
- No session affinity required (load balancer freedom)
- Requests can be cached
- Failures don’t corrupt server state
- Easier debugging (each request is self-contained)
What breaks when violated:
If the server stores session state:
- You need sticky sessions (client always goes to same server)
- Server failure loses user state
- Scaling requires session replication
- Caching becomes difficult
Common misconception: “Stateless means no user data.”
Wrong. Stateless means no client session state on the server between requests. The server can store resource state (user profiles, orders, etc.). It just can’t store application state (shopping cart in memory, wizard progress, etc.) between requests.
The client sends everything needed:
GET /cart HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
The token contains or references all context the server needs.
Constraint 3: Cacheable
The constraint: Responses must explicitly define themselves as cacheable or non-cacheable.
Why it matters:
- Reduces server load (cached responses don’t hit origin)
- Reduces latency (client or proxy serves from cache)
- Enables CDNs and edge caching
- Scales read-heavy workloads dramatically
What breaks when violated:
If cacheability isn’t explicit:
- Intermediaries can’t optimize
- Clients make unnecessary requests
- You lose the biggest scaling lever on the web
HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
ETag: "abc123"
{
"user": {"id": 123, "name": "Alice"}
}
The Cache-Control header tells everyone: “Cache this for 1 hour.” Without it, caching behavior is undefined.
Constraint 4: Uniform Interface
The constraint: All components communicate through a standardized interface.
This is the most fundamental constraint. It has four sub-constraints:
4.1 Identification of Resources
Every resource has a unique identifier (URI).
/users/123 ← User 123
/orders/456 ← Order 456
/users/123/orders ← Orders belonging to user 123
A resource is any concept that can be named. Users, orders, documents, searches—anything.
4.2 Manipulation Through Representations
Clients don’t directly modify resources. They send representations of resources.
graph LR
subgraph "Resource (Abstract)"
R[User 123]
end
subgraph "Representations (Concrete)"
J[JSON]
X[XML]
H[HTML]
end
R --> J
R --> X
R --> H
style R fill:#fff3e0
style J fill:#e3f2fd
style X fill:#e3f2fd
style H fill:#e3f2fdThe same resource (User 123) can have multiple representations:
- JSON for APIs
- XML for legacy systems
- HTML for browsers
The client sends a representation to modify the resource:
PUT /users/123 HTTP/1.1
Content-Type: application/json
{"name": "Alice Smith", "email": "[email protected]"}
The server updates the resource based on this representation.
4.3 Self-Descriptive Messages
Each message contains enough information to understand how to process it.
GET /users/123 HTTP/1.1
Accept: application/json
---
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=3600
{"id": 123, "name": "Alice"}
The Content-Type tells you how to parse the body. The Cache-Control tells you how to cache it. No out-of-band knowledge required.
4.4 Hypermedia as the Engine of Application State (HATEOAS)
This is where REST gets controversial.
The constraint: Clients should navigate the application entirely through hyperlinks provided by the server.
GET /users/123 HTTP/1.1
---
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "Alice",
"_links": {
"self": {"href": "/users/123"},
"orders": {"href": "/users/123/orders"},
"edit": {"href": "/users/123", "method": "PUT"},
"delete": {"href": "/users/123", "method": "DELETE"}
}
}
The response tells the client: “Here’s what you can do next.”
Why HATEOAS matters:
- Clients don’t need hardcoded URLs
- Server can change URLs without breaking clients
- Discoverability: new features appear as new links
- Server controls navigation flow
Why almost nobody implements it:
- Adds payload overhead
- Requires sophisticated client logic
- Most APIs have documentation instead
- Mobile apps prefer static endpoints for performance
We’ll discuss this trade-off later.
Constraint 5: Layered System
The constraint: The architecture can be composed of hierarchical layers, each only knowing about the layer it directly interacts with.
graph TB
C[Client] --> LB[Load Balancer]
LB --> CDN[CDN / Cache]
CDN --> GW[API Gateway]
GW --> S1[Service A]
GW --> S2[Service B]
style C fill:#e3f2fd
style LB fill:#fff3e0
style CDN fill:#fff3e0
style GW fill:#fff3e0
style S1 fill:#c8e6c9
style S2 fill:#c8e6c9Why it matters:
- Add caching layers without changing clients or servers
- Add security layers (API gateway, WAF) transparently
- Scale components independently
- Replace components without affecting others
What breaks when violated:
If clients need to know about internal architecture (which server to hit, how services communicate), you’ve coupled everything together. Changes ripple through the system.
Constraint 6: Code on Demand (Optional)
The constraint: Servers can extend client functionality by transferring executable code.
This is the only optional constraint. Examples:
- JavaScript in web browsers
- Java applets (remember those?)
- WebAssembly modules
Most REST APIs don’t use this. It’s mainly relevant for browsers.
4. Resources vs. Representations
This distinction is fundamental to REST and widely misunderstood.
Resources Are Abstract
A resource is any concept that can be named and identified. It’s abstract—you can’t touch it.
Examples of resources:
- A specific user (User 123)
- The current weather in Madrid
- A document version at a point in time
- A search result for “REST APIs”
- The author of this guide
A resource has identity (URI), but it’s not the data itself.
Representations Are Concrete
A representation is concrete data that represents the resource’s current state.
The same resource can have many representations:
# JSON representation
GET /users/123 HTTP/1.1
Accept: application/json
{"id": 123, "name": "Alice", "email": "[email protected]"}
# XML representation
GET /users/123 HTTP/1.1
Accept: application/xml
<user><id>123</id><name>Alice</name><email>[email protected]</email></user>
# HTML representation (for browsers)
GET /users/123 HTTP/1.1
Accept: text/html
<html><body><h1>Alice</h1><p>[email protected]</p></body></html>
Same resource, different representations.
Why This Matters
Understanding resource vs. representation clarifies API design:
Wrong thinking: “My API returns user objects.”
Right thinking: “My API exposes user resources. Clients request representations in their preferred format.”
This enables:
- Content negotiation (client requests preferred format)
- Multiple clients (JSON for mobile, HTML for web, XML for legacy)
- Versioned representations (same resource, different schema versions)
- Partial representations (fields parameter, sparse fieldsets)
5. The REST Maturity Model
Leonard Richardson proposed a maturity model to classify APIs:
graph TB
L0[Level 0: The Swamp of POX]
L1[Level 1: Resources]
L2[Level 2: HTTP Verbs]
L3[Level 3: Hypermedia Controls]
L0 --> L1
L1 --> L2
L2 --> L3
style L0 fill:#ffccbc
style L1 fill:#fff9c4
style L2 fill:#c8e6c9
style L3 fill:#b3e5fcLevel 0: The Swamp of POX
One URI, one HTTP method (usually POST). The HTTP protocol is just a tunnel.
POST /api HTTP/1.1
{"action": "getUser", "userId": 123}
This is SOAP, XML-RPC, or “RPC over HTTP.” Not REST at all.
Level 1: Resources
Multiple URIs, one method. Resources are identified, but HTTP semantics are ignored.
POST /users/123 HTTP/1.1
{"action": "get"}
POST /users/123 HTTP/1.1
{"action": "delete"}
You have resources, but everything is POST.
Level 2: HTTP Verbs
Multiple URIs, appropriate HTTP methods. This is where most “REST APIs” live.
GET /users/123
POST /users
PUT /users/123
DELETE /users/123
HTTP methods have semantic meaning. Status codes are used correctly. This is “HTTP done right.”
Level 3: Hypermedia Controls (HATEOAS)
Responses include links to related actions. The API is self-describing.
{
"id": 123,
"name": "Alice",
"_links": {
"self": "/users/123",
"orders": "/users/123/orders",
"deactivate": "/users/123/deactivate"
}
}
This is full REST.
What Level Should You Target?
Level 2 is the pragmatic sweet spot for most APIs:
- Uses HTTP correctly
- Good developer experience
- Mature tooling support
- Well-understood patterns
Level 3 is ideal but costly:
- Requires sophisticated clients
- Increases payload size
- Limited tooling support
- Most benefits require client adoption
Most successful public APIs (Stripe, Twilio, GitHub) are Level 2. They’re not “pure REST,” but they’re well-designed HTTP APIs.
6. Why Most APIs Aren’t REST (And That’s Okay)
Here’s a liberating truth: you don’t have to build a REST API.
REST is one architectural style among many. It optimizes for:
- Long-lived systems
- Many independent clients
- Evolvability over time
- Web-scale distribution
If your system doesn’t need these properties, REST constraints may be overhead.
When REST Makes Sense
- Public APIs consumed by unknown third parties
- Systems that must evolve without breaking clients
- Large-scale distributed systems
- APIs that benefit from caching
When REST Might Not Be Ideal
- Internal microservices (gRPC often better)
- Real-time applications (WebSockets)
- Complex queries (GraphQL)
- Simple scripts or tools (just make it work)
The Honest Approach
Instead of saying “We have a REST API” when you don’t, say:
- “We have an HTTP API following REST-like conventions”
- “We have a JSON API with resource-oriented URLs”
- “We follow Level 2 of the Richardson Maturity Model”
Precision helps your team make better decisions.
7. Common Misconceptions
Let’s address frequent misunderstandings:
“REST means CRUD”
No. REST is about resources and representations. CRUD (Create, Read, Update, Delete) is one way to think about resource operations, but REST doesn’t require it.
Resources can support operations that don’t map to CRUD:
POST /orders/123/cancelPOST /documents/456/publishPOST /users/789/verify-email
These are valid in REST if the operation affects resource state.
“REST means no versioning”
No. Fielding argues that properly RESTful systems shouldn’t need versioning because HATEOAS enables evolution. But in practice, most APIs need versioning because:
- They’re Level 2, not Level 3
- Breaking changes happen
- Clients don’t always update
Versioning is a pragmatic necessity for most real-world APIs.
“REST means JSON”
No. REST doesn’t specify a data format. JSON is popular because:
- Lightweight and human-readable
- Native to JavaScript
- Wide tooling support
But XML, HTML, or Protocol Buffers are equally valid for REST. The key is self-description (Content-Type header) and content negotiation (Accept header).
“REST is the only way to build APIs”
Definitely no. REST is one tool in your toolkit. Consider:
- GraphQL for complex queries with varying data needs
- gRPC for high-performance internal services
- WebSockets for real-time bidirectional communication
- Server-Sent Events for server push
Choose based on your requirements, not dogma.
8. Practical Takeaways
Here’s how to apply this knowledge:
When Designing APIs
Be honest about your constraints. Are you building full REST or “HTTP with JSON”? Both are valid.
Prioritize Level 2. Use HTTP methods correctly, return proper status codes, make resources identifiable. This gets you 80% of the benefit.
Consider HATEOAS selectively. Adding links to related resources helps discoverability without full HATEOAS commitment.
Keep statelessness. This is the most valuable constraint. Never store session state on the server.
Enable caching. Add
Cache-Controlheaders. This scales your read traffic.
When Evaluating APIs
Ask these questions:
- Are resources clearly identified by URIs?
- Do HTTP methods match their semantics?
- Are responses self-describing (Content-Type, status codes)?
- Is the API stateless?
- Can responses be cached?
If yes to all, you have a well-designed HTTP API—even if it’s not “full REST.”
When Discussing REST
Use precise language:
- “RESTful” → API following all REST constraints including HATEOAS
- “REST-like” or “RESTish” → API using REST conventions (Level 2)
- “HTTP API” → API using HTTP correctly without REST claims
This prevents endless debates about what is or isn’t REST.
What’s Next
This guide covered REST as an architectural style—what it really means, why it exists, and how to evaluate it.
For deeper topics like:
- Modeling complex resources and relationships
- When to consciously break REST constraints
- Refactoring poorly designed APIs
- Advanced HATEOAS patterns
See the upcoming course: Professional REST Design: Criteria and Trade-offs.
Related Vocabulary Terms
Deepen your understanding:
- REST - The architectural style in depth
- Resource - The core abstraction of REST
- Stateless - Why and how to eliminate server-side session state
- RESTful API - What makes an API truly RESTful
- Client-Server - The fundamental separation pattern
- HTTP - The protocol that implements REST on the web