Backend

RESTful API Design Best Practices

S

Sajan Acharya

Author

August 25, 2024
24 min read

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.

Tags

#API#REST#Backend#Design

Share this article

About the Author

S

Sajan Acharya

Expert Writer & Developer

Sajan Acharya is an experienced software engineer and technology writer passionate about helping developers master modern web technologies. With years of professional experience in full-stack development, system design, and best practices, they bring real-world insights to every article.

Specializing in Next.js, TypeScript, Node.js, databases, and web performance optimization. Follow for more in-depth technical content.

Stay Updated

Get the latest articles delivered to your inbox