Skip to main content

REST Fundamentals

REST (Representational State Transfer) is an architectural style for building web APIs. It uses standard HTTP methods and status codes to provide a predictable, stateless interface for clients to interact with server resources.

Core REST Principles

REST APIs are built around a few key constraints:

  • Stateless — Each request contains all the information the server needs. No session state is stored on the server between requests.
  • Resource-based — Everything is a resource identified by a URL (e.g., /users/42).
  • Uniform interface — Use standard HTTP methods and status codes consistently.
  • Client-server separation — The client and server evolve independently.

HTTP Methods

Each HTTP method maps to a specific operation on a resource:

MethodPurposeIdempotentSafe
GETRetrieve a resourceYesYes
POSTCreate a new resourceNoNo
PUTReplace a resource entirelyYesNo
PATCHPartially update a resourceYesNo
DELETERemove a resourceYesNo

Idempotent means calling it multiple times produces the same result. Safe means it doesn't modify the resource.

Status Codes

Use the correct HTTP status code for every response. Here are the most important ones:

Success (2xx)

// 200 OK — Successful GET, PUT, PATCH, or DELETE
res.status(200).json({ id: 1, name: "Alice" });

// 201 Created — Successful POST that created a resource
res.status(201).json({ id: 2, name: "Bob" });

// 204 No Content — Successful DELETE with no response body
res.status(204).send();

Client Errors (4xx)

// 400 Bad Request — Invalid input
res.status(400).json({ error: "Email is required" });

// 401 Unauthorized — Missing or invalid authentication
res.status(401).json({ error: "Invalid token" });

// 403 Forbidden — Authenticated but not authorized
res.status(403).json({ error: "Admin access required" });

// 404 Not Found — Resource doesn't exist
res.status(404).json({ error: "User not found" });

// 409 Conflict — Resource already exists
res.status(409).json({ error: "Email already registered" });

// 422 Unprocessable Entity — Validation failed
res.status(422).json({ errors: [{ field: "email", message: "Invalid format" }] });

Server Errors (5xx)

// 500 Internal Server Error — Unexpected server failure
res.status(500).json({ error: "Something went wrong" });

Resource Design

Good URL design is fundamental to a clean API. Resources should be nouns, not verbs.

# Good  resource-oriented
GET    /api/users          # List all users
GET    /api/users/42       # Get user 42
POST   /api/users          # Create a user
PUT    /api/users/42       # Replace user 42
PATCH  /api/users/42       # Update user 42
DELETE /api/users/42       # Delete user 42

# Nested resources
GET    /api/users/42/posts       # List posts by user 42
POST   /api/users/42/posts       # Create a post for user 42

# Bad  verb-oriented (avoid this)
GET    /api/getUsers
POST   /api/createUser
POST   /api/deleteUser/42

Filtering, Sorting, and Pagination

Use query parameters for filtering and pagination, never path segments:

# Filtering
GET /api/users?role=admin&active=true

# Sorting
GET /api/users?sort=createdAt&order=desc

# Pagination
GET /api/users?page=2&limit=20

JSON API Response Structure

Keep your response format consistent across all endpoints:

// Single resource
{
  "data": {
    "id": 1,
    "name": "Alice",
    "email": "alice@example.com",
    "createdAt": "2026-03-12T10:00:00Z"
  }
}

// Collection with pagination
{
  "data": [
    { "id": 1, "name": "Alice" },
    { "id": 2, "name": "Bob" }
  ],
  "meta": {
    "total": 50,
    "page": 1,
    "limit": 20,
    "totalPages": 3
  }
}

// Error response
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "details": [
      { "field": "email", "message": "Must be a valid email address" }
    ]
  }
}

Versioning

Always version your API to avoid breaking changes for existing clients:

# URL versioning (most common)
GET /api/v1/users
GET /api/v2/users

# Header versioning
GET /api/users
Accept: application/vnd.myapp.v2+json

URL-based versioning is simpler and more explicit. Start with /api/v1/ from day one.