# API Design Principles

## Checklist for API Design

- [ ] URLs are resource-based, using nouns not verbs ([RESTful Design](#restful-design-principles))
- [ ] HTTP methods used semantically ([HTTP Methods](#http-methods-semantic-usage))
- [ ] Consistent URL structure conventions ([URL Structure](#url-structure-conventions))
- [ ] Consistent request/response envelope structure ([Request/Response Design](#requestresponse-design))
- [ ] Proper HTTP status codes for all scenarios ([Status Codes](#status-codes-and-meanings))
- [ ] Authentication implemented consistently ([Authentication](#authentication--authorization))
- [ ] Authorization checks on all protected endpoints ([Authorization](#authorization-patterns))
- [ ] Errors have consistent format and codes ([Error Handling](#error-handling))
- [ ] API versioning strategy defined ([Versioning](#versioning-strategy))
- [ ] Pagination implemented for list endpoints ([Pagination](#pagination))
- [ ] Rate limiting configured ([Rate Limiting](#rate-limiting))
- [ ] Filtering and sorting available where needed ([Filtering](#filtering-and-searching))
- [ ] Field selection for optimizing payloads ([Field Selection](#field-selection))
- [ ] Batch operations supported ([Batch Operations](#batch-operations))
- [ ] Webhooks defined and documented ([Webhooks](#webhooks-and-events))
- [ ] Idempotency keys for non-idempotent operations ([Idempotency](#idempotency))
- [ ] Performance optimizations implemented ([Performance](#performance-optimization))
- [ ] Security headers configured ([Security](#security-best-practices))
- [ ] Input validation on all endpoints ([Security](#security-best-practices))
- [ ] API documentation up to date ([Documentation](#api-documentation))
- [ ] Health check endpoint available ([Testing](#testing-your-api))
- [ ] Cache headers set appropriately ([Performance](#cache-headers))
- [ ] CORS configured correctly ([Security](#cors-configuration))

## Core Philosophy

APIs are contracts between systems. They should be predictable, consistent, and self-documenting. Every endpoint should follow the principle of least surprise - developers should be able to guess how an endpoint works based on their experience with other endpoints in your API.

## RESTful Design Principles

### Resource-Based URLs
Design URLs around resources (nouns), not actions (verbs).

**Why:** Resource-based URLs create a consistent, predictable interface that maps naturally to business entities. This approach scales better and is easier to understand than RPC-style endpoints.

```typescript
// ❌ BAD: Action-based URLs
POST /getUserById
POST /createNewUser  
POST /updateUserEmail
POST /deleteUserAccount

// ✅ GOOD: Resource-based URLs
GET    /users/:id      // Get user
POST   /users          // Create user
PATCH  /users/:id      // Update user
DELETE /users/:id      // Delete user
```

### HTTP Methods Semantic Usage
Use HTTP methods according to their intended semantics.

**Why:** Proper HTTP method usage enables better caching, clarifies intent, and allows infrastructure (like CDNs and proxies) to optimize handling of requests.

```typescript
// Method semantics
GET     // Retrieve resource(s) - Safe, Idempotent, Cacheable
POST    // Create new resource - Not safe, Not idempotent
PUT     // Replace entire resource - Not safe, Idempotent
PATCH   // Partial update - Not safe, Not idempotent  
DELETE  // Remove resource - Not safe, Idempotent

// Examples with proper usage
GET    /products?category=electronics  // Safe to retry, can cache
POST   /orders                         // Creates new order each time
PUT    /users/:id                      // Same request = same result
PATCH  /users/:id                      // Update only what changed
DELETE /sessions/:id                    // Can safely retry
```

### URL Structure Conventions
Maintain consistent URL patterns across your API.

**Why:** Consistency reduces cognitive load and makes APIs self-documenting. Developers can predict endpoints without constantly referencing documentation.

```typescript
// URL patterns
/resources                    // Collection
/resources/:id               // Single resource
/resources/:id/subresources  // Nested resources
/resources/:id/actions        // Resource actions

// Consistent naming conventions
/users                       // Plural for collections
/users/:userId              // Descriptive parameter names
/users/:userId/posts        // Clear relationships
/users/:userId/posts/:postId // Deep nesting when logical

// Query parameter patterns
/products?category=electronics&sort=price&order=asc
/users?filter[status]=active&filter[role]=admin
/posts?page=2&limit=20&include=author,comments
```

## Request/Response Design

### Request Payload Structure
Design consistent, predictable request bodies.

**Why:** Consistent request structure reduces errors, improves validation, and makes client code more reusable across different endpoints.

```typescript
// POST /users
{
  "data": {
    "email": "user@example.com",
    "name": "John Doe",
    "role": "member"
  }
}

// PATCH /users/:id
{
  "data": {
    "name": "Jane Doe"  // Only fields being updated
  }
}

// Bulk operations
{
  "data": [
    { "id": "123", "status": "active" },
    { "id": "456", "status": "active" }
  ]
}
```

### Response Envelope Pattern
Wrap responses in a consistent structure.

**Why:** A consistent envelope makes client-side error handling and data extraction uniform across all endpoints. It also provides space for metadata without polluting the data itself.

```typescript
// Success response
{
  "success": true,
  "data": {
    "id": "123",
    "email": "user@example.com",
    "name": "John Doe"
  },
  "meta": {
    "timestamp": "2024-01-15T10:30:00Z",
    "version": "1.0"
  }
}

// Error response
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input data",
    "details": {
      "email": "Email format is invalid",
      "age": "Must be 18 or older"
    }
  },
  "meta": {
    "timestamp": "2024-01-15T10:30:00Z",
    "requestId": "req_abc123"
  }
}

// Paginated response
{
  "success": true,
  "data": [...],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total": 156,
    "totalPages": 8,
    "hasNext": true,
    "hasPrev": true
  }
}
```

### Status Codes
Use HTTP status codes correctly and consistently.

**Why:** Proper status codes enable better client-side handling, work with browser defaults, and make debugging easier. They're a standardized way to communicate operation results.

```typescript
// Success codes
200 OK                  // GET, PUT, PATCH success
201 Created            // POST success with resource creation
202 Accepted           // Async operation started
204 No Content         // DELETE success, no body needed

// Client error codes
400 Bad Request        // Malformed request syntax
401 Unauthorized       // Missing or invalid authentication
403 Forbidden          // Authenticated but not authorized
404 Not Found          // Resource doesn't exist
409 Conflict           // State conflict (e.g., duplicate email)
422 Unprocessable      // Validation errors
429 Too Many Requests  // Rate limit exceeded

// Server error codes
500 Internal Error     // Generic server error
502 Bad Gateway        // Upstream service error
503 Service Unavailable // Maintenance or overload
504 Gateway Timeout    // Upstream service timeout

// Example implementation
export async function createUser(req: Request): Promise<Response> {
  try {
    const user = await userService.create(req.body);
    return res.status(201).json({
      success: true,
      data: user
    });
  } catch (error) {
    if (error instanceof ValidationError) {
      return res.status(422).json({
        success: false,
        error: formatValidationError(error)
      });
    }
    if (error instanceof DuplicateEmailError) {
      return res.status(409).json({
        success: false,
        error: { code: 'DUPLICATE_EMAIL', message: error.message }
      });
    }
    return res.status(500).json({
      success: false,
      error: { code: 'INTERNAL_ERROR', message: 'An error occurred' }
    });
  }
}
```

## Authentication & Authorization

### Authentication Patterns
Implement secure, scalable authentication.

**Why:** Consistent authentication across all endpoints reduces security vulnerabilities and simplifies client implementation. Token-based auth enables stateless, scalable APIs.

```typescript
// JWT Bearer Token (Recommended for most cases)
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

// API Key (For server-to-server)
X-API-Key: sk_live_abc123xyz789

// Session Cookie (For web apps)
Cookie: session=abc123; Secure; HttpOnly; SameSite=Strict

// Implementation example
export function authenticateRequest(req: Request): User | null {
  const authHeader = req.headers.authorization;
  
  if (!authHeader?.startsWith('Bearer ')) {
    throw new UnauthorizedError('Missing authentication');
  }
  
  const token = authHeader.substring(7);
  
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET);
    return payload.user;
  } catch (error) {
    throw new UnauthorizedError('Invalid token');
  }
}
```

### Authorization Patterns
Implement fine-grained access control.

**Why:** Proper authorization prevents data breaches and ensures users can only access resources they own or have permission to view. Clear patterns make security audits easier.

```typescript
// Role-Based Access Control (RBAC)
export function authorize(requiredRole: Role) {
  return (req: Request, res: Response, next: Next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Authentication required' });
    }
    
    if (!hasRole(req.user, requiredRole)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    
    next();
  };
}

// Resource ownership check
export async function canAccessResource(
  user: User,
  resource: Resource
): boolean {
  // Owner can always access
  if (resource.ownerId === user.id) return true;
  
  // Admin can access everything
  if (user.role === 'admin') return true;
  
  // Check shared access
  if (await hasSharedAccess(user.id, resource.id)) return true;
  
  return false;
}

// Usage in routes
router.get('/admin/users', authorize('admin'), getUsers);
router.get('/posts/:id', authenticate, checkResourceAccess, getPost);
```

## Error Handling

### Error Response Format
Provide consistent, actionable error responses.

**Why:** Consistent error formats make debugging easier and enable better error handling on the client. Detailed errors in development speed up debugging while keeping production secure.

```typescript
interface ApiError {
  code: string;           // Machine-readable error code
  message: string;        // Human-readable message
  details?: any;          // Additional context
  timestamp: string;      // When error occurred
  path?: string;          // Which endpoint failed
  requestId?: string;     // For tracking in logs
}

// Development error response (detailed)
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": {
      "email": "Must be a valid email",
      "password": "Must be at least 8 characters"
    },
    "timestamp": "2024-01-15T10:30:00Z",
    "path": "/api/users",
    "requestId": "req_abc123",
    "stack": "Error: Validation failed\n  at validateUser..."  // Dev only
  }
}

// Production error response (secure)
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input provided",
    "timestamp": "2024-01-15T10:30:00Z",
    "requestId": "req_abc123"
  }
}
```

### Error Codes System
Create a standardized error code system.

**Why:** Machine-readable error codes enable programmatic error handling, internationalization, and consistent error tracking across services.

```typescript
// Error code structure: DOMAIN_SPECIFIC_ERROR
enum ErrorCodes {
  // Authentication
  AUTH_INVALID_CREDENTIALS = 'AUTH_INVALID_CREDENTIALS',
  AUTH_TOKEN_EXPIRED = 'AUTH_TOKEN_EXPIRED',
  AUTH_INSUFFICIENT_PERMISSIONS = 'AUTH_INSUFFICIENT_PERMISSIONS',
  
  // Validation
  VALIDATION_REQUIRED_FIELD = 'VALIDATION_REQUIRED_FIELD',
  VALIDATION_INVALID_FORMAT = 'VALIDATION_INVALID_FORMAT',
  VALIDATION_OUT_OF_RANGE = 'VALIDATION_OUT_OF_RANGE',
  
  // Business logic
  USER_EMAIL_EXISTS = 'USER_EMAIL_EXISTS',
  USER_NOT_FOUND = 'USER_NOT_FOUND',
  PAYMENT_INSUFFICIENT_FUNDS = 'PAYMENT_INSUFFICIENT_FUNDS',
  
  // System
  RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED',
  SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',
}

// Client can handle specific errors
if (error.code === 'AUTH_TOKEN_EXPIRED') {
  await refreshToken();
  retryRequest();
}
```

## Versioning Strategy

### URL Path Versioning
Include version in the URL path for major versions.

**Why:** URL versioning is explicit, works with all clients, and makes it clear which version is being used. It's the most straightforward approach for major breaking changes.

```typescript
// Version in path (Recommended for MVP)
/api/v1/users
/api/v2/users  // Breaking changes

// Implementation
app.use('/api/v1', v1Routes);
app.use('/api/v2', v2Routes);

// Deprecation notice
response.headers['X-API-Deprecation'] = 'v1 will be sunset on 2024-12-31';
response.headers['X-API-Version'] = 'v1';
response.headers['X-Latest-Version'] = 'v2';
```

### Backward Compatibility Rules
Maintain compatibility within major versions.

**Why:** Breaking changes frustrate developers and break production apps. Following strict compatibility rules builds trust and reduces support burden.

```typescript
// Safe changes (non-breaking)
✅ Adding new endpoints
✅ Adding optional request parameters
✅ Adding new response fields
✅ Adding new error codes
✅ Making required field optional

// Breaking changes (require new version)
❌ Removing endpoints
❌ Renaming fields
❌ Changing field types
❌ Making optional field required
❌ Changing authentication method

// Graceful migration example
// v1 response
{ "user_name": "John" }

// v2 response (both fields during migration)
{ 
  "user_name": "John",      // Deprecated but maintained
  "username": "John"         // New field
}
```

## Pagination

### Cursor vs Offset Pagination
Choose the right pagination strategy.

**Why:** Cursor pagination handles real-time data better and performs well at scale, while offset pagination is simpler and allows jumping to specific pages.

```typescript
// Offset pagination (Simple, good for static data)
GET /users?page=2&limit=20

{
  "data": [...],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total": 156,
    "totalPages": 8
  }
}

// Cursor pagination (Scalable, good for feeds)
GET /posts?cursor=eyJpZCI6MTIzfQ&limit=20

{
  "data": [...],
  "pagination": {
    "nextCursor": "eyJpZCI6MTQzfQ",
    "hasMore": true
  }
}

// Implementation
export async function paginateCursor(
  query: Query,
  cursor?: string,
  limit = 20
) {
  const decodedCursor = cursor ? decodeCursor(cursor) : null;
  
  const items = await db.query({
    ...query,
    where: {
      ...query.where,
      id: decodedCursor ? { gt: decodedCursor.id } : undefined
    },
    limit: limit + 1  // Fetch one extra to check hasMore
  });
  
  const hasMore = items.length > limit;
  const results = hasMore ? items.slice(0, -1) : items;
  
  return {
    data: results,
    pagination: {
      nextCursor: hasMore ? encodeCursor(results[results.length - 1]) : null,
      hasMore
    }
  };
}
```

## Rate Limiting

### Rate Limit Implementation
Protect your API from abuse.

**Why:** Rate limiting prevents API abuse, ensures fair usage, protects against DDoS attacks, and maintains service stability for all users.

```typescript
// Rate limit headers
X-RateLimit-Limit: 1000        // Max requests per window
X-RateLimit-Remaining: 999     // Requests remaining
X-RateLimit-Reset: 1640995200  // Window reset time (Unix timestamp)
X-RateLimit-Retry-After: 3600  // Seconds until retry (when limited)

// Rate limit response (429)
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests",
    "retryAfter": 3600
  }
}

// Implementation with different tiers
const rateLimits = {
  anonymous: { requests: 100, window: '15m' },
  authenticated: { requests: 1000, window: '15m' },
  premium: { requests: 10000, window: '15m' }
};

export async function rateLimit(req: Request) {
  const tier = getUserTier(req);
  const limit = rateLimits[tier];
  const key = getRateLimitKey(req);
  
  const current = await redis.incr(key);
  
  if (current === 1) {
    await redis.expire(key, limit.window);
  }
  
  if (current > limit.requests) {
    throw new RateLimitError();
  }
  
  // Set headers
  res.setHeader('X-RateLimit-Limit', limit.requests);
  res.setHeader('X-RateLimit-Remaining', limit.requests - current);
}
```

## Filtering and Searching

### Filter Syntax
Provide powerful, consistent filtering.

**Why:** Good filtering reduces bandwidth, improves performance, and enables clients to get exactly the data they need without multiple requests.

```typescript
// Simple filters
GET /products?category=electronics&inStock=true

// Comparison operators
GET /products?price[gte]=100&price[lte]=500

// Array contains
GET /users?roles[contains]=admin

// Full-text search
GET /products?search=wireless+headphones

// Complex filtering implementation
interface FilterOperators {
  eq?: any;      // Equals
  ne?: any;      // Not equals
  gt?: any;      // Greater than
  gte?: any;     // Greater than or equal
  lt?: any;      // Less than
  lte?: any;     // Less than or equal
  in?: any[];    // In array
  contains?: any; // Contains (for arrays/strings)
}

// Parse filter from query
function parseFilters(query: QueryParams): Filters {
  const filters: Filters = {};
  
  for (const [key, value] of Object.entries(query)) {
    if (key.includes('[') && key.includes(']')) {
      const [field, operator] = key.split(/\[|\]/);
      filters[field] = { [operator]: value };
    } else {
      filters[key] = { eq: value };
    }
  }
  
  return filters;
}
```

### Sorting
Enable flexible result ordering.

**Why:** Sorting enables users to find relevant data quickly and supports different use cases (newest first, by relevance, by price) without multiple endpoints.

```typescript
// Single field sort
GET /products?sort=price
GET /products?sort=-price  // Descending with minus prefix

// Multiple field sort
GET /products?sort=category,-price

// Implementation
function parseSorting(sortParam: string): SortOptions {
  return sortParam.split(',').map(field => {
    const descending = field.startsWith('-');
    const fieldName = descending ? field.slice(1) : field;
    
    return {
      field: fieldName,
      order: descending ? 'desc' : 'asc'
    };
  });
}
```

## Field Selection

### Sparse Fieldsets
Allow clients to request specific fields.

**Why:** Field selection reduces bandwidth, improves performance, and enables mobile clients to request minimal data while desktop clients get full details.

```typescript
// Request only specific fields
GET /users?fields=id,name,email

// Nested field selection
GET /posts?fields=id,title,author.name,author.avatar

// Exclude fields
GET /users?exclude=password,refreshToken

// Implementation
function selectFields(object: any, fields: string[]): any {
  if (!fields.length) return object;
  
  const result: any = {};
  
  for (const field of fields) {
    if (field.includes('.')) {
      // Handle nested fields
      const [parent, child] = field.split('.');
      result[parent] = result[parent] || {};
      result[parent][child] = _.get(object, field);
    } else {
      result[field] = object[field];
    }
  }
  
  return result;
}
```

## Batch Operations

### Bulk Endpoints
Support efficient bulk operations.

**Why:** Bulk operations reduce network overhead, improve performance, and enable atomic operations on multiple resources.

```typescript
// Bulk create
POST /users/bulk
{
  "data": [
    { "email": "user1@example.com", "name": "User 1" },
    { "email": "user2@example.com", "name": "User 2" }
  ]
}

// Response with individual results
{
  "success": true,
  "data": [
    { "success": true, "id": "123", "data": {...} },
    { "success": false, "error": { "code": "EMAIL_EXISTS" } }
  ]
}

// Bulk update
PATCH /users/bulk
{
  "updates": [
    { "id": "123", "data": { "status": "active" } },
    { "id": "456", "data": { "status": "inactive" } }
  ]
}

// Bulk delete
DELETE /users/bulk
{
  "ids": ["123", "456", "789"]
}
```

## Webhooks and Events

### Webhook Design
Enable real-time event notifications.

**Why:** Webhooks enable real-time integrations, reduce polling, and allow loosely coupled systems to stay synchronized.

```typescript
// Webhook payload structure
{
  "id": "evt_abc123",
  "type": "user.created",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "id": "user_123",
    "email": "user@example.com"
  },
  "signature": "sha256=abc..."  // HMAC signature for verification
}

// Webhook registration endpoint
POST /webhooks
{
  "url": "https://example.com/webhook",
  "events": ["user.created", "user.updated"],
  "secret": "whsec_abc123"  // For signature verification
}

// Webhook delivery with retry
async function deliverWebhook(webhook: Webhook, event: Event) {
  const signature = createHmacSignature(event, webhook.secret);
  
  const attempts = [0, 1, 5, 30, 120];  // Retry delays in minutes
  
  for (const delay of attempts) {
    if (delay > 0) {
      await sleep(delay * 60 * 1000);
    }
    
    try {
      const response = await fetch(webhook.url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Webhook-Signature': signature,
          'X-Webhook-ID': event.id,
          'X-Webhook-Timestamp': event.timestamp
        },
        body: JSON.stringify(event),
        timeout: 30000
      });
      
      if (response.ok) return;
      
    } catch (error) {
      continue;  // Try next attempt
    }
  }
  
  // Mark webhook as failed after all retries
  await markWebhookFailed(webhook, event);
}
```

## Idempotency

### Idempotency Keys
Prevent duplicate operations.

**Why:** Idempotency prevents duplicate charges, orders, or records when network failures cause retries. It's essential for financial operations and any non-idempotent actions.

```typescript
// Client sends idempotency key
POST /payments
Headers:
  Idempotency-Key: 7b03a8b7-f98e-4d12-a079-afb8d3c7e901
Body:
  { "amount": 100, "currency": "USD" }

// Server implementation
export async function handleIdempotentRequest(
  req: Request,
  handler: () => Promise<Response>
) {
  const key = req.headers['idempotency-key'];
  
  if (!key) {
    return handler();  // No idempotency requested
  }
  
  // Check if we've seen this key before
  const cached = await redis.get(`idempotency:${key}`);
  
  if (cached) {
    return JSON.parse(cached);  // Return cached response
  }
  
  // Process request
  const response = await handler();
  
  // Cache response (24 hours)
  await redis.setex(
    `idempotency:${key}`,
    86400,
    JSON.stringify(response)
  );
  
  return response;
}
```

## Performance Optimization

### Response Compression
Reduce bandwidth usage.

**Why:** Compression significantly reduces bandwidth costs and improves response times, especially for mobile clients or large datasets.

```typescript
// Enable gzip/brotli compression
app.use(compression({
  threshold: 1024,  // Only compress responses > 1KB
  level: 6          // Compression level (1-9)
}));

// Client accepts compression
Accept-Encoding: gzip, deflate, br

// Server responds with compression
Content-Encoding: gzip
```

### Caching Strategy
Implement efficient caching.

**Why:** Proper caching reduces server load, improves response times, and enables offline functionality. Cache headers let CDNs and browsers cache effectively.

```typescript
// Cache headers
Cache-Control: public, max-age=3600           // Cache for 1 hour
Cache-Control: private, max-age=0, must-revalidate  // Don't cache
ETag: "33a64df551"                           // Entity tag for validation
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT

// Conditional requests
If-None-Match: "33a64df551"      // Return 304 if unchanged
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT

// Implementation
export function setCacheHeaders(res: Response, options: CacheOptions) {
  if (options.public) {
    res.setHeader('Cache-Control', `public, max-age=${options.maxAge}`);
  } else {
    res.setHeader('Cache-Control', 'private, no-cache');
  }
  
  if (options.etag) {
    res.setHeader('ETag', options.etag);
  }
}

// Conditional response
export function handleConditional(req: Request, res: Response, data: any) {
  const etag = generateETag(data);
  
  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end();  // Not Modified
  }
  
  res.setHeader('ETag', etag);
  res.json(data);
}
```

## Testing Your API

### API Testing Checklist
Ensure comprehensive API testing.

**Why:** Thorough testing prevents breaking changes, ensures reliability, and builds confidence in your API. Different test types catch different issues.

```typescript
// Unit tests for business logic
describe('UserService', () => {
  test('creates user with valid data', async () => {
    const user = await userService.create({
      email: 'test@example.com',
      password: 'secure123'
    });
    expect(user.id).toBeDefined();
  });
});

// Integration tests for endpoints
describe('POST /users', () => {
  test('returns 201 with valid data', async () => {
    const response = await request(app)
      .post('/users')
      .send({ email: 'test@example.com' })
      .expect(201);
      
    expect(response.body.data.id).toBeDefined();
  });
  
  test('returns 422 with invalid email', async () => {
    const response = await request(app)
      .post('/users')
      .send({ email: 'invalid' })
      .expect(422);
      
    expect(response.body.error.code).toBe('VALIDATION_ERROR');
  });
});

// Contract tests
describe('API Contract', () => {
  test('user response matches schema', async () => {
    const response = await getUser('123');
    expect(response).toMatchSchema(userSchema);
  });
});
```

## Security Best Practices

### API Security Checklist
Essential security measures for production APIs.

**Why:** Security breaches destroy user trust and can end companies. These practices prevent common vulnerabilities and protect user data.

```typescript
// Input validation
const schema = Joi.object({
  email: Joi.string().email().required(),
  age: Joi.number().min(0).max(150)
});

// SQL injection prevention (use parameterized queries)
// ❌ BAD
db.query(`SELECT * FROM users WHERE id = ${userId}`);

// ✅ GOOD  
db.query('SELECT * FROM users WHERE id = ?', [userId]);

// Rate limiting per user
const userRateLimit = rateLimit({
  keyGenerator: (req) => req.user?.id || req.ip,
  max: 100,
  window: '15m'
});

// CORS configuration
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(','),
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

// Security headers
app.use(helmet({
  contentSecurityPolicy: true,
  hidePoweredBy: true
}));
```

## API Documentation

### OpenAPI/Swagger Specification
Document your API comprehensively.

**Why:** Good documentation reduces support requests, speeds up integration, and serves as a contract between frontend and backend teams.

```yaml
openapi: 3.0.0
info:
  title: MyApp API
  version: 1.0.0
  description: RESTful API for MyApp

paths:
  /users:
    post:
      summary: Create a new user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
      responses:
        201:
          description: User created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserResponse'
        422:
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        name:
          type: string
      required:
        - id
        - email
```

