Fundamentals of REST
REST (Representational State Transfer) is an architectural style for designing networked applications based on standard HTTP methods and status codes. Good REST APIs are intuitive, scalable, and easy for developers to integrate with. REST emphasizes resources (nouns) over actions (verbs) and leverages HTTP's built-in semantics.
Core REST Principles:
- Client-Server Architecture: Clear separation between client and server
- Statelessness: Each request contains all information needed; server doesn't store client context
- Uniform Interface: Consistent resource naming, representation, and manipulation
- Cacheability: Responses should be cacheable when appropriate
- Layered System: Clients shouldn't know if connected directly to end server
RESTful Endpoints Design
Use resources as the fundamental concept. Endpoints should represent nouns (resources), not verbs (actions):
# Good RESTful design
GET /api/users # List all users
POST /api/users # Create new user
GET /api/users/:id # Get specific user
PUT /api/users/:id # Update user (full replacement)
PATCH /api/users/:id # Partial update
DELETE /api/users/:id # Delete user
# Nested resources for relationships
GET /api/users/:userId/posts # Get user's posts
POST /api/users/:userId/posts # Create post for user
GET /api/posts/:postId/comments # Get post's comments
POST /api/posts/:postId/comments # Create comment on post
# Bad design - using verbs
GET /api/getUsers
POST /api/createUser
GET /api/fetchUser/:id
POST /api/updateUser/:id
POST /api/deleteUser/:id
Naming Conventions
# Use lowercase with hyphens for multi-word resources
/api/user-profiles
/api/search-history
/api/favorite-items
# Use plural for collections
/api/users # collection
/api/posts # collection
/api/users/:id # single resource
# Avoid deep nesting (max 2-3 levels)
# Good
/api/users/:userId/posts/:postId
# Avoid
/api/users/:userId/posts/:postId/comments/:commentId/replies/:replyId
HTTP Methods & Semantics
Use HTTP methods correctly to express intent:
// GET - Retrieve data (safe, idempotent)
GET /api/users/:id
// POST - Create new resource (not idempotent)
POST /api/users
Body: { name: "John", email: "john@example.com" }
Response: 201 Created with Location header
// PUT - Replace entire resource (idempotent)
PUT /api/users/:id
Body: { name: "Jane", email: "jane@example.com" }
// All fields must be provided
// PATCH - Partial update (may or may not be idempotent)
PATCH /api/users/:id
Body: { email: "newemail@example.com" }
// Only provided fields are updated
// DELETE - Remove resource (idempotent)
DELETE /api/users/:id
// HEAD - Like GET but no response body (for checking existence)
HEAD /api/users/:id
// OPTIONS - Describe communication options
OPTIONS /api/users
HTTP Status Codes
Use appropriate status codes to communicate success or failure:
# 2xx Success
200 OK # Request succeeded, returning data
201 Created # Resource created, usually with Location header
202 Accepted # Request accepted for async processing
204 No Content # Success but no content to return (DELETE)
# 3xx Redirection
301 Moved Permanently # Resource permanently moved
302 Found # Temporary redirect
304 Not Modified # Cached version is still valid
# 4xx Client Error
400 Bad Request # Invalid request format or parameters
401 Unauthorized # Authentication required/failed
403 Forbidden # Authenticated but not authorized
404 Not Found # Resource doesn't exist
409 Conflict # Request conflicts (duplicate email, etc)
422 Unprocessable Entity # Validation failed
429 Too Many Requests # Rate limit exceeded
# 5xx Server Error
500 Internal Server Error # Unexpected server error
502 Bad Gateway # Gateway/load balancer error
503 Service Unavailable # Service temporarily unavailable
504 Gateway Timeout # Request timed out
Request/Response Patterns
Maintain consistency in API request and response structure:
// POST request - Create user
{
"name": "John Doe",
"email": "john@example.com",
"password": "securePassword123"
}
// 201 Success response with created resource
{
"success": true,
"data": {
"id": "507f1f77bcf86cd799439011",
"name": "John Doe",
"email": "john@example.com",
"createdAt": "2024-01-01T12:00:00Z"
},
"meta": {
"timestamp": "2024-01-01T12:00:00Z",
"version": "1.0"
}
}
// Error response - 400 Bad Request
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{
"field": "email",
"message": "Invalid email format"
},
{
"field": "password",
"message": "Password must be 8+ characters"
}
]
}
}
Pagination & Filtering
# Pagination with limit and offset
GET /api/users?limit=20&offset=0
# Pagination with page number
GET /api/users?page=1&pageSize=20
# Filtering
GET /api/posts?status=published&author=john
# Sorting
GET /api/posts?sort=-createdAt,title
# - prefix for descending, default ascending
# Full example
GET /api/posts?status=published&author=john&sort=-createdAt&page=1&pageSize=20
// Response with pagination metadata
{
"success": true,
"data": [...],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 150,
"totalPages": 8,
"hasNext": true,
"hasPrev": false
}
}
API Versioning Strategies
Plan for API evolution with clear versioning strategies:
# URL path versioning (most common, clear)
GET /api/v1/users
GET /api/v2/users
# Subdomain versioning
GET https://v1.api.example.com/users
GET https://v2.api.example.com/users
# Header versioning (more flexible, less visible)
GET /api/users
Accept-Version: 1
# Query parameter versioning
GET /api/users?version=1
GET /api/users?version=2
Managing Deprecation
# Include deprecation headers
Deprecation: true
Sunset: Sun, 30 Jun 2025 23:59:59 GMT
Link: ; rel="successor-version"
# Example endpoint with deprecation notice
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sun, 30 Jun 2025 23:59:59 GMT
Warning: 299 - "Deprecated API. Please migrate to v2."
Input Validation & Error Handling
Validate early and provide clear error messages:
// Good validation response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Invalid email format",
"value": "not-an-email"
},
{
"field": "age",
"message": "Must be between 18 and 120",
"value": 15
}
]
}
}
// Conflict error - resource already exists
{
"success": false,
"error": {
"code": "DUPLICATE_EMAIL",
"message": "User with this email already exists",
"details": {
"field": "email",
"existingId": "507f1f77bcf86cd799439011"
}
}
}
Security Best Practices
- HTTPS Only: Always use TLS/SSL encryption for all endpoints
- Authentication: Implement JWT, OAuth 2.0, or API keys (never basic auth in production)
- Authorization: Check permissions for every resource access
- Input Validation: Validate type, format, length, and range for all inputs
- Output Encoding: Prevent XSS by properly encoding response data
- Rate Limiting: Prevent abuse with rate limits (per IP, per user, per API key)
- CORS: Configure properly to allow only trusted origins
- Security Headers: Include X-Content-Type-Options, X-Frame-Options, etc.
- Audit Logging: Log all sensitive operations for compliance
- Error Messages: Never expose stack traces or sensitive info in error responses
Authentication Example
// JWT Bearer token
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
// API Key
X-API-Key: sk_live_abc123def456
// OAuth 2.0 Bearer
Authorization: Bearer access_token_value
// Refresh token pattern
POST /api/auth/refresh
Body: { refreshToken: "refresh_token_value" }
Response: { accessToken: "new_access_token", expiresIn: 3600 }
Rate Limiting Implementation
// Include rate limit headers in response
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1609459200
// When rate limit exceeded
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1609459200
Retry-After: 3600
// Response body
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Try again in 1 hour.",
"retryAfter": 3600
}
}
Documentation & OpenAPI/Swagger
Comprehensive API documentation is essential for adoption:
openapi: 3.0.0
info:
title: User API
version: 2.0.0
description: Complete user management API
servers:
- url: https://api.example.com
description: Production server
- url: https://staging-api.example.com
description: Staging server
paths:
/api/users:
get:
summary: List all users
operationId: listUsers
tags:
- Users
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: pageSize
in: query
schema:
type: integer
default: 20
responses:
'200':
description: List of users
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: array
items:
$ref: '#/components/schemas/User'
'401':
description: Unauthorized
post:
summary: Create new user
operationId: createUser
tags:
- Users
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
description: Validation error
'409':
description: Duplicate email
components:
schemas:
User:
type: object
properties:
id:
type: string
name:
type: string
email:
type: string
format: email
createdAt:
type: string
format: date-time
required:
- id
- name
- email
CreateUserRequest:
type: object
properties:
name:
type: string
minLength: 1
email:
type: string
format: email
password:
type: string
minLength: 8
required:
- name
- email
- password
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []
Caching Strategies
Implement caching to improve performance:
# Cache-Control header directives
Cache-Control: public, max-age=3600
# Public: can be cached by any cache
# max-age: cache valid for 3600 seconds
# Only cache in private client cache
Cache-Control: private, max-age=1800
# Don't cache sensitive data
Cache-Control: no-cache, no-store, must-revalidate
# ETag for conditional requests
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
# Client conditional request
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
# Server responds 304 Not Modified if unchanged
Common Patterns & Best Practices
Bulk Operations
// Bulk update endpoint
POST /api/users/bulk-update
{
"updates": [
{ "id": "1", "updates": { "role": "admin" } },
{ "id": "2", "updates": { "status": "active" } }
]
}
// Response
{
"success": true,
"data": {
"updated": 2,
"failed": 0,
"results": [...]
}
}
Async Operations
// Long-running operation - return 202 Accepted
POST /api/reports/generate
Response: 202 Accepted
{
"success": true,
"message": "Report generation started",
"operationId": "op_abc123",
"statusUrl": "/api/reports/op_abc123/status"
}
// Check status
GET /api/reports/op_abc123/status
{
"success": true,
"status": "processing",
"progress": 65,
"estimatedTime": 30
}
Search & Advanced Filtering
# Full-text search
GET /api/posts?q=mongodb+performance&fields=title,content
# Range filtering
GET /api/posts?createdAfter=2024-01-01&createdBefore=2024-12-31
# Comparison operators
GET /api/products?price__gte=100&price__lte=500
# Complex filtering
GET /api/posts?filter={"status":"published","author":"john","views":{"$gt":1000}}
Webhook Patterns
Enable real-time notifications with webhooks:
// Register webhook
POST /api/webhooks
{
"url": "https://example.com/webhook",
"events": ["user.created", "post.published"],
"secret": "webhook_secret_key"
}
// Webhook payload - signed for security
POST https://example.com/webhook
X-Webhook-Signature: sha256=abc123def456
Content-Type: application/json
{
"event": "user.created",
"timestamp": "2024-01-01T12:00:00Z",
"data": {
"id": "123",
"email": "user@example.com"
}
}
Conclusion
Well-designed REST APIs are a pleasure to integrate with and maintain. By following these best practices—clear resource naming, appropriate HTTP semantics, comprehensive documentation, robust security, and thoughtful error handling—you create APIs that scale with your business, are easy for developers to understand, and become a valuable asset to your platform. Remember that API design is an iterative process; gather feedback from your users and evolve your API thoughtfully over time.