REST API Design Cheat Sheet

New

REST principles, URL design, HTTP methods, versioning, authentication, and best practices

URL Design

Use Nouns, Not Verbs

Resources are nouns; HTTP methods express the action

// Good GET /users POST /users GET /users/123 // Bad GET /getUsers POST /createUser GET /fetchUserById?id=123

Plural Resource Names

Collections should always use the plural form

/users ✓ /user ✗ /posts/123 ✓ /post/123 ✗

Nested Resources

Express ownership/relationship through nesting (max 2 levels)

/users/123/posts # posts by user 123 /users/123/posts/456 # specific post by user // Avoid deep nesting: /users/123/posts/456/comments/789/likes ✗

Query Parameters

Use for filtering, sorting, pagination — not resource identity

GET /posts?status=published&author=alice GET /posts?sort=created_at&order=desc GET /posts?page=2&limit=20 GET /users?search=alice

CRUD to HTTP Methods

Standard Mapping

Map CRUD operations to HTTP methods

Create → POST /resources → 201 Read → GET /resources → 200 Read → GET /resources/:id → 200 Update → PUT /resources/:id → 200 Update → PATCH /resources/:id → 200 Delete → DELETE /resources/:id → 204

Versioning Strategies

URL Path Versioning

Version in the URL — most visible, easiest to test

/api/v1/users /api/v2/users // Easy to route to different handlers app.use("/api/v1", v1Router) app.use("/api/v2", v2Router)

Header Versioning

Version via Accept or custom header — keeps URLs clean

GET /api/users Accept: application/vnd.myapi.v2+json // or custom header API-Version: 2

Authentication

Bearer Token (JWT)

Standard for stateless API authentication

GET /api/users/me Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

API Key

Simple key-based auth for server-to-server

// Header (preferred) X-API-Key: abc123def456 // Query param (avoid for sensitive APIs) GET /api/data?api_key=abc123def456

Basic Auth

Base64 encoded username:password — only over HTTPS

Authorization: Basic dXNlcjpwYXNzd29yZA== # = base64("user:password")

Pagination & Filtering

Cursor-Based Pagination

Efficient for large datasets; use opaque cursor token

GET /posts?cursor=abc123&limit=20 // Response { "data": [...], "pagination": { "nextCursor": "def456", "hasMore": true } }

Offset Pagination

Simple page/offset-based — good for small datasets

GET /posts?page=3&limit=20 // Response { "data": [...], "pagination": { "page": 3, "limit": 20, "total": 150 } }

Error Response Format

Consistent Error Shape

Always return the same error structure

// 422 Validation Error { "error": { "code": "VALIDATION_FAILED", "message": "Validation failed", "details": [ { "field": "email", "message": "Invalid format" } ] } } // 404 Not Found { "error": { "code": "NOT_FOUND", "message": "User 999 not found" } }

Common Patterns

RESTful URL Structure

Complete URL design for a blog API

# Collections
GET    /api/v1/posts              # list posts
POST   /api/v1/posts              # create post

# Single resources
GET    /api/v1/posts/:id          # get post
PUT    /api/v1/posts/:id          # replace post
PATCH  /api/v1/posts/:id          # update post
DELETE /api/v1/posts/:id          # delete post

# Nested resources (max 2 levels)
GET    /api/v1/posts/:id/comments # list comments
POST   /api/v1/posts/:id/comments # add comment

# Actions (non-CRUD)
POST   /api/v1/posts/:id/publish  # publish post
POST   /api/v1/auth/login         # login action

Pagination Response

Standard paginated list response envelope

{
  "data": [
    { "id": 1, "title": "First Post" },
    { "id": 2, "title": "Second Post" }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 150,
    "totalPages": 8,
    "hasNext": true,
    "hasPrev": false
  },
  "links": {
    "self":  "/api/v1/posts?page=1&limit=20",
    "next":  "/api/v1/posts?page=2&limit=20",
    "last":  "/api/v1/posts?page=8&limit=20"
  }
}

Standard Error Body

Consistent error response format

// All error responses follow this shape
{
  "error": {
    "code": "RESOURCE_NOT_FOUND",   // machine-readable
    "message": "Post 123 not found", // human-readable
    "details": [],                   // optional field-level errors
    "timestamp": "2024-01-15T14:30:00Z",
    "path": "/api/v1/posts/123"
  }
}

// Validation error (422)
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Request validation failed",
    "details": [
      { "field": "title",   "message": "Required" },
      { "field": "content", "message": "Must be at least 10 characters" }
    ]
  }
}

Tips & Best Practices

Use nouns for resources — HTTP methods express the action, not the URL

Return consistent error shapes so clients can handle errors uniformly across all endpoints

Version your API from day one — adding /v1/ later is painful

Use HTTPS everywhere — never send API keys or tokens over plain HTTP

Document your API with OpenAPI/Swagger — it generates interactive docs automatically