🌐 Section 6 · Question #11

What is RESTful API design

In RESTful design, a web service is structured around three foundational pillars:


🟢 Junior Level

RESTful API Design is an architectural approach to designing web APIs centered around Resource-Oriented Architecture (ROA), leveraging the constraints of REST (Representational State Transfer) and the standard semantics of the HTTP protocol (RFC 9110).

In RESTful design, a web service is structured around three foundational pillars:

  1. Resources (Nouns): Domain entities (users, orders, products, invoices) addressed via canonical URIs using plural nouns (/users, /orders).
  2. Operations (HTTP Verbs): Standard HTTP methods declare the action to be performed (GET to read, POST to create, PUT to replace, PATCH to partially update, DELETE to remove).
  3. Outcomes (HTTP Status Codes): The server reports execution results using standard status codes (200 OK, 201 Created, 204 No Content, 400 Bad Request, 404 Not Found, 500 Server Error).

Canonical RESTful CRUD Mapping

HTTP Verb Target URI Semantic Intent Standard Success Response
GET /api/v1/users Retrieve a paginated collection of users 200 OK
GET /api/v1/users/42 Retrieve a single user with ID 42 200 OK
POST /api/v1/users Create a new subordinate user 201 Created + Location
PUT /api/v1/users/42 Completely replace user 42 200 OK or 204 No Content
PATCH /api/v1/users/42 Partially update fields of user 42 200 OK
DELETE /api/v1/users/42 Remove user 42 from the server 204 No Content

Core URI Anti-Patterns to Avoid

  • ❌ GET /api/v1/getUsers — Verbs in the URI duplicate the HTTP method GET.
  • ❌ POST /api/v1/deleteUser?id=42 — Uses POST for a non-idempotent delete action instead of DELETE.
  • ❌ POST /api/v1/user/save — RPC/SOAP procedural style; mixes singular nouns and procedural verbs.

🟡 Middle Level

The Richardson Maturity Model (RMM)

Leonard Richardson defined a 4-tier model evaluating the degree of REST adherence in web APIs:

┌────────────────────────────────────────────────────────┐
│  Level 3: HATEOAS (Hypermedia as the Engine of State)  │
├────────────────────────────────────────────────────────┤
│  Level 2: HTTP Verbs & Status Codes (Industry Standard)│
├────────────────────────────────────────────────────────┤
│  Level 1: Resources & Discrete URIs (/users/42)        │
├────────────────────────────────────────────────────────┤
│  Level 0: The Swamp of POX (RPC over HTTP POST)        │
└────────────────────────────────────────────────────────┘
  • Level 0 (The Swamp of POX): HTTP serves merely as a transport tunnel. All requests are sent via POST to a single endpoint (/api/service) with operations encoded in XML/JSON (SOAP, XML-RPC).
  • Level 1 (Resources): Introduces discrete URIs for individual entities (/users/1, /orders/2), but operations might still use a single method (e.g., all actions dispatched via POST).
  • Level 2 (HTTP Verbs & Status Codes): The gold standard of commercial enterprise APIs. Employs correct HTTP methods (GET, POST, PUT, DELETE), status codes (201, 204, 404), and standard headers.
  • Level 3 (HATEOAS): The server returns hypermedia links (_links) alongside data, guiding clients dynamically through permissible state transitions.

Filtering, Sorting, and Pagination

Subset operations must be expressed via Query Parameters, never through custom path segments:

  • Offset/Limit Pagination: GET /api/v1/orders?offset=20&limit=10
  • Cursor-Based Pagination (Keyset): GET /api/v1/orders?cursor=eyJpZCI6MTAxfQ&limit=20 Critical for high-frequency feeds; eliminates record skipping caused by concurrent inserts.
  • Multi-Field Sorting: GET /api/v1/products?sort=price,desc&sort=name,asc
  • Attribute Filtering: GET /api/v1/tickets?status=OPEN&priority=HIGH

Mitigating Over-Fetching and Under-Fetching in REST

Unlike GraphQL, REST endpoints risk over-fetching (returning unnecessary fields) or under-fetching (requiring N+1 HTTP calls):

  1. Sparse Fieldsets: Clients request only required fields: GET /api/v1/users/42?fields=id,email,displayName
  2. Resource Embedding / Expansion: Clients request related sub-resources in a single round-trip: GET /api/v1/orders/101?embed=customer,items

🔴 Senior Level

Design-First (API-First) vs. Code-First

In enterprise microservices, the choice of contract development strategy determines velocity and integration stability:

Criterion Design-First (API-First) Code-First
Workflow OpenAPI 3.1 specification written in YAML first; code is generated from contract. Spring @RestController written first; Swagger documentation generated via SpringDoc.
Parallel Development Frontend, mobile, and backend teams develop in parallel against mock servers (Prism). Client teams are blocked waiting for backend endpoints to be deployed.
Contract Integrity 100% adherence guaranteed; enforced via contract linters (Spectral) in CI/CD. Documentation can diverge from actual runtime serialization behavior.
Tooling openapi-generator-maven-plugin, Stoplight, SwaggerHub. springdoc-openapi-starter-webmvc-ui.

Spring MVC Request Processing Lifecycle

Under the hood, incoming REST calls are dispatched through the Spring Web framework:

  1. DispatcherServlet intercepts the incoming HTTP request.
  2. RequestMappingHandlerMapping inspects the URL, HTTP method, and consumes/produces headers to locate the corresponding @RestController handler method.
  3. HandlerAdapter invokes method resolvers (HandlerMethodArgumentResolver) to populate @RequestBody, @PathVariable, and @RequestParam.
  4. HttpMessageConverter (Jackson MappingJackson2HttpMessageConverter) deserializes JSON into Java DTOs and serializes responses back to JSON.

Idiomatic Spring Boot 3 REST Controller

package com.example.controller;

import com.example.dto.CreateUserRequest;
import com.example.dto.UpdateUserRequest;
import com.example.dto.UserResponseDto;
import com.example.service.UserService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;

@RestController
@RequestMapping("/api/v1/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    // 1. Paginated & Sorted Collection: 200 OK
    @GetMapping
    public ResponseEntity<Page<UserResponseDto>> getUsers(
            @RequestParam(required = false) String status,
            @PageableDefault(size = 20, sort = "createdAt") Pageable pageable) {
        return ResponseEntity.ok(userService.findAll(status, pageable));
    }

    // 2. Single Resource: 200 OK
    @GetMapping("/{id}")
    public ResponseEntity<UserResponseDto> getUserById(@PathVariable Long id) {
        return ResponseEntity.ok(userService.findById(id));
    }

    // 3. Subordinate Resource Creation: 201 Created + Location Header
    @PostMapping
    public ResponseEntity<UserResponseDto> createUser(@Valid @RequestBody CreateUserRequest request) {
        UserResponseDto created = userService.create(request);

        // Build canonical URI of newly created resource: /api/v1/users/{id}
        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(created.id())
                .toUri();

        return ResponseEntity.created(location).body(created);
    }

    // 4. Complete Replacement: 200 OK
    @PutMapping("/{id}")
    public ResponseEntity<UserResponseDto> replaceUser(@PathVariable Long id, 
                                                       @Valid @RequestBody UpdateUserRequest request) {
        return ResponseEntity.ok(userService.update(id, request));
    }

    // 5. Deletion: 204 No Content
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();
    }
}

URL Canonicalization & Distributed Tracing

  1. Trailing Slash Disambiguation: In HTTP specifications, /users and /users/ represent two distinct URIs. Production API gateways (Envoy, Kong) or Spring Web configurations enforce canonicalization by redirecting /users/ to /users via 301 Moved Permanently.
  2. Distributed Tracing Headers: Every enterprise REST API must accept, generate, and propagate correlation headers:
    X-Correlation-ID: 1d9c4f72-9bb2-47e2-8924-d2e850b55ff8
    Traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
    

4 Tricky Questions

1. What is the Richardson Maturity Model, and why is Level 3 (HATEOAS) rarely adopted in enterprise production?

Answer: The Richardson Maturity Model categorizes APIs from Level 0 (tunneling RPC over HTTP), Level 1 (individual resource URIs), Level 2 (HTTP verbs and status codes), to Level 3 (HATEOAS).

  • Level 3 is rarely adopted because it inflates JSON response payloads by 30–50% with hypermedia link graphs (_links).
  • Modern frontend and mobile engineering relies on static type generation and compile-time contract checking (via OpenAPI or TypeScript generators). Parsing runtime dynamic link graphs adds unnecessary frontend state complexity without substantial operational benefit.

2. Why is returning the Location header mandatory when responding with 201 Created on a POST request?

Answer: Under RFC 9110 Section 10.2.2, when a server successfully creates a subordinate resource via POST, it is required to identify the newly created resource by returning its canonical URI in the Location header (e.g., Location: /api/v1/users/42).

  • This allows clients to immediately reference, fetch, or inspect the created entity without relying on client-side URI string concatenation.

3. How do Design-First and Code-First approaches differ, and why do large enterprise architectures mandate Design-First?

Answer:

  • Code-First: Developers implement Java @RestController endpoints first, and OpenAPI schemas are generated after the fact. This risks contract divergence and forces client teams to wait until backend implementations are deployed.
  • Design-First (API-First): The contract (OpenAPI 3.1 YAML) is designed, reviewed, and finalized first.
    • Client and backend teams work in parallel against Prism mock servers.
    • Contract linters (Spectral) enforce organizational naming standards before code is written.
    • Code generators (openapi-generator-maven-plugin) automatically produce server stubs and client SDKs, eliminating contract drift.

4. How should non-CRUD or complex business operations (e.g., “lock user account”, “transfer money”, “approve loan”) be designed in RESTful APIs without using verbs in URLs?

Answer:

  1. Subordinate Action Resources: Model the operation as the creation of an action or audit resource: POST /api/v1/users/42/lock-events or POST /api/v1/loans/10/approval-decisions.
  2. State Mutation via PATCH: Modify the resource state attribute: PATCH /api/v1/users/42 with payload {"status": "LOCKED"}.
  3. Transaction Resources: For operations spanning multiple entities (financial transfers), treat the operation itself as a first-class domain resource: POST /api/v1/transfers with body {"sourceAccount": 1, "targetAccount": 2, "amount": 500}.

🎯 Interview Cheat Sheet

30-Second Elevator Pitch

RESTful API Design is a Resource-Oriented Architecture (ROA) where APIs are structured around resources (plural nouns like /users), operations are performed using standard HTTP verbs (GET, POST, PUT, PATCH, DELETE), and results are conveyed via standard HTTP status codes.

In practice, the global industry standard operates at Richardson Maturity Level 2. Filtering, sorting, and pagination are handled via query parameters. In large enterprises, the API-First (Design-First) approach using OpenAPI 3.1 is standard practice to enable parallel development and eliminate contract drift.


Red Flags (What NOT to Say)

  • ❌ “Using verbs in URIs like /getUsers or /deleteUser makes the API clearer and is standard REST.” (Verbs in URIs violate ROA; actions must be expressed by HTTP verbs).
  • ❌ “Always return HTTP 200 OK and describe any errors inside the JSON body.” (The RPC anti-pattern that breaks HTTP caching, proxies, and observability).
  • ❌ “An API cannot be called REST unless it fully implements Level 3 HATEOAS.” (95% of commercial production REST APIs operate successfully at Richardson Level 2).
  • ❌ “Query parameters and Path variables are completely interchangeable.” (Path variables identify the resource; Query parameters filter, sort, or paginate).