exit lab
Backend Engineering
Level 2·api·guide·beginner

REST API Design Fundamentals

Design stable resource APIs with correct HTTP semantics, validation, pagination, idempotency, errors, compatibility, and OpenAPI documentation.

50 minutes RFC 9110, RFC 9457, OpenAPI 3.2; examples use Spring MVC 6.2 Updated 2026-07-16
HTTPJSONOpenAPISpring MVC

Learning objectives

  • Map resource operations to HTTP method and status semantics
  • Design compatible DTO, error, pagination, and idempotency contracts

What it is

REST API design is the deliberate use of HTTP resource identifiers, methods, representations, and response semantics to create a contract that clients can understand and evolve against. REST does not require one universal response envelope or CRUD-only behavior.

Why it matters

Clients build retry, cache, validation, authorization, and error-handling logic around the contract. Ambiguous status codes, unstable JSON, offset-only pagination, or undocumented failure shapes turn implementation details into long-lived integration risk.

How it works

OperationMethodTypical success
List tasksGET /v1/tasks200 with page
Create taskPOST /v1/tasks201 + Location
Read taskGET /v1/tasks/{id}200 or 404
Replace taskPUT /v1/tasks/{id}200/204
Partial updatePATCH /v1/tasks/{id}200/204
Delete taskDELETE /v1/tasks/{id}204 (idempotent effect)

Simple example

task-page.json
{
  "items": [
    { "id": 42, "title": "Review settlement", "status": "OPEN" }
  ],
  "nextCursor": "eyJpZCI6NDJ9",
  "hasMore": true
}

A cursor should be opaque to clients and derived from a stable, unique ordering. If ordering is by created_at, use a unique tie-breaker such as id.

Backend example

TaskController.java
@PostMapping
ResponseEntity<TaskResponse> create(
        @Valid @RequestBody CreateTaskRequest request,
        @RequestHeader(value = "Idempotency-Key", required = false) String key) {
    TaskResponse created = taskService.create(request, key);
    URI location = URI.create("/v1/tasks/" + created.id());
    return ResponseEntity.created(location).body(created);
}

public record CreateTaskRequest(
        @NotBlank @Size(max = 160) String title,
        @FutureOrPresent Instant dueAt) {}
problem-details.json
{
  "type": "https://henheang.site/problems/task-title-invalid",
  "title": "Task validation failed",
  "status": 400,
  "detail": "One or more fields are invalid.",
  "instance": "/v1/tasks",
  "errors": [{ "field": "title", "code": "NotBlank" }],
  "traceId": "7fbc..."
}

Production example

  1. 1

    Compatibility

    Add optional response fields freely when clients ignore unknown fields; do not silently change meaning, type, enum policy, or nullability.

  2. 2

    Idempotency

    Scope a client key to actor and operation, hash the relevant request, atomically persist outcome, and reject key reuse with a different request.

  3. 3

    Errors

    Use stable machine codes, human-safe details, trace correlation, and documented status mappings. RFC 9457 obsoletes RFC 7807.

  4. 4

    Documentation

    Keep OpenAPI request, success, error, auth, pagination, and idempotency examples executable in contract tests where practical.

Common mistakes

  • Verbs and database table names in every URI
  • Returning 200 with success=false for protocol errors
  • Leaking stack traces or SQL messages
  • Using page numbers without a stable order
  • Accepting arbitrary sort columns directly into SQL
  • Breaking clients by renaming fields or narrowing accepted values

Best practices

  • Model external DTOs separately from persistence models
  • Use explicit limits and allowlists for filter/sort/search input
  • Return 201 and Location for created resources when applicable
  • Design retry and idempotency behavior together
  • Document timezone and null/omission rules
  • Treat OpenAPI as a reviewed contract, not decoration

Trade-offs

DecisionAdvantageCost
Offset paginationSimple random page accessDegrades and shifts under writes
Keyset paginationStable and efficient continuationNo arbitrary page jump; stable sort required
URI versionVery visible compatibility boundaryMultiple routes and duplicated docs
Compatible evolutionFewer version branchesRequires strict change discipline

Interview questions

  • When should an API return 400, 401, 403, 404, 409, or 422?
  • How do PUT and PATCH differ?
  • How would you implement idempotent payment creation?
  • Why can keyset pagination outperform offset?
  • How would you evolve an enum safely?

Hands-on task

  1. 1

    Contract

    Write OpenAPI for create, read, list, update, and delete Task operations, including every error response.

  2. 2

    Policy

    Define stable order, cursor encoding, maximum page size, allowed filters, and idempotency-key behavior.

  3. 3

    Verify

    Create integration tests for validation, conflict, unknown resource, duplicate idempotency key, and backward-compatible JSON.

References

  • IETF: RFC 9110 — HTTP Semantics — https://www.rfc-editor.org/rfc/rfc9110
  • IETF: RFC 9457 — Problem Details for HTTP APIs — https://www.rfc-editor.org/rfc/rfc9457
  • OpenAPI Initiative: OpenAPI Specification 3.2.0 — https://spec.openapis.org/oas/v3.2.0.html
  • Spring: Annotated Controllers — https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller.html

Retrieval check

Before you continue

  • Explain the main idea in your own words without rereading the page.
  • Name one production problem this knowledge helps prevent.
  • Describe where you would apply it in a Spring Boot project.

Source metadata

Primary references checked on 2026-07-16. Version scope is stated at the top of this page.