Seven RESTful Design Principles

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

TRY NANO BANANA FOR FREE

Seven RESTful Design Principles

TRY NANO BANANA FOR FREE
Contents

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:

  1. Uses resource-based URLs: /pets/123, not /getPet?id=123
  2. Uses HTTP methods correctly: GET for retrieval, POST for creation, etc.
  3. Is stateless: Each request contains all needed information
  4. Uses standard representations: JSON with consistent structure
  5. Includes hypermedia links: Responses guide clients to next actions
  6. Indicates cacheability: Proper Cache-Control and ETag headers
  7. 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?