Meta Description: Build truly RESTful APIs by following these 7 core principles: resources, HTTP methods, statelessness, representations, HATEOAS, caching, and layered architecture.
Keywords: rest principles, api design patterns, restful architecture, rest constraints, api best practices, rest api design
Word Count: ~2,400 words
You call your API "RESTful." But is it really?
Most APIs labeled "REST" are just HTTP APIs. They use HTTP methods and return JSON, but they don't follow REST principles.
REST (Representational State Transfer) is an architectural style with specific constraints. Follow these 7 principles to build truly RESTful APIs.
Principle 1: Resource-Based URLs
REST APIs are built around resources, not actions. Resources are things (nouns), not operations (verbs).
What Are Resources?
Resources represent entities in your system: - Pets - Orders - Users - Appointments - Payments
Each resource has a unique identifier and can be accessed via a URL.
Resource URLs
Good (resource-based):
GET /pets
GET /pets/123
POST /pets
PUT /pets/123
DELETE /pets/123
Bad (action-based):
GET /getAllPets
GET /getPetById?id=123
POST /createPet
POST /updatePet
POST /deletePet
The URL identifies the resource. The HTTP method specifies the action.
Nested Resources
Resources can have sub-resources:
GET /pets/123/vaccinations
GET /pets/123/vaccinations/456
POST /pets/123/vaccinations
This represents the relationship: "vaccinations belonging to pet 123."
Keep nesting shallow (2-3 levels max). Deep nesting creates unwieldy URLs:
Too deep:
GET /users/123/pets/456/vaccinations/789/appointments/012
Better:
GET /vaccinations/789/appointments/012
If you need the context, use query parameters:
GET /appointments/012?vaccination_id=789
Collection vs Individual Resources
Use plural names for collections:
GET /pets ← Collection
GET /pets/123 ← Individual resource
Don't mix singular and plural:
Inconsistent:
GET /pet/123 ← Singular
GET /pets ← Plural
Consistent:
GET /pets/123 ← Plural
GET /pets ← Plural
Principle 2: Use HTTP Methods Correctly
HTTP methods have specific meanings. Use them correctly.
GET: Retrieve Resources
GET retrieves resources without side effects. It's safe and idempotent.
GET /pets/123
Safe: Doesn't modify server state Idempotent: Multiple identical requests have the same effect as one
Never use GET for operations that modify data:
Wrong:
GET /pets/123/delete
GET /orders/456/cancel
Right:
DELETE /pets/123
POST /orders/456/cancellations
POST: Create Resources
POST creates new resources:
POST /pets
{
"name": "Max",
"species": "DOG"
}
Response: 201 Created
Location: /pets/019b4132-70aa-764f-b315-e2803d882a24
POST is neither safe nor idempotent. Multiple identical POST requests create multiple resources.
PUT: Replace Resources
PUT replaces an entire resource:
PUT /pets/123
{
"name": "Max",
"species": "DOG",
"breed": "Golden Retriever",
"age": 3
}
PUT is idempotent. Sending the same PUT request multiple times has the same effect as sending it once.
Important: PUT replaces the entire resource. If you omit fields, they're removed:
PUT /pets/123
{
"name": "Max"
}
This removes all other fields (species, breed, age).
PATCH: Update Resources Partially
PATCH updates specific fields:
PATCH /pets/123
{
"age": 4
}
Only the age field changes. Other fields remain unchanged.
PATCH is idempotent when updating to specific values (not when incrementing).
DELETE: Remove Resources
DELETE removes resources:
DELETE /pets/123
Response: 204 No Content
DELETE is idempotent. Deleting the same resource multiple times has the same effect as deleting it once (the resource is gone).
Other Methods
HEAD: Like GET but returns only headers (no body). Useful for checking if a resource exists.
HEAD /pets/123
Response: 200 OK (if exists) or 404 Not Found
OPTIONS: Returns allowed methods for a resource.
OPTIONS /pets/123
Response: 200 OK
Allow: GET, PUT, PATCH, DELETE
QUERY: For complex searches (RFC 9535).
QUERY /pets/search
{
"filters": {
"species": "DOG",
"age": {"min": 1, "max": 5}
}
}
Principle 3: Stateless Communication
Each request must contain all information needed to process it. The server doesn't store client state between requests.
What Is Stateless?
Stateless: Each request is independent. The server doesn't remember previous requests.
Stateful: The server remembers client state across requests.
Why Stateless?
Scalability: Any server can handle any request. No need to route clients to specific servers.
Reliability: Server crashes don't lose client state.
Simplicity: No session management complexity.
How to Be Stateless
Bad (stateful):
POST /login
{ "username": "user", "password": "pass" }
Response: Session ID stored on server
GET /pets
Cookie: session_id=abc123
Server looks up session to get user identity
The server stores session state. Requests depend on previous requests.
Good (stateless):
POST /auth/token
{ "username": "user", "password": "pass" }
Response: { "token": "eyJhbGc..." }
GET /pets
Authorization: Bearer eyJhbGc...
Server validates token (no session lookup)
The token contains all needed information. The server doesn't store session state.
Client State vs Application State
Client state (stored by client): Current page, filters, sort order Application state (stored by server): User data, pets, orders
REST APIs manage application state, not client state. Clients manage their own state.
Principle 4: Use Standard Representations
Resources have representations (JSON, XML, HTML). Use standard formats and content negotiation.
Content Negotiation
Clients specify desired format with the Accept header:
GET /pets/123
Accept: application/json
Response:
Content-Type: application/json
{
"id": "123",
"name": "Max"
}
GET /pets/123
Accept: application/xml
Response:
Content-Type: application/xml
<pet>
<id>123</id>
<name>Max</name>
</pet>
Standard Media Types
Use standard media types: - application/json for JSON - application/xml for XML - text/html for HTML - application/pdf for PDF
Don't invent custom types unless necessary.
Consistent Structure
Keep response structure consistent:
Good:
{
"data": [...],
"pagination": {...},
"meta": {...}
}
Every collection response follows this structure.
Bad:
// Sometimes
{
"pets": [...]
}
// Other times
{
"results": [...],
"total": 100
}
Inconsistent structure confuses clients.
Principle 5: HATEOAS (Hypermedia)
Responses include links to related resources and available actions.
Why HATEOAS?
Discoverability: Clients discover available actions from responses.
Decoupling: Clients don't hardcode URLs.
Evolvability: You can change URLs without breaking clients.
Example
{
"id": "123",
"name": "Max",
"status": "AVAILABLE",
"_links": {
"self": {
"href": "/v1/pets/123"
},
"adopt": {
"href": "/v1/pets/123/adoptions",
"method": "POST"
},
"vaccinations": {
"href": "/v1/pets/123/vaccinations"
}
}
}
Clients follow links instead of constructing URLs.
When to Use HATEOAS
HATEOAS adds complexity. Use it when: - Your API has complex workflows - URLs change frequently - You want maximum decoupling
For simple APIs, HATEOAS might be overkill.
Principle 6: Cacheable Responses
Responses should indicate whether they can be cached.
Cache-Control Header
GET /pets/123
Response:
Cache-Control: public, max-age=3600
ETag: "abc123"
{
"id": "123",
"name": "Max"
}
Cache-Control: public, max-age=3600 means: - Response can be cached by anyone (public) - Cache is valid for 3600 seconds (1 hour)
ETags for Conditional Requests
ETags enable conditional requests:
GET /pets/123
If-None-Match: "abc123"
Response: 304 Not Modified
The server returns 304 if the resource hasn't changed. This saves bandwidth.
When to Cache
Cache: - GET requests for stable data - Public resources - Expensive computations
Don't cache: - POST/PUT/PATCH/DELETE requests - User-specific data (unless using private cache) - Real-time data
Principle 7: Layered System
Clients shouldn't know if they're connected directly to the server or through intermediaries.
What Is a Layered System?
Your API might have: - Load balancers - API gateways - Caching proxies - CDNs
Clients shouldn't care. The API behaves the same regardless of layers.
Benefits
Scalability: Add load balancers without changing clients.
Security: Add firewalls and gateways transparently.
Caching: Add CDNs without client changes.
Implementation
Use standard HTTP. Avoid custom protocols or assumptions about direct connections.
Good:
GET /pets/123
Authorization: Bearer token
Works through any HTTP proxy or gateway.
Bad:
Custom binary protocol over TCP
Doesn't work through standard HTTP infrastructure.
Putting It All Together
A truly RESTful API:
- Uses resource-based URLs:
/pets/123, not/getPet?id=123 - Uses HTTP methods correctly: GET for retrieval, POST for creation, etc.
- Is stateless: Each request contains all needed information
- Uses standard representations: JSON with consistent structure
- Includes hypermedia links: Responses guide clients to next actions
- Indicates cacheability: Proper
Cache-ControlandETagheaders - Works through layers: Functions correctly through proxies and gateways
Example: Modern PetStore API
The Modern PetStore API follows all 7 principles:
GET /v1/pets/019b4132-70aa-764f-b315-e2803d882a24
Authorization: Bearer eyJhbGc...
Accept: application/json
Response: 200 OK
Cache-Control: public, max-age=300
ETag: "abc123"
Content-Type: application/json
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"name": "Max",
"species": "DOG",
"status": "AVAILABLE",
"_links": {
"self": {
"href": "/v1/pets/019b4132-70aa-764f-b315-e2803d882a24"
},
"adopt": {
"href": "/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/adoptions",
"method": "POST"
},
"vaccinations": {
"href": "/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/vaccinations"
}
}
}
This response demonstrates: - ✅ Resource-based URL - ✅ Correct HTTP method (GET) - ✅ Stateless (token in request) - ✅ Standard representation (JSON) - ✅ Hypermedia links - ✅ Cacheable (Cache-Control, ETag) - ✅ Works through layers (standard HTTP)
Conclusion
Most "REST" APIs are just HTTP APIs. True REST requires following all 7 principles.
You don't have to implement everything at once. Start with the basics (resources, HTTP methods, statelessness) and add advanced features (HATEOAS, caching) as needed.
The Modern PetStore API shows how to implement all 7 principles in a production-ready API. Use it as a reference for your own APIs.
Related Articles: - Why the Old Swagger Petstore Teaches Bad API Design- Stop Using Action Verbs in Your API URLs- HATEOAS: Should Your REST API Include Hypermedia Links?