Knowledge

Modern RESTful API Design in 2026: Patterns and Best Practices

REST remains the most used communication standard in 2026. Learn design principles, common patterns, and modern practices for production RESTful APIs.

3/14/20268 min readKnowledge
Modern RESTful API Design in 2026: Patterns and Best Practices

Executive summary

REST remains the most used communication standard in 2026. Learn design principles, common patterns, and modern practices for production RESTful APIs.

Last updated: 3/14/2026

Introduction: REST Remains Relevant in 2026

With GraphQL, gRPC, and WebSockets gaining popularity, REST has been "declared dead" multiple times. In 2026, REST continues to be the most used communication standard for HTTP APIs, not out of nostalgia, but for simplicity, mature tooling, and native cacheability.

Well-designed RESTful APIs are predictable, documentable, and easily consumable by any HTTP client. The challenge isn't using HTTP — it's using HTTP consistently, semantically, and at scale.

This guide covers modern RESTful API design patterns, architecture principles, and implementation practices that differentiate professional APIs from improvised ones.

Fundamental REST Principles

REST vs "REST-ish"

APIs using HTTP and JSON aren't necessarily RESTful. REST (Representational State Transfer) is an architectural style with six principles:

  1. Client-server: Separation of concerns between client and server
  2. Stateless: Each request contains all necessary information
  3. Cacheable: Responses must explicitly define if they can be cached
  4. Uniform interface: Consistent interface across all resources
  5. Layered system: Architecture can have intermediate layers (proxies, gateways)
  6. Code on demand (optional): Server can execute code on the client (JavaScript)

"REST-ish" APIs violate these principles and pay the price in complexity, cacheability, and interoperability.

Uniform interface: More than GET, POST, PUT, DELETE

A RESTful API shouldn't restrict itself to four main methods when other HTTP methods are more appropriate:

GET    /api/users           → List users
POST   /api/users           → Create user
GET    /api/users/123       → Get specific user
PUT    /api/users/123       → Replace user
PATCH  /api/users/123       → Partially update user
DELETE /api/users/123       → Delete user

Additional methods for specific cases:

HEAD   /api/users/123       → Get headers without body
OPTIONS /api/users          → Get allowed methods

Patches vs PUTs:

  • PUT: Complete replacement. Sends all fields, even unchanged ones.
  • PATCH: Partial update. Sends only changed fields.
json// PUT - Complete replacement
{
  "id": "123",
  "name": "John Doe",
  "email": "john@example.com",
  "age": 30,
  "address": { "street": "123 Main St", "city": "NYC" }
}

// PATCH - Partial update
{
  "age": 31
}

URI Design and Resource Structure

Nouns, not verbs

URIs should identify resources, not actions:

CORRECT:
GET    /api/users
POST   /api/users
GET    /api/users/123
DELETE /api/users/123

WRONG:
GET    /api/getUsers
POST   /api/createUser
GET    /api/getUser?id=123
POST   /api/deleteUser

Resource hierarchy

Use hierarchy to express relationships:

GET    /api/users                    → List users
GET    /api/users/123               → Get user
GET    /api/users/123/orders        → User's orders
GET    /api/users/123/orders/456    → Specific order of user
POST   /api/users/123/orders        → Create order for user

Golden rule: Avoid overly deep URIs. More than 3-4 levels indicate the API may need reorganization.

Query strings for filters, sorting, and pagination

Use query strings to modify representation, not the resource:

GET    /api/users?age=gte:18&limit=50&offset=0&sort=-created_at

Common query string patterns:

typescript// Filters
GET /api/users?status=active
GET /api/users?status=active&role=admin

// Sorting
GET /api/users?sort=created_at          // Ascending
GET /api/users?sort=-created_at         // Descending
GET /api/users?sort=created_at,name    // Multiple fields

// Pagination
GET /api/users?page=2&per_page=50
GET /api/users?offset=50&limit=50

// Fields (sparse fieldsets)
GET /api/users?fields=id,name,email
GET /api/users?expand=profile,orders   // Expand relationships

// Search
GET /api/users?q=john

API Versioning

Why version?

Public APIs are contracts. Breaking changes can break clients that depend on them. Versioning allows evolution without breaking compatibility.

Versioning strategies

1. URI versioning

https://api.example.com/v1/users
https://api.example.com/v2/users

Advantages:

  • Easy to implement
  • Visible in URI
  • Cache friendly (each version is a distinct resource)

Disadvantages:

  • Dirty URIs
  • Duplicate routes in code

2. Header versioning

GET /api/users
Accept: application/vnd.api.v1+json

Advantages:

  • Clean URIs
  • Version negotiation via HTTP

Disadvantages:

  • Less visible
  • Harder to debug
  • Proxy-friendly caches can be problematic

3. Query parameter versioning

GET /api/users?v=1

Advantages:

  • Easy to test
  • Doesn't pollute URI

Disadvantages:

  • Ambiguous (is it part of URI?)
  • Complex caching

Recommendation: Use URI versioning for public APIs for simplicity and cacheability.

Version lifecycle

yamlv1 (Stable):
  - Bug fixes only
  - Minimum support for 12 months
  - Deprecation warnings in headers

v2 (Current):
  - Active development
  - Breaking changes allowed
  - Migration guide v1 → v2

v3 (Beta):
  - For early adopters
  - Can change without notice
  - SLA not guaranteed

Response Patterns

Success responses

json// GET /api/users/123 (200 OK)
{
  "data": {
    "id": "123",
    "name": "John Doe",
    "email": "john@example.com",
    "created_at": "2026-01-15T10:30:00Z",
    "updated_at": "2026-03-10T14:22:00Z"
  }
}

// POST /api/users (201 Created)
{
  "data": {
    "id": "124",
    "name": "Jane Smith",
    "email": "jane@example.com",
    "created_at": "2026-03-14T09:15:00Z",
    "updated_at": "2026-03-14T09:15:00Z"
  },
  "links": {
    "self": "/api/users/124"
  }
}

Important headers:

200 OK → Cache-Control: public, max-age=3600
201 Created → Location: /api/users/124
204 No Content → Cache-Control: no-store

Error responses

Use appropriate HTTP codes:

typescript// 400 Bad Request - Validation failed
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      { "field": "email", "message": "Email is required" },
      { "field": "age", "message": "Age must be >= 18" }
    ]
  }
}

// 401 Unauthorized - Not authenticated
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authentication required"
  }
}

// 403 Forbidden - No permission
{
  "error": {
    "code": "FORBIDDEN",
    "message": "You don't have permission to access this resource"
  }
}

// 404 Not Found - Resource doesn't exist
{
  "error": {
    "code": "NOT_FOUND",
    "message": "User with id '999' not found"
  }
}

// 409 Conflict - State conflict
{
  "error": {
    "code": "EMAIL_ALREADY_EXISTS",
    "message": "Email already registered",
    "field": "email"
  }
}

// 422 Unprocessable Entity - Valid syntax, invalid semantics
{
  "error": {
    "code": "UNPROCESSABLE_ENTITY",
    "message": "Cannot delete user with active orders"
  }
}

// 429 Too Many Requests - Rate limit exceeded
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests",
    "retry_after": 60
  }
}

// 500 Internal Server Error - Unexpected error
{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred"
  }
}

RFC 9457: Problem Details for HTTP APIs

Standardized pattern for error responses:

json{
  "type": "https://api.example.com/problems/validation-error",
  "title": "Validation Error",
  "status": 400,
  "detail": "The request could not be validated",
  "instance": "/api/users",
  "errors": [
    {
      "field": "email",
      "message": "Email is required"
    }
  ]
}

Pagination

Offset-based pagination

Most common, but has performance issues:

GET /api/users?offset=100&limit=50
json{
  "data": [...],
  "pagination": {
    "total": 1000,
    "count": 50,
    "offset": 100,
    "limit": 50,
    "total_pages": 20,
    "current_page": 3
  },
  "links": {
    "first": "/api/users?offset=0&limit=50",
    "prev": "/api/users?offset=50&limit=50",
    "self": "/api/users?offset=100&limit=50",
    "next": "/api/users?offset=150&limit=50",
    "last": "/api/users?offset=950&limit=50"
  }
}

Problems:

  • High offsets are slow (OFFSET 100000)
  • Data can appear on two pages if items are added/deleted

Cursor-based pagination

Better for large datasets and real-time data:

GET /api/users?cursor=eyJpZCI6IDEyMywgImNyZWF0ZWRfYXQiOiAiMjAyNi0wMy0xNCJ9&limit=50
json{
  "data": [...],
  "pagination": {
    "count": 50,
    "next_cursor": "eyJpZCI6IDEyNCwgImNyZWF0ZWRfYXQiOiAiMjAyNi0wMy0xNCJ9"
  },
  "links": {
    "next": "/api/users?cursor=...&limit=50"
  }
}

Advantages:

  • Consistent performance even at high offsets
  • Works well with frequently changing data

Disadvantages:

  • Doesn't support direct navigation (skip to specific page)
  • More complex to implement

API Security

Authentication vs Authorization

  • Authentication: Who are you? (JWT, OAuth, API keys)
  • Authorization: What can you do? (Permissions, roles, scopes)

JWT Authentication

POST /api/auth/login
{
  "email": "user@example.com",
  "password": "password"
}

→ 200 OK
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 3600
}

Headers for authenticated requests:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Rate limiting

Protects against abuse:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1678790400
json// 429 Too Many Requests
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded",
    "retry_after": 60
  }
}

CORS

Configure CORS appropriately:

typescript// Public API (allow any origin)
app.use(cors({
  origin: '*',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

// Restricted API (specific origins)
app.use(cors({
  origin: ['https://app.example.com', 'https://admin.example.com'],
  credentials: true
}));

Documentation and Specification

OpenAPI (Swagger)

Open specification for REST APIs:

yamlopenapi: 3.0.0
info:
  title: User API
  version: 1.0.0
  description: API for managing users

paths:
  /users:
    get:
      summary: List users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
    post:
      summary: Create user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUser'
      responses:
        '201':
          description: User created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: string
          format: email

30-day Implementation Plan

Week 1: Structure and consistency

  • Define URI conventions
  • Standardize response formats
  • Configure error structure

Week 2: Versioning and documentation

  • Implement API versioning
  • Create OpenAPI specification
  • Generate automatic documentation

Week 3: Security and performance

  • Implement JWT authentication
  • Add rate limiting
  • Configure cache headers

Week 4: Testing and validation

  • Create API test suite
  • Test previous versions
  • Validate OpenAPI compliance

Your company needs robust, documented, and scalable RESTful APIs? Talk to Imperialis specialists about API design, microservices architecture, and secure integrations.

Sources

Related reading