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.
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:
- Client-server: Separation of concerns between client and server
- Stateless: Each request contains all necessary information
- Cacheable: Responses must explicitly define if they can be cached
- Uniform interface: Consistent interface across all resources
- Layered system: Architecture can have intermediate layers (proxies, gateways)
- 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 userAdditional methods for specific cases:
HEAD /api/users/123 → Get headers without body
OPTIONS /api/users → Get allowed methodsPatches 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/deleteUserResource 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 userGolden 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_atCommon 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=johnAPI 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/usersAdvantages:
- 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+jsonAdvantages:
- 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=1Advantages:
- 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 guaranteedResponse 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-storeError 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=50json{
"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=50json{
"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: 1678790400json// 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: email30-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.