HomeCurriculumAPI DesignREST Resource Architecture & HTTP Verbs
REST Resource Architecture & HTTP Verbs

REST Resource Architecture & HTTP Verbs

Master REST architectural foundations, evaluating Fielding's constraints, Richardson Maturity Model levels, safe versus idempotent HTTP methods, and idempotency key middleware.

DT
Authored by DevThesis Systems Editorial Team
Reviewed for Technical Accuracy & Code Determinism

Executive Architectural Overview#

Modern distributed software engineering relies fundamentally on Application Programming Interfaces (APIs) as the contract bridging microservices, mobile clients, web applications, and partner integrations. Among architectural styles, Representational State Transfer (REST) remains the ubiquitous foundation of web services.

Introduced by Dr. Roy Fielding in his seminal 2000 doctoral dissertation ("Architectural Styles and the Design of Network-based Software Architectures"), REST was formulated not as a rigid protocol or standard, but as a coordinated set of architectural constraints designed to preserve the foundational properties that enabled the World Wide Web to scale globally: scalability, generality, component independence, and evolvability.

Despite its ubiquity, many commercial APIs violate foundational REST principles, reducing REST to arbitrary JSON serialization over HTTP tunnels ("RPC-over-HTTP" or "The Swamp of POX"). Designing enterprise-grade, resilient APIs requires a rigorous understanding of Fielding's six core constraints, the Richardson Maturity Model, HTTP semantic verbs, mathematical idempotency, partial update specifications (RFC 6902 vs. RFC 7396), structured machine-readable error contracts (RFC 9457), and cursor-based pagination mechanics.

text
       RPC-STYLE (ANTI-PATTERN) vs. RESOURCE-ORIENTED REST
  ┌────────────────────────────────────────────────────────┐
  │ RPC-STYLE / SOAP / TUNNELING (Action-Centric)          │
  │ POST /api/v1/updateUserSalary                          │
  │ POST /api/v1/deleteUserById                            │
  │ POST /api/v1/getUserOrders                             │
  │ (Verbs in URIs; HTTP treated as a dumb transport pipe) │
  └──────────────────────────┬─────────────────────────────┘
                             │ Architectural Shift
                             ▼
  ┌────────────────────────────────────────────────────────┐
  │ RESOURCE-ORIENTED REST (Resource-Centric)              │
  │ PATCH  /api/v1/users/{id}    (Payload: { "salary": ..})│
  │ DELETE /api/v1/users/{id}    (HTTP status 204)         │
  │ GET    /api/v1/users/{id}/orders (Query params filter) │
  │ (Nouns identify resources; HTTP verbs declare actions) │
  └────────────────────────────────────────────────────────┘

1. Fielding's Architectural Constraints & Richardson Maturity Model#

REST is defined strictly by the simultaneous application of six architectural constraints. Violating any of the five mandatory constraints disqualifies an architecture from being genuinely RESTful.

The Six Foundational Constraints#

  1. Client-Server Separation of Concerns:

The user interface concerns (clients) are decoupled from data storage concerns (servers). Clients are not concerned with data persistence, allowing them to remain portable across platforms (iOS, Android, React, CLI). Servers are not concerned with UI state, simplifying server architecture and enabling independent horizontal scaling.

  1. Statelessness:

Communication between client and server must be completely stateless. Every incoming request from a client must contain all necessary context, credentials, and parameters for the server to understand and process the request. The server must never store client session state in memory across requests.

- Architectural Benefit: Servers can be destroyed, restarted, or scaled horizontally behind load balancers with zero session synchronization or sticky session affinity.

  1. Cacheability:

Responses must explicitly declare themselves as cacheable or non-cacheable (via HTTP headers such as Cache-Control, ETag, and Last-Modified). If a response is cacheable, intermediate client caches and Content Delivery Networks (CDNs) can reuse response bytes for equivalent subsequent requests, eliminating server load and reducing network latency.

  1. Layered System:

A client cannot tell whether it is connected directly to the end server or an intermediate proxy, reverse proxy, API gateway, cache, or load balancer. Intermediary layers can enforce rate limiting, SSL termination, and security isolation without altering client-server interactions.

  1. Code-on-Demand (Optional):

Servers can temporarily extend client functionality by transferring executable code (e.g., JavaScript widgets, WebAssembly binaries). This is the only optional constraint.

  1. Uniform Interface (The Core Differentiator):

The central constraint separating REST from other network architectures. The uniform interface comprises four sub-constraints:

- Identification of Resources: Individual resources are uniquely identified in requests via Uniform Resource Identifiers (URIs).

- Manipulation through Representations: A client holds a representation of a resource (e.g., JSON, XML) and has sufficient information to modify or delete the resource on the server.

- Self-Descriptive Messages: Each message includes sufficient metadata (MIME media types, caching headers) describing how to parse and process the message.

- HATEOAS (Hypermedia As The Engine Of Application State): Clients navigate application state dynamically via hypermedia links provided in representations, without hardcoding URI routing logic.

text
                  THE RICHARDSON MATURITY MODEL
                  ─────────────────────────────
 Level 3: HATEOAS (Hypermedia Controls embedded in responses)
    ▲
    │  Level 2: HTTP Verbs & Standard Status Codes (GET, POST, DELETE, 200, 404)
    │
    │  Level 1: Discrete Individual Resources (/users, /orders/981)
    │
    │  Level 0: The Swamp of POX (Single URI endpoint, POST only, XML/JSON tunneling)
  • Level 0 (The Swamp of POX): Single URI (e.g., /api or /service.asmx), single HTTP method (POST), routing actions via request body wrappers (e.g., XML-RPC, SOAP, GraphQL).
  • Level 1 (Resources): Multiple discrete URI endpoints addressing individual resources (/customers, /orders), but still misusing HTTP methods.
  • Level 2 (HTTP Verbs & Status Codes): Exploits HTTP verbs (GET, POST, PUT, DELETE) according to their protocol specifications and returns standard HTTP status codes (200 OK, 201 Created, 204 No Content, 400 Bad Request, 404 Not Found). The vast majority of production enterprise APIs operate at Level 2.
  • Level 3 (HATEOAS): Representations include navigational hypermedia links (_links) dictating permissible subsequent state transitions.

2. HTTP Semantic Verbs: Safety, Idempotency & Cacheability#

The HTTP/1.1 specification (RFC 7231 / RFC 9110) establishes precise mathematical definitions for method properties that dictate network resilience and proxy behavior.

text
+────────+──────────+──────────────+────────────+─────────────────────────────────────────+
| Verb   | Safe?    | Idempotent?  | Cacheable? | Primary Functional Role                 |
+────────+──────────+──────────────+────────────+─────────────────────────────────────────+
| GET    | YES      | YES          | YES        | Retrieve resource representation.       |
| HEAD   | YES      | YES          | YES        | Retrieve headers only (metadata check). |
| POST   | NO       | NO           | Conditional| Subordinate creation / processing action|
| PUT    | NO       | YES          | NO         | Full replacement of resource state.     |
| PATCH  | NO       | Spec: NO*    | Conditional| Partial delta update of resource.       |
| DELETE | NO       | YES          | NO         | Purge / soft-delete target resource.    |
| OPTIONS| YES      | YES          | NO         | Pre-flight capability discovery (CORS). |
+────────+──────────+──────────────+────────────+─────────────────────────────────────────+

Formal Mathematical Definitions#

  1. Safe Methods (f(x) ──> x):

A method is Safe if its semantics are read-only: invoking the method produces zero side-effects on the server's resource state. Safe methods can be pre-fetched by browsers, web crawlers, and CDNs without risk of altering transactional data.

State_{after} = State_{before}2. Idempotent Methods (f(f(x)) = f(x)):

A method is Idempotent if the intended effect on the server of multiple identical requests is identical to the effect of a single request.

for all n ≥ 1, f^n(x) = f(x)- Operational Meaning: If a client issues a PUT or DELETE request, and the connection drops before the server's HTTP response reaches the client, the client or network proxy can safely retry the request automatically without risk of duplicate billing, duplicate row creation, or corrupted state.


3. Mathematical Idempotency in Distributed Networks#

In an unreliable distributed network, packet loss occurs across three distinct failure windows:

text
  Client                     Network Gateway                   Server
    │                               │                             │
    ├─── 1. Write Request ─────────►│                             │ (Lost in transit: Safe retry)
    │                               ├─── 2. Process Request ─────►│
    │                               │                             │ (Server processes & updates DB)
    │                               │◄── 3. Return 200 OK ────────┤
    │◄── 4. Drop Packet (Timeout) ──x                             │ (Lost on return: RETRY HAZARD!)
    │                                                             │
   [Client Times Out: Did the server charge the customer's card, or did it fail?]

If the client issues a non-idempotent POST /api/v1/charges to bill a credit card USD 100, and network packet #4 is dropped by a router, the client receives a SocketTimeoutException. If the client naively retries, the server processes the charge a second time, billing the customer USD 200!

The IETF Idempotency-Key Specification#

To make non-idempotent HTTP actions (POST, non-idempotent PATCH) safe against network retries, modern enterprise architectures implement the IETF Draft Idempotency-Key Header:

http
POST /api/v1/payments HTTP/1.1
Host: api.bank.com
Idempotency-Key: 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
Content-Type: application/json

{
  "account_id": "ACC-9921",
  "amount_cents": 10000,
  "currency": "USD"
}

The Distributed Idempotency State Machine#

The server enforces idempotency using an in-memory distributed store (e.g., Redis) that tracks the lifecycle of the idempotency key:

text
                IDEMPOTENCY KEY LIFECYCLE STATE MACHINE
                ───────────────────────────────────────
                      Incoming Request + Key
                                 │
                                 ▼
                     Atomic Check in Redis (SETNX)
                                 │
         ┌───────────────────────┴───────────────────────┐
         ▼                                               ▼
 Key Already Exists                              Key Inserted Successfully
         │                                               │
 Is Status == COMPLETED ?                        Set Status: IN_FLIGHT (TTL: 120s)
   ├── YES ──► Return CACHED HTTP Status                 │
   │           and Cached Response Body!                 ▼
   │           (Zero duplicate processing)       Execute Database Business Logic
   │                                                     │
   └── NO (Status == IN_FLIGHT)                          ▼
       ──► Return HTTP 409 Conflict              Store Response Body + Status
           or 425 Too Early                      Set Status: COMPLETED (TTL: 24h)
                                                         │
                                                         ▼
                                                 Emit HTTP 201 Created to Client

4. Partial Updates: JSON Patch (RFC 6902) vs. JSON Merge Patch (RFC 7396)#

While PUT requires replacing the entire resource representation (omitted fields are overwritten with null or defaults), PATCH is designed for delta updates. The Internet Engineering Task Force (IETF) standardized two formal specifications for PATCH operations.

text
+───────────────────────────+──────────────────────────────────────────────────────────+
| JSON Patch (RFC 6902)     | JSON Merge Patch (RFC 7396)                              |
+───────────────────────────+──────────────────────────────────────────────────────────+
| • Content-Type:           | • Content-Type:                                          |
|   `application/json-patch+json`| `application/merge-patch+json`                     |
| • Sequence of atomic ops  | • Target partial JSON document matching resource shape.  |
|   (`add`, `remove`, `replace`,| • Cannot easily differentiate between deleting a field|
|   `move`, `copy`, `test`).|   and explicitly setting a field to null.                |
| • Explicit array indexing.| • Merging arrays replaces the entire array.              |
+───────────────────────────+──────────────────────────────────────────────────────────+

1. JSON Patch (RFC 6902)#

JSON Patch represents changes as an ordered array of operation objects. It supports the powerful test operation, which enforces optimistic concurrency control:

http
PATCH /api/v1/employees/402 HTTP/1.1
Content-Type: application/json-patch+json

[
  { "op": "test", "path": "/version", "value": 3 },
  { "op": "replace", "path": "/title", "value": "Principal Architect" },
  { "op": "add", "path": "/skills/0", "value": "Distributed Systems" },
  { "op": "remove", "path": "/temporary_badge_number" }
]

If the employee's current version is not 3, the test operation fails, the entire transaction aborts atomically, and the server returns HTTP 422 Unprocessable Entity or HTTP 409 Conflict.

2. JSON Merge Patch (RFC 7396)#

JSON Merge Patch uses a partial JSON document resembling the original resource. The server merges the patch recursively:

  • A field with a value updates that field.
  • A field set to null deletes that field from the resource.
  • An omitted field leaves the existing value untouched.
http
PATCH /api/v1/employees/402 HTTP/1.1
Content-Type: application/merge-patch+json

{
  "title": "Principal Architect",
  "temporary_badge_number": null
}

Limitation: Because null means "delete field", JSON Merge Patch cannot represent setting a nullable field to a genuine null value without bespoke workarounds.


5. Standardized Machine-Readable Error Contracts: RFC 9457 / RFC 7807#

Historically, API teams designed proprietary error envelopes, forcing client developers to write custom parsing adapters for every service:

json
// PROPRIETARY ANTI-PATTERN: Inconsistent, unstandardized error format
{
  "success": false,
  "err_code": 9812,
  "msg": "Invalid email address."
}

In 2023, the IETF updated and finalized RFC 9457 ("Problem Details for HTTP APIs"), obsoleting RFC 7807. RFC 9457 standardizes an extensible JSON format using the media type application/problem+json:

http
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Content-Language: en

{
  "type": "https://api.example.com/errors/insufficient-funds",
  "title": "Insufficient Funds",
  "status": 403,
  "detail": "Your current account balance of 14.20 is insufficient to complete the requested transfer of 50.00.",
  "instance": "/accounts/acc_901/transfers/txn_4401",
  "balance": 14.20,
  "currency": "USD"
}

Standard RFC 9457 Core Members#

  • type (URI reference): A URI that identifies the problem type. When dereferenced in a browser, it should return human-readable documentation. Defaults to about:blank if no specific documentation exists.
  • title (String): A short, human-readable summary of the problem type (must remain constant across occurrences of the same type).
  • status (Integer): The HTTP status code generated by the origin server for this occurrence.
  • detail (String): A human-readable explanation specific to this occurrence of the problem.
  • instance (URI reference): A URI identifying the specific occurrence of the problem (e.g., audit log ID or request endpoint).
  • Extension Members: Developers can append arbitrary domain-specific keys (e.g., balance, invalid_params).

6. Cursor-Based (Keyset) Pagination versus Offset Pagination#

Handling collections with millions of records requires pagination. The choice between Offset Pagination and Cursor-Based Pagination is a major architectural determinant of database health.

text
+───────────────────────────+──────────────────────────────────────────────────────────+
| Offset-Based Pagination   | Keyset / Cursor-Based Pagination                         |
+───────────────────────────+──────────────────────────────────────────────────────────+
| `?limit=20&offset=1000000`| `?limit=20&cursor=eyJpZCI6MTA0Mn0=`                      |
| • O(N) database cost.     | • O(1) database cost.                                    |
| • Server scans and drops  | • Queries index directly via WHERE (B-Tree Seek).        |
|   1,000,000 tuples in RAM!| • Zero read performance degradation at arbitrary depth.  |
| • Vulnerable to drift     | • Immune to pagination drift when records are inserted   |
|   (duplicate/skipped rows)|   or deleted concurrently.                               |
+───────────────────────────+──────────────────────────────────────────────────────────+
text
                  OFFSET DRIFT FAILURE MECHANISM
                  ──────────────────────────────
User on Page 1: Sees Items [1, 2, 3, 4, 5]
Concurrently: Item 0 is inserted at the top of the database!
User requests Page 2 (offset=5):
  Database offsets 5 rows: [Item 0, Item 1, Item 2, Item 3, Item 4]
  Returns: Items [5, 6, 7, 8, 9]
  RESULT: User sees Item 5 TWICE! (Duplicate read anomaly)

Keyset Cursor Mathematics#

Cursor pagination encodes the unique, immutable sort key of the last seen record into an opaque Base64 string:

text
  Last seen record: { id: 89412, created_at: "2026-10-30T10:14:22.102Z" }
  Base64 Cursor: "eyJpZCI6ODk0MTIsImMiOiIyMDI2LTEwLTMwVDEwOjE0OjIyLjEwMloifQ=="

The database query transforms from an expensive offset scan into a high-speed index range seek:

sql
-- HIGH-PERFORMANCE KEYSET INDEX SEEK
SELECT id, created_at, user_name, email
FROM users
WHERE (created_at, id) < ('2026-10-30T10:14:22.102Z', 89412)
ORDER BY created_at DESC, id DESC
LIMIT 20;

Because the query is backed by a composite B-Tree index on (created_at, id), the database engine seeks directly to the cursor coordinates in O(log N) time, scanning only the 20 requested leaf nodes.


7. Enterprise Checkpoint Practice Problems#

Test your mastery with these four real-world production checkpoint problems. Each problem presents a concrete business schema, data samples, edge cases, a failed attempt analysis, and a fully explained optimal solution.


Problem 1: Designing an Idempotent Payment Processing API Gateway Middleware#

Context: You are the lead API platform architect for a fintech unicorn. Your merchant API processes USD 50,000,000 daily.

  1. Implement a distributed idempotency middleware in Python using Redis.
  2. The middleware must intercept incoming requests with an Idempotency-Key header.
  3. If an identical key arrives while a transaction is actively running (IN_FLIGHT), return HTTP 409 Conflict with an RFC 9457 error body.
  4. If an identical key arrives after a transaction completed, immediately return the cached HTTP status code and JSON payload without executing the database charge.
  5. Keys must automatically expire from Redis after 24 hours.

Optimal Production Python/Redis Middleware Implementation:

python
import json
import redis
from fastapi import FastAPI, Request, Response, status
from fastapi.responses import JSONResponse

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

async def idempotency_middleware(request: Request, call_next):
    idempotency_key = request.headers.get("Idempotency-Key")
    
    # If no key is provided, pass through (or enforce header presence on critical paths)
    if not idempotency_key or request.method != "POST":
        return await call_next(request)
        
    redis_key = f"idempotency:{idempotency_key}"
    
    # Step 1: Attempt to acquire atomic lock with IN_FLIGHT status
    # SETNX semantics: returns True if key was set, False if key already exists
    acquired = r.set(
        redis_key, 
        json.dumps({"status": "IN_FLIGHT"}), 
        nx=True, 
        ex=120 # 2-minute safety lock timeout
    )
    
    if not acquired:
        # Key already exists: inspect current state
        cached_data = json.loads(r.get(redis_key))
        
        if cached_data.get("status") == "IN_FLIGHT":
            # Concurrent duplicate request in progress!
            return JSONResponse(
                status_code=status.HTTP_409_CONFLICT,
                content={
                    "type": "https://api.payments.com/errors/concurrent-mutation",
                    "title": "Conflict: Mutation In Flight",
                    "status": 409,
                    "detail": "A transaction with this Idempotency-Key is currently being processed. Please await response or retry later."
                },
                media_type="application/problem+json"
            )
        elif cached_data.get("status") == "COMPLETED":
            # Transaction already finished: Replay cached response
            return JSONResponse(
                status_code=cached_data["http_code"],
                content=cached_data["response_body"],
                headers={"X-Cache-Lookup": "HIT - Idempotent Replay"}
            )

    # Step 2: Execute actual business logic downstream
    try:
        response = await call_next(request)
        
        # Read response body stream
        response_body = [chunk async for chunk in response.body_iterator]
        response.body_iterator = iter(response_body)
        raw_body_str = b"".join(response_body).decode("utf-8")
        parsed_json_body = json.loads(raw_body_str) if raw_body_str else {}
        
        # Step 3: Store completed response in Redis with 24-hour retention
        completed_record = {
            "status": "COMPLETED",
            "http_code": response.status_code,
            "response_body": parsed_json_body
        }
        r.set(redis_key, json.dumps(completed_record), ex=86400) # 24 hours
        
        return response
        
    except Exception as exc:
        # If execution crashed, delete the lock so the merchant can retry
        r.delete(redis_key)
        raise exc

Architectural Walkthrough:

  1. r.set(..., nx=True, ex=120) provides atomic distributed mutual exclusion across all load-balanced API gateway instances.
  2. COMPLETED state stores both HTTP status and response payload, enabling instantaneous replay without executing database writes.
  3. Network dropped packets during return hops are neutralized completely.

Problem 2: Implementing JSON Patch (RFC 6902) Optimistic Concurrency#

Context: A multi-user project management system allows concurrent editing of tasks.

  1. When two users edit a task description simultaneously, the last write must not blindly overwrite the first write.
  2. Implement a JSON Patch endpoint processing RFC 6902 operations.
  3. Enforce optimistic concurrency using the test operation on an integer version field.
  4. If the version matches, update the task and increment the version atomically. If the version test fails, abort with RFC 9457 Problem Details.

Optimal Production Python Implementation:

python
from fastapi import FastAPI, HTTPException, status
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from typing import List, Any
import copy

app = FastAPI()

class JsonPatchOperation(BaseModel):
    op: str # "test", "replace", "add", "remove"
    path: str
    value: Any = None

# Mock database record
tasks_db = {
    "TASK-101": {
        "id": "TASK-101",
        "title": "Deploy API Gateway",
        "description": "Initial draft",
        "status": "IN_PROGRESS",
        "version": 4
    }
}

@app.patch("/api/v1/tasks/{task_id}")
def patch_task(task_id: str, operations: List[JsonPatchOperation]):
    if task_id not in tasks_db:
        return JSONResponse(
            status_code=404,
            content={"type": "about:blank", "title": "Not Found", "status": 404, "detail": "Task does not exist"},
            media_type="application/problem+json"
        )
        
    task = copy.deepcopy(tasks_db[task_id])
    
    # Process operations sequentially
    for operation in operations:
        field = operation.path.lstrip("/")
        
        if operation.op == "test":
            current_val = task.get(field)
            if current_val != operation.value:
                # Optimistic concurrency check failed!
                return JSONResponse(
                    status_code=status.HTTP_412_PRECONDITION_FAILED,
                    content={
                        "type": "https://api.pm.com/errors/version-mismatch",
                        "title": "Precondition Failed: Resource Version Outdated",
                        "status": 412,
                        "detail": f"Test operation on path '{operation.path}' expected value '{operation.value}', but found '{current_val}'. Please fetch the latest task state and rebase changes."
                    },
                    media_type="application/problem+json"
                )
        elif operation.op == "replace":
            task[field] = operation.value
        elif operation.op == "add":
            task[field] = operation.value
        elif operation.op == "remove":
            task.pop(field, None)
            
    # Atomically increment version and persist
    task["version"] += 1
    tasks_db[task_id] = task
    
    return task

Architectural Walkthrough:

  1. The test operation validates that the client's working version matches the server's current state.
  2. If another user edited the task and incremented version to 5, a patch testing version == 4 fails instantly before any mutative operations take effect.
  3. Returns 412 Precondition Failed with structured RFC 9457 metadata instructing the client to rebase.

Problem 3: Cursor-Based Pagination with Opaque Base64 Tokens#

Context: An enterprise audit logging service ingests 100,000,000 events.

  1. The API endpoint GET /api/v1/audit-logs must paginate records in descending chronological order (logged_at DESC, event_id DESC).
  2. Offset pagination (?offset=1000000) caused database thread pool starvation.
  3. Design a cursor pagination contract using opaque Base64 tokens.
  4. Return next_cursor and HATEOAS navigational links.

Optimal Production Python/SQL Implementation:

python
import base64
import json
from datetime import datetime

def generate_cursor(logged_at: datetime, event_id: int) -> str:
    payload = {
        "t": logged_at.isoformat(),
        "id": event_id
    }
    json_bytes = json.dumps(payload).encode("utf-8")
    return base64.urlsafe_b64encode(json_bytes).decode("utf-8")

def decode_cursor(cursor_str: str):
    json_bytes = base64.urlsafe_b64decode(cursor_str.encode("utf-8"))
    payload = json.loads(json_bytes.decode("utf-8"))
    return datetime.fromisoformat(payload["t"]), payload["id"]

def fetch_audit_logs(db_session, limit: int = 20, cursor: str = None):
    # Base query
    sql = '''
    SELECT event_id, logged_at, actor_id, action_type 
    FROM audit_events 
    '''
    params = {}
    
    if cursor:
        cursor_logged_at, cursor_id = decode_cursor(cursor)
        # Keyset Range Predicate backed by composite index (logged_at, event_id)
        sql += '''
        WHERE (logged_at < :c_logged_at) 
           OR (logged_at = :c_logged_at AND event_id < :c_id)
        '''
        params["c_logged_at"] = cursor_logged_at
        params["c_id"] = cursor_id
        
    sql += " ORDER BY logged_at DESC, event_id DESC LIMIT :limit"
    params["limit"] = limit + 1 # Fetch 1 extra record to determine has_more
    
    results = db_session.execute(sql, params).fetchall()
    
    has_more = len(results) > limit
    records = results[:limit]
    
    next_cursor = None
    if has_more and records:
        last_rec = records[-1]
        next_cursor = generate_cursor(last_rec.logged_at, last_rec.event_id)
        
    return {
        "data": [dict(r) for r in records],
        "pagination": {
            "limit": limit,
            "has_more": has_more,
            "next_cursor": next_cursor
        },
        "_links": {
            "self": f"/api/v1/audit-logs?limit={limit}" + (f"&cursor={cursor}" if cursor else ""),
            "next": f"/api/v1/audit-logs?limit={limit}&cursor={next_cursor}" if next_cursor else None
        }
    }

Architectural Walkthrough:

  1. Base64 encoding hides internal database column mechanics from API consumers.
  2. (logged_at < :c_t) OR (logged_at = :c_t AND event_id < :c_id) executes via a B-Tree range seek, scanning exactly limit + 1 index leaf blocks regardless of depth.
  3. Concurrently inserted logs do not cause page drift or duplicate rows.

Problem 4: HATEOAS Dynamic State Machine for E-Commerce Orders#

Context: An e-commerce platform manages order lifecycles (PENDING -> PAID -> PROCESSING -> SHIPPED -> DELIVERED or CANCELLED).

  1. Client developers frequently issue invalid HTTP requests (e.g., trying to cancel an order after it has shipped).
  2. Implement HATEOAS (Level 3 REST): Represent permissible next actions dynamically as hypermedia _links inside the order representation.
  3. The client UI renders action buttons (e.g., "Cancel Order", "Track Shipment") dynamically based on the presence of links, completely decoupling client UI logic from server business rules.

Optimal Production API Payload Representation (JSON-HAL Format):

json
// GET /api/v1/orders/ORD-9021 (Order Status: PAID)
{
  "order_id": "ORD-9021",
  "status": "PAID",
  "customer_id": "CUST-881",
  "total_amount": 149.99,
  "currency": "USD",
  "items_count": 3,
  "created_at": "2026-10-30T10:00:00Z",
  "_links": {
    "self": {
      "href": "/api/v1/orders/ORD-9021",
      "method": "GET"
    },
    "receipt": {
      "href": "/api/v1/orders/ORD-9021/receipt",
      "method": "GET"
    },
    "cancel": {
      "href": "/api/v1/orders/ORD-9021/cancellation",
      "method": "POST",
      "title": "Cancel order and initiate automatic refund"
    },
    "modify_shipping_address": {
      "href": "/api/v1/orders/ORD-9021/shipping-address",
      "method": "PUT"
    }
  }
}

Representational Shift when Order transitions to `SHIPPED`:

json
// GET /api/v1/orders/ORD-9021 (Order Status: SHIPPED)
{
  "order_id": "ORD-9021",
  "status": "SHIPPED",
  "tracking_number": "1Z999AA10123456784",
  "carrier": "UPS",
  "_links": {
    "self": {
      "href": "/api/v1/orders/ORD-9021",
      "method": "GET"
    },
    "tracking": {
      "href": "https://www.ups.com/track?loc=en_US&tracknum=1Z999AA10123456784",
      "method": "GET"
    },
    "return": {
      "href": "/api/v1/orders/ORD-9021/returns",
      "method": "POST",
      "title": "Initiate return merchandise authorization"
    }
    // "cancel" link is completely omitted because shipped orders cannot be cancelled!
  }
}

Architectural Walkthrough:

  1. In Level 3 REST, the server controls the state machine. The client inspects _links: if "cancel" is absent, the client UI automatically hides the "Cancel" button.
  2. If business rules change (e.g., allowing cancellations up to 10 minutes after shipping), only the server logic updates; zero client app updates or app-store redeployments are required.

Conclusion & Architectural Summary#

  1. Architectural Discipline: REST is governed by Fielding's six core constraints. Prioritize resource-oriented nouns over RPC verb tunneling.
  2. Method Semantics: Adhere strictly to method safety and idempotency: GET is safe; PUT and DELETE are idempotent; POST is non-idempotent.
  3. Idempotency Guarantees: Protect financial and transactional endpoints from distributed network retry hazards using atomic Idempotency-Key distributed locks.
  4. Standardized Contracts: Adopt RFC 9457 application/problem+json for machine-readable error contracts and JSON Patch (RFC 6902) for optimistic concurrency control.
  5. Scale with Cursors: Eliminate O(N) database offset pagination by implementing Base64 keyset cursor pagination over indexed columns.

Key Architectural Takeaways

  • REST is an architectural style defined by 6 core constraints, prominently client-server separation and statelessness.
  • Safe methods (GET, HEAD) cause no server side-effects; idempotent methods (GET, PUT, DELETE) satisfy f(f(x)) = f(x).
  • URIs should use plural nouns for collections (/orders) and rely on query parameters for filtering, sorting, and pagination.
  • Idempotency keys prevent duplicate mutations during network retries by caching responses in a distributed store.

Hands-on Code Playground Available

Execute, edit, and experiment with code for this topic in the live sandbox.

Practice Check • Quick Quiz

Test Your Understanding

Which HTTP method is defined as idempotent but NOT safe according to the HTTP/1.1 specification?

A
POST
B
PUT
C
GET
D
OPTIONS