REST API Design Principles

REST API Design Principles: Building Scalable Web Services

Table of Contents

  1. Introduction to REST
  2. Core REST Principles
  3. HTTP Methods and Status Codes
  4. URL Design and Resource Naming
  5. Request and Response Structure
  6. Authentication and Authorization
  7. Error Handling
  8. Versioning Strategies
  9. Caching and Performance
  10. Security Best Practices
  11. Documentation and Testing

Introduction to REST {#introduction}

Representational State Transfer (REST) is an architectural style for designing networked applications, particularly web services. REST was introduced by Roy Fielding in his doctoral dissertation in 2000 and has become the standard for building web APIs.

What is REST?

REST is not a protocol or standard, but rather a set of architectural principles that define how web standards like HTTP should be used to create web services.

Benefits of RESTful Design

  • Simplicity: Easy to understand and implement
  • Scalability: Stateless nature enables horizontal scaling
  • Flexibility: Platform and language agnostic
  • Performance: Leverages HTTP caching mechanisms
  • Visibility: Clear separation between client and server

REST vs. Other Paradigms

// REST: Resource-oriented
GET /api/users/123
PUT /api/users/123
DELETE /api/users/123

// RPC: Action-oriented
POST /api/getUser
POST /api/updateUser
POST /api/deleteUser

// GraphQL: Query-oriented
POST /graphql
{
  query: "{ user(id: 123) { name, email } }"
}

Core REST Principles {#principles}

1. Uniform Interface

The uniform interface constraint defines the interface between clients and servers:

Resource Identification

Resources are identified by URIs:

GET /api/v1/users/123
GET /api/v1/orders/456
GET /api/v1/products?category=electronics

Resource Manipulation through Representations

Resources are manipulated through their representations (JSON, XML, etc.):

{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "created_at": "2023-01-15T10:30:00Z"
}

Self-Descriptive Messages

Each message includes enough information to describe how to process it:

GET /api/v1/users/123 HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 156
Cache-Control: max-age=3600

{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com"
}

Hypermedia as the Engine of Application State (HATEOAS)

Responses include links to related resources:

{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "_links": {
    "self": { "href": "/api/v1/users/123" },
    "orders": { "href": "/api/v1/users/123/orders" },
    "profile": { "href": "/api/v1/users/123/profile" }
  }
}

2. Stateless

Each request must contain all information needed to understand and process it:

// Bad: Server-side session state
// Server stores user session information

// Good: Stateless with JWT token
const headers = {
  'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
  'Content-Type': 'application/json'
};

3. Cacheable

Responses must define themselves as cacheable or non-cacheable:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=3600
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT

4. Client-Server

Clear separation of concerns between client and server:
- Client handles user interface and user experience
- Server handles data storage and business logic
- They can evolve independently

5. Layered System

Architecture can be composed of hierarchical layers:

Client → Load Balancer → API Gateway → Authentication Service → Business Logic → Database

6. Code on Demand (Optional)

Server can extend client functionality by transferring executable code:

{
  "data": { ... },
  "scripts": [
    "/js/validation.js",
    "/js/formatting.js"
  ]
}

HTTP Methods and Status Codes {#http-methods}

HTTP Methods (Verbs)

GET - Retrieve Resources

GET /api/v1/users
GET /api/v1/users/123
GET /api/v1/users/123/orders

Response: 200 OK
{
  "users": [
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"}
  ],
  "total": 2,
  "page": 1,
  "per_page": 10
}

POST - Create Resources

POST /api/v1/users
Content-Type: application/json

{
  "name": "John Doe",
  "email": "john@example.com"
}

Response: 201 Created
Location: /api/v1/users/123
{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "created_at": "2023-01-15T10:30:00Z"
}

PUT - Update/Replace Resources

PUT /api/v1/users/123
Content-Type: application/json

{
  "name": "John Smith",
  "email": "john.smith@example.com"
}

Response: 200 OK
{
  "id": 123,
  "name": "John Smith",
  "email": "john.smith@example.com",
  "updated_at": "2023-01-15T11:30:00Z"
}

PATCH - Partial Update

PATCH /api/v1/users/123
Content-Type: application/json

{
  "email": "newemail@example.com"
}

Response: 200 OK
{
  "id": 123,
  "name": "John Doe",
  "email": "newemail@example.com",
  "updated_at": "2023-01-15T11:45:00Z"
}

DELETE - Remove Resources

DELETE /api/v1/users/123

Response: 204 No Content

HTTP Status Codes

Success Codes (2xx)

// 200 OK - General success
// 201 Created - Resource created successfully
// 202 Accepted - Request accepted for processing
// 204 No Content - Success with no response body
// 206 Partial Content - Partial response (pagination)

const statusCodes = {
  SUCCESS: 200,
  CREATED: 201,
  ACCEPTED: 202,
  NO_CONTENT: 204,
  PARTIAL_CONTENT: 206
};

Client Error Codes (4xx)

// 400 Bad Request - Invalid request syntax
// 401 Unauthorized - Authentication required
// 403 Forbidden - Insufficient permissions
// 404 Not Found - Resource doesn't exist
// 405 Method Not Allowed - HTTP method not supported
// 409 Conflict - Resource conflict
// 422 Unprocessable Entity - Validation errors
// 429 Too Many Requests - Rate limit exceeded

const clientErrors = {
  BAD_REQUEST: 400,
  UNAUTHORIZED: 401,
  FORBIDDEN: 403,
  NOT_FOUND: 404,
  METHOD_NOT_ALLOWED: 405,
  CONFLICT: 409,
  UNPROCESSABLE_ENTITY: 422,
  TOO_MANY_REQUESTS: 429
};

Server Error Codes (5xx)

// 500 Internal Server Error - Generic server error
// 502 Bad Gateway - Invalid response from upstream
// 503 Service Unavailable - Server temporarily unavailable
// 504 Gateway Timeout - Upstream timeout

const serverErrors = {
  INTERNAL_SERVER_ERROR: 500,
  BAD_GATEWAY: 502,
  SERVICE_UNAVAILABLE: 503,
  GATEWAY_TIMEOUT: 504
};

URL Design and Resource Naming {#url-design}

Resource Naming Conventions

Use Nouns, Not Verbs

# Good
GET /api/v1/users
POST /api/v1/users
GET /api/v1/orders/123

# Bad
GET /api/v1/getUsers
POST /api/v1/createUser
GET /api/v1/retrieveOrder/123

Use Plural Nouns for Collections

# Good
GET /api/v1/users
GET /api/v1/products
GET /api/v1/orders

# Bad
GET /api/v1/user
GET /api/v1/product
GET /api/v1/order

Use Hierarchical Structure for Relationships

# User's orders
GET /api/v1/users/123/orders

# Order's items
GET /api/v1/orders/456/items

# Product reviews
GET /api/v1/products/789/reviews

URL Structure Examples

Collection and Resource Operations

// Collection operations
const apiEndpoints = {
  // Get all users
  getAllUsers: 'GET /api/v1/users',

  // Create new user
  createUser: 'POST /api/v1/users',

  // Get specific user
  getUser: 'GET /api/v1/users/:id',

  // Update user
  updateUser: 'PUT /api/v1/users/:id',

  // Partially update user
  patchUser: 'PATCH /api/v1/users/:id',

  // Delete user
  deleteUser: 'DELETE /api/v1/users/:id'
};

Query Parameters for Filtering and Pagination

# Filtering
GET /api/v1/users?status=active&role=admin
GET /api/v1/products?category=electronics&price_min=100&price_max=500

# Sorting
GET /api/v1/users?sort=name&order=asc
GET /api/v1/products?sort=price,desc&sort=rating,desc

# Pagination
GET /api/v1/users?page=2&per_page=20
GET /api/v1/users?offset=40&limit=20

# Field selection
GET /api/v1/users?fields=id,name,email
GET /api/v1/products?include=category,reviews&exclude=internal_notes

Search Operations

# Simple search
GET /api/v1/users?q=john

# Advanced search
GET /api/v1/products?search[name]=laptop&search[category]=electronics

# Full-text search
GET /api/v1/articles?query=javascript&highlight=true

Path Parameters vs Query Parameters

Path Parameters (Resource Identification)

// Path parameters identify specific resources
app.get('/api/v1/users/:userId/orders/:orderId', (req, res) => {
  const { userId, orderId } = req.params;
  // Fetch specific order for specific user
});

Query Parameters (Resource Modification)

// Query parameters modify the response
app.get('/api/v1/users', (req, res) => {
  const { 
    page = 1, 
    per_page = 10, 
    status, 
    sort = 'id',
    order = 'asc' 
  } = req.query;
  // Filter, sort, and paginate users
});

Request and Response Structure {#request-response}

Request Structure

Headers

POST /api/v1/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
User-Agent: MyApp/1.0
X-Request-ID: abc123-def456-ghi789

Request Body Structure

{
  "data": {
    "type": "user",
    "attributes": {
      "name": "John Doe",
      "email": "john@example.com",
      "preferences": {
        "language": "en",
        "timezone": "UTC"
      }
    }
  }
}

Response Structure

Consistent Response Format

{
  "status": "success",
  "data": {
    "id": 123,
    "type": "user",
    "attributes": {
      "name": "John Doe",
      "email": "john@example.com",
      "created_at": "2023-01-15T10:30:00Z"
    }
  },
  "meta": {
    "request_id": "abc123-def456-ghi789",
    "timestamp": "2023-01-15T10:30:00Z"
  }
}

Collection Response Format

{
  "status": "success",
  "data": [
    {
      "id": 1,
      "type": "user",
      "attributes": {
        "name": "Alice",
        "email": "alice@example.com"
      }
    },
    {
      "id": 2,
      "type": "user",
      "attributes": {
        "name": "Bob",
        "email": "bob@example.com"
      }
    }
  ],
  "meta": {
    "pagination": {
      "current_page": 1,
      "per_page": 10,
      "total_pages": 5,
      "total_items": 50
    },
    "request_id": "xyz789-abc123-def456"
  }
}

Error Response Format

{
  "status": "error",
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request contains invalid data",
    "details": [
      {
        "field": "email",
        "code": "INVALID_FORMAT",
        "message": "Email must be a valid email address"
      },
      {
        "field": "age",
        "code": "OUT_OF_RANGE",
        "message": "Age must be between 18 and 120"
      }
    ]
  },
  "meta": {
    "request_id": "error123-456789",
    "timestamp": "2023-01-15T10:30:00Z"
  }
}

Content Negotiation

# Client specifies preferred format
Accept: application/json
Accept: application/xml
Accept: application/json, application/xml;q=0.8

# Server responds with appropriate format
Content-Type: application/json
Content-Type: application/xml

Authentication and Authorization {#auth}

Authentication Methods

JWT (JSON Web Tokens)

// JWT Structure: header.payload.signature
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";

// Usage in requests
const headers = {
  'Authorization': `Bearer ${token}`,
  'Content-Type': 'application/json'
};

API Keys

# Header-based
Authorization: ApiKey your-api-key-here
X-API-Key: your-api-key-here

# Query parameter
GET /api/v1/users?api_key=your-api-key-here

OAuth 2.0

// Authorization Code Flow
const authUrl = 'https://auth.example.com/oauth/authorize?' +
  'client_id=your-client-id&' +
  'redirect_uri=https://yourapp.com/callback&' +
  'response_type=code&' +
  'scope=read write&' +
  'state=random-state-string';

// Token exchange
const tokenResponse = await fetch('https://auth.example.com/oauth/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    client_id: 'your-client-id',
    client_secret: 'your-client-secret',
    code: 'authorization-code-from-callback',
    redirect_uri: 'https://yourapp.com/callback'
  })
});

Authorization Patterns

Role-Based Access Control (RBAC)

{
  "user": {
    "id": 123,
    "roles": ["admin", "editor"],
    "permissions": [
      "users:read",
      "users:write",
      "products:read",
      "products:write"
    ]
  }
}

Attribute-Based Access Control (ABAC)

{
  "subject": {
    "id": 123,
    "department": "sales",
    "level": "manager"
  },
  "resource": {
    "type": "customer_data",
    "owner": "sales_team",
    "classification": "confidential"
  },
  "action": "read",
  "environment": {
    "time": "business_hours",
    "location": "office"
  }
}

Security Headers

# Request headers
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Request-ID: unique-request-identifier

# Response headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1609459200

Error Handling {#error-handling}

Comprehensive Error Responses

Validation Errors

{
  "status": "error",
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "value": "invalid-email",
        "code": "INVALID_EMAIL_FORMAT",
        "message": "Please provide a valid email address"
      },
      {
        "field": "password",
        "code": "PASSWORD_TOO_SHORT",
        "message": "Password must be at least 8 characters long"
      }
    ]
  },
  "meta": {
    "request_id": "req_123456789",
    "timestamp": "2023-01-15T10:30:00Z",
    "documentation_url": "https://docs.api.example.com/errors#validation"
  }
}

Business Logic Errors

{
  "status": "error",
  "error": {
    "code": "INSUFFICIENT_FUNDS",
    "message": "Cannot process payment due to insufficient funds",
    "details": {
      "account_balance": 50.00,
      "required_amount": 75.00,
      "currency": "USD"
    }
  },
  "meta": {
    "request_id": "req_987654321",
    "timestamp": "2023-01-15T10:30:00Z"
  }
}

System Errors

{
  "status": "error",
  "error": {
    "code": "SERVICE_UNAVAILABLE",
    "message": "The service is temporarily unavailable",
    "details": {
      "retry_after": 300,
      "estimated_resolution": "2023-01-15T11:00:00Z"
    }
  },
  "meta": {
    "request_id": "req_error_001",
    "timestamp": "2023-01-15T10:30:00Z"
  }
}

Error Handling Implementation

// Express.js error handling middleware
app.use((error, req, res, next) => {
  const errorResponse = {
    status: 'error',
    error: {
      code: error.code || 'INTERNAL_SERVER_ERROR',
      message: error.message || 'An unexpected error occurred'
    },
    meta: {
      request_id: req.id,
      timestamp: new Date().toISOString()
    }
  };

  // Add error details for validation errors
  if (error.type === 'validation') {
    errorResponse.error.details = error.details;
  }

  // Log error for monitoring
  logger.error('API Error', {
    error: error.stack,
    request_id: req.id,
    method: req.method,
    url: req.url,
    user_id: req.user?.id
  });

  res.status(error.statusCode || 500).json(errorResponse);
});

Versioning Strategies {#versioning}

URI Versioning

GET /api/v1/users
GET /api/v2/users
GET /api/v3/users

Header Versioning

GET /api/users
Accept: application/vnd.api+json;version=1
Accept: application/vnd.api+json;version=2

Query Parameter Versioning

GET /api/users?version=1
GET /api/users?v=2

Content Type Versioning

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

Versioning Best Practices

// Semantic versioning for APIs
const versions = {
  v1: {
    major: 1,
    minor: 0,
    patch: 0,
    deprecated: false,
    sunset_date: null
  },
  v2: {
    major: 2,
    minor: 1,
    patch: 3,
    deprecated: false,
    sunset_date: null
  },
  v1_deprecated: {
    major: 1,
    minor: 0,
    patch: 0,
    deprecated: true,
    sunset_date: '2024-12-31T23:59:59Z'
  }
};

// Deprecation headers
const deprecationHeaders = {
  'Deprecation': 'true',
  'Sunset': '2024-12-31T23:59:59Z',
  'Link': '<https://docs.api.example.com/migration>; rel="successor-version"'
};

Caching and Performance {#performance}

HTTP Caching Headers

Cache-Control

# Public cache, 1 hour expiration
Cache-Control: public, max-age=3600

# Private cache, must revalidate
Cache-Control: private, must-revalidate

# No caching
Cache-Control: no-cache, no-store, must-revalidate

ETag and Conditional Requests

# Initial request
GET /api/v1/users/123
Accept: application/json

# Response with ETag
HTTP/1.1 200 OK
ETag: "abc123def456"
Content-Type: application/json

# Conditional request
GET /api/v1/users/123
If-None-Match: "abc123def456"

# Not modified response
HTTP/1.1 304 Not Modified
ETag: "abc123def456"

Last-Modified and If-Modified-Since

# Response with Last-Modified
HTTP/1.1 200 OK
Last-Modified: Wed, 15 Jan 2023 10:30:00 GMT
Content-Type: application/json

# Conditional request
GET /api/v1/users/123
If-Modified-Since: Wed, 15 Jan 2023 10:30:00 GMT

# Not modified response
HTTP/1.1 304 Not Modified

Performance Optimization Techniques

Pagination

// Offset-based pagination
const paginationParams = {
  page: 1,
  per_page: 20,
  offset: 0,
  limit: 20
};

// Cursor-based pagination (better for large datasets)
const cursorParams = {
  cursor: 'eyJpZCI6MTIzfQ==',
  limit: 20,
  direction: 'next'
};

// Response with pagination metadata
const paginatedResponse = {
  data: [...],
  meta: {
    pagination: {
      current_page: 1,
      per_page: 20,
      total_pages: 10,
      total_items: 200,
      has_next: true,
      has_previous: false,
      next_cursor: 'eyJpZCI6MTQzfQ==',
      previous_cursor: null
    }
  }
};

Field Selection and Inclusion

# Select specific fields
GET /api/v1/users?fields=id,name,email

# Include related resources
GET /api/v1/users?include=profile,orders

# Exclude sensitive fields
GET /api/v1/users?exclude=internal_notes,private_data

Compression

# Request compression support
Accept-Encoding: gzip, deflate, br

# Response with compression
Content-Encoding: gzip
Vary: Accept-Encoding

Rate Limiting

// Rate limiting headers
const rateLimitHeaders = {
  'X-RateLimit-Limit': '1000',
  'X-RateLimit-Remaining': '999',
  'X-RateLimit-Reset': '1609459200',
  'X-RateLimit-Window': '3600'
};

// Rate limit exceeded response
const rateLimitResponse = {
  status: 'error',
  error: {
    code: 'RATE_LIMIT_EXCEEDED',
    message: 'API rate limit exceeded',
    details: {
      limit: 1000,
      window: 3600,
      reset_time: '2023-01-15T11:00:00Z'
    }
  }
};

Security Best Practices {#security}

Input Validation and Sanitization

// Input validation schema
const userSchema = {
  name: {
    type: 'string',
    minLength: 2,
    maxLength: 100,
    pattern: '^[a-zA-Z\s]+$'
  },
  email: {
    type: 'string',
    format: 'email',
    maxLength: 255
  },
  age: {
    type: 'integer',
    minimum: 18,
    maximum: 120
  }
};

// SQL injection prevention
const getUserById = async (id) => {
  // Use parameterized queries
  const query = 'SELECT * FROM users WHERE id = $1';
  const result = await db.query(query, [id]);
  return result.rows[0];
};

HTTPS and Transport Security

// Enforce HTTPS
app.use((req, res, next) => {
  if (!req.secure && req.get('x-forwarded-proto') !== 'https') {
    return res.redirect('https://' + req.get('host') + req.url);
  }
  next();
});

// Security headers
app.use((req, res, next) => {
  res.set({
    'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
    'X-Content-Type-Options': 'nosniff',
    'X-Frame-Options': 'DENY',
    'X-XSS-Protection': '1; mode=block',
    'Content-Security-Policy': "default-src 'self'"
  });
  next();
});

CORS Configuration

// CORS configuration
const corsOptions = {
  origin: ['https://app.example.com', 'https://admin.example.com'],
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
  exposedHeaders: ['X-RateLimit-Limit', 'X-RateLimit-Remaining'],
  credentials: true,
  maxAge: 86400 // 24 hours
};

app.use(cors(corsOptions));

API Security Checklist

const securityChecklist = [
  '✓ Use HTTPS for all endpoints',
  '✓ Implement proper authentication',
  '✓ Validate and sanitize all inputs',
  '✓ Use parameterized queries',
  '✓ Implement rate limiting',
  '✓ Set security headers',
  '✓ Configure CORS properly',
  '✓ Log security events',
  '✓ Keep dependencies updated',
  '✓ Implement proper error handling',
  '✓ Use least privilege principle',
  '✓ Encrypt sensitive data'
];

Documentation and Testing {#documentation}

OpenAPI/Swagger Documentation

# openapi.yaml
openapi: 3.0.3
info:
  title: User Management API
  description: A comprehensive API for managing users
  version: 1.0.0
  contact:
    name: API Support
    email: api-support@example.com
    url: https://example.com/support

servers:
  - url: https://api.example.com/v1
    description: Production server
  - url: https://staging-api.example.com/v1
    description: Staging server

paths:
  /users:
    get:
      summary: List users
      description: Retrieve a paginated list of users
      parameters:
        - name: page
          in: query
          description: Page number for pagination
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: per_page
          in: query
          description: Number of items per page
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 10
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
                  meta:
                    $ref: '#/components/schemas/PaginationMeta'

components:
  schemas:
    User:
      type: object
      required:
        - id
        - name
        - email
      properties:
        id:
          type: integer
          example: 123
        name:
          type: string
          example: "John Doe"
        email:
          type: string
          format: email
          example: "john@example.com"
        created_at:
          type: string
          format: date-time
          example: "2023-01-15T10:30:00Z"

API Testing

// Unit tests for API endpoints
describe('User API', () => {
  describe('GET /api/v1/users', () => {
    it('should return list of users', async () => {
      const response = await request(app)
        .get('/api/v1/users')
        .set('Authorization', `Bearer ${validToken}`)
        .expect(200);

      expect(response.body).toHaveProperty('data');
      expect(response.body).toHaveProperty('meta');
      expect(Array.isArray(response.body.data)).toBe(true);
    });

    it('should support pagination', async () => {
      const response = await request(app)
        .get('/api/v1/users?page=2&per_page=5')
        .set('Authorization', `Bearer ${validToken}`)
        .expect(200);

      expect(response.body.meta.pagination.current_page).toBe(2);
      expect(response.body.meta.pagination.per_page).toBe(5);
    });
  });

  describe('POST /api/v1/users', () => {
    it('should create a new user', async () => {
      const userData = {
        name: 'Jane Doe',
        email: 'jane@example.com'
      };

      const response = await request(app)
        .post('/api/v1/users')
        .set('Authorization', `Bearer ${validToken}`)
        .send(userData)
        .expect(201);

      expect(response.body.data).toMatchObject(userData);
      expect(response.body.data).toHaveProperty('id');
    });

    it('should validate required fields', async () => {
      const invalidData = { name: 'Jane Doe' }; // Missing email

      const response = await request(app)
        .post('/api/v1/users')
        .set('Authorization', `Bearer ${validToken}`)
        .send(invalidData)
        .expect(422);

      expect(response.body.error.code).toBe('VALIDATION_ERROR');
    });
  });
});

// Integration tests
describe('User Management Flow', () => {
  it('should complete user lifecycle', async () => {
    // Create user
    const createResponse = await request(app)
      .post('/api/v1/users')
      .set('Authorization', `Bearer ${validToken}`)
      .send({ name: 'Test User', email: 'test@example.com' })
      .expect(201);

    const userId = createResponse.body.data.id;

    // Retrieve user
    await request(app)
      .get(`/api/v1/users/${userId}`)
      .set('Authorization', `Bearer ${validToken}`)
      .expect(200);

    // Update user
    await request(app)
      .put(`/api/v1/users/${userId}`)
      .set('Authorization', `Bearer ${validToken}`)
      .send({ name: 'Updated User', email: 'updated@example.com' })
      .expect(200);

    // Delete user
    await request(app)
      .delete(`/api/v1/users/${userId}`)
      .set('Authorization', `Bearer ${validToken}`)
      .expect(204);

    // Verify deletion
    await request(app)
      .get(`/api/v1/users/${userId}`)
      .set('Authorization', `Bearer ${validToken}`)
      .expect(404);
  });
});

This comprehensive guide covers the essential principles and practices for designing robust REST APIs. By following these guidelines, you'll create APIs that are intuitive, scalable, secure, and maintainable. Remember that REST is not just about using HTTP methods correctly, but about creating a well-designed, resource-oriented architecture that provides a great developer experience.

The key to successful API design is consistency, clear documentation, and continuous feedback from API consumers. Start with simple, clear endpoints and gradually add complexity as needed, always keeping the developer experience at the forefront of your design decisions.