Skip to content

REST Architectural Constraints — The Six Rules That Define RESTful Systems

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about REST Architectural Constraints. We cover key concepts, practical examples, and best practices to help you master this topic.

The six REST architectural constraints defined by Roy Fielding form the foundation of RESTful API design: uniform interface, client-server separation, statelessness, cacheability, layered system, and optional code-on-demand.

What You'll Learn

  • What each of the six REST constraints means
  • How each constraint contributes to API scalability and reliability
  • Which constraints are mandatory and which are optional

Why It Matters

Understanding the constraints helps you design APIs that leverage the web's strengths. Violating constraints leads to APIs that are harder to scale, cache, and evolve. Roy Fielding designed these constraints specifically to create the properties that make the web work at planetary scale.

Real-World Use

DodaTech's APIs follow all six REST constraints. The uniform interface ensures all resources use consistent naming and HTTP methods. Statelessness allows any server to handle any request. Caching reduces latency for threat intelligence queries. The layered system enables multiple proxy and CDN layers.

flowchart TD
    C1["Uniform Interface"] --> REST
    C2["Client-Server"] --> REST
    C3["Stateless"] --> REST
    C4["Cacheable"] --> REST
    C5["Layered System"] --> REST
    C6["Code-on-Demand (optional)"] --> REST
    REST -->|"produces"| S1["Scalability"]
    REST -->|"produces"| S2["Simplicity"]
    REST -->|"produces"| S3["Visibility"]
    REST -->|"produces"| S4["Portability"]
    REST -->|"produces"| S5["Reliability"]
    style REST fill:#dbeafe,stroke:#2563eb

1. Uniform Interface

The uniform interface simplifies the architecture by applying consistent conventions across all resources:

from flask import Flask, jsonify, request

app = Flask(__name__)

# Consistent resource identification
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = find_user(user_id)
    if user:
        return jsonify(user)
    return jsonify({"error": "not_found"}), 404

# Consistent resource manipulation through representations
@app.route('/api/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
    data = request.json
    user = update_user_data(user_id, data)
    return jsonify(user)

# Self-descriptive messages
@app.route('/api/users', methods=['GET'])
def list_users():
    users = get_all_users()
    return jsonify({
        "data": users,
        "count": len(users),
        "_links": {
            "self": {"href": "/api/users"},
            "create": {"href": "/api/users", "method": "POST"}
        }
    })

2. Client-Server Separation

The client and server evolve independently as long as the interface stays consistent:

// Client side - knows only the API contract
async function getUser(userId) {
  const response = await fetch(`/api/users/${userId}`, {
    headers: { 'Accept': 'application/json' }
  });
  return response.json();
}

// Server side - can change implementation freely
// Switching from PostgreSQL to MongoDB doesn't affect clients

3. Statelessness

Each request contains all information needed to process it:

# Good: stateless request
@app.route('/api/orders')
def get_orders():
    # State comes entirely from the request
    user_id = request.headers.get('X-User-Id')  # or auth token
    page = request.args.get('page', 1, type=int)
    limit = request.args.get('limit', 20, type=int)

    orders = get_user_orders(user_id, page, limit)
    return jsonify(orders)

# Bad: server keeps session state
# DON'T use server-side sessions for REST APIs

4. Cacheable

Responses must explicitly indicate cacheability:

from flask import make_response
from datetime import datetime, timedelta

@app.route('/api/public/threats')
def get_threats():
    threats = get_cached_threats()
    response = make_response(jsonify(threats))

    # Explicit cache control
    response.headers['Cache-Control'] = 'public, max-age=300'
    response.headers['ETag'] = calculate_etag(threats)
    response.headers['Expires'] = (
        datetime.utcnow() + timedelta(seconds=300)
    ).strftime('%a, %d %b %Y %H:%M:%S GMT')

    return response

Common Mistakes

1. Violating Statelessness with Server Sessions

Using server-side sessions breaks statelessness. If you need sessions, store the session identifier in a cookie and the data in a shared cache.

2. Not Setting Cache Headers

Without explicit cache headers, clients and proxies may cache responses incorrectly or not at all.

3. Inconsistent Resource Naming

Mixing /users/123 with /getUser?id=123 breaks the uniform interface. Always use consistent, noun-based, plural resource names.

4. Ignoring the Layered System

Without proper proxy headers, your API breaks when deployed behind a reverse proxy or CDN.

5. Making Code-on-Demand Mandatory

Code-on-demand (like JavaScript) is optional. Do not require it for your API to function.

Practice Questions

  1. Which REST constraint is optional?
  2. Why does statelessness improve scalability?
  3. How do you make a response cacheable?
  4. What does the uniform interface require?
  5. How does client-server separation enable independent evolution?

Answers

  1. Code-on-demand. 2. Any server can handle any request without session affinity. 3. Set Cache-Control and ETag headers. 4. Consistent resource identification, self-descriptive messages, and HATEOAS. 5. Clients and servers can change independently as long as the interface stays consistent.

Challenge

Build an API that demonstrates all six REST constraints: consistent naming (uniform interface), stateless auth headers, explicit cache headers, Layered Architecture with proxy headers, and HATEOAS links in responses.

FAQ

How many REST constraints are there?

Six: uniform interface, client-server, stateless, cacheable, layered system, code-on-demand.

Which REST constraint is optional?

Code-on-demand. The other five are mandatory for REST compliance.

Does REST require HATEOAS?

The uniform interface constraint includes HATEOAS as part of self-descriptive messages.

Can REST APIs use sessions?

Technically yes, but it violates the statelessness constraint and reduces scalability.

What is a layered system in REST?

The ability to insert proxies, gateways, firewalls, and CDNs between client and server.

Mini Project

Design and implement a RESTful API for a blog platform that demonstrates all six constraints: consistent resource naming, stateless JWT authentication, cacheable public endpoints, layered proxy architecture, self-descriptive HATEOAS responses, and optional code-on-demand for post preview rendering.

What's Next

  • Learn about the uniform interface constraint in detail
  • Explore statelessness in depth for scalable API design
  • Continue to caching strategies with Cache-Control and ETag

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro