RESTful API Design

RESTful principles, resource design, and HTTP methods used in the API architecture.

RESTful API Design

This section covers the RESTful principles and design patterns used throughout the API architecture.

API Design Philosophy

Our API architecture follows REST principles with modern enhancements:

1. RESTful Design Principles

Resource-Based URLs

  • Resources are nouns, not verbs
  • Use plural forms for consistency
  • Hierarchical structure for relationships
✅ Good Examples:
GET /api/products              # Get all products
GET /api/products/123         # Get specific product
GET /api/products/123/reviews # Get product reviews

❌ Bad Examples:
GET /api/getProducts          # Verb in URL
GET /api/product              # Singular form
GET /api/products/get/123     # Mixed patterns

HTTP Methods for Actions

  • GET: Retrieve resources (safe, idempotent)
  • POST: Create new resources
  • PUT: Update/replace resources (idempotent)
  • PATCH: Partial updates
  • DELETE: Remove resources (idempotent)
// Product API endpoints
interface ProductEndpoints {
  'GET /api/products': {
    query?: ProductFilters
    response: PaginatedResponse<Product[]>
  }
  'POST /api/products': {
    body: CreateProductRequest
    response: Product
  }
  'GET /api/products/:id': {
    params: { id: string }
    response: Product
  }
  'PUT /api/products/:id': {
    params: { id: string }
    body: UpdateProductRequest
    response: Product
  }
  'DELETE /api/products/:id': {
    params: { id: string }
    response: { success: boolean }
  }
}

2. Stateless Communication

Each request contains all necessary information:

// Request includes authentication and context
const apiRequest = {
  headers: {
    'Authorization': 'Bearer jwt-token',
    'Content-Type': 'application/json',
    'X-Client-Version': '1.0.0'
  },
  body: {
    // All required data for the operation
    data: requestData,
    metadata: {
      timestamp: new Date().toISOString(),
      requestId: generateUniqueId()
    }
  }
}

3. Consistent Response Formats

Standardized response structure across all endpoints:

// Success response format
interface ApiSuccessResponse<T> {
  success: true
  data: T
  metadata: {
    timestamp: string
    requestId: string
    version: string
  }
  pagination?: {
    page: number
    limit: number
    total: number
    hasNext: boolean
    hasPrev: boolean
  }
}

// Error response format
interface ApiErrorResponse {
  success: false
  error: {
    code: string
    message: string
    details?: any
    field?: string // For validation errors
  }
  metadata: {
    timestamp: string
    requestId: string
  }
}

Resource Design Patterns

1. Collection and Resource URLs

// Collection operations
GET    /api/products           # List products
POST   /api/products           # Create product

// Resource operations
GET    /api/products/:id       # Get product
PUT    /api/products/:id       # Update product
PATCH  /api/products/:id       # Partial update
DELETE /api/products/:id       # Delete product

2. Nested Resources

// Product categories (nested)
GET    /api/products/:id/categories    # Get product categories
POST   /api/products/:id/categories    # Add category to product

// Inventory for specific product
GET    /api/products/:id/inventory     # Get product inventory
PUT    /api/products/:id/inventory     # Update product inventory

// Product reviews
GET    /api/products/:id/reviews       # Get product reviews
POST   /api/products/:id/reviews       # Create product review

3. Query Parameters and Filtering

interface ProductQuery {
  // Pagination
  page?: number
  limit?: number
  
  // Sorting
  sort?: 'name' | 'price' | 'created_at' | 'updated_at'
  order?: 'asc' | 'desc'
  
  // Filtering
  category?: string
  min_price?: number
  max_price?: number
  in_stock?: boolean
  
  // Search
  search?: string
  
  // Field selection
  fields?: string[] // ['id', 'name', 'price']
  
  // Expansion
  expand?: string[] // ['category', 'inventory']
}

// Example usage
GET /api/products?category=electronics&in_stock=true&sort=price&order=asc&page=1&limit=20

HTTP Status Codes

Success Codes

  • 200 OK: Standard success response
  • 201 Created: Resource created successfully
  • 202 Accepted: Request accepted for processing
  • 204 No Content: Success with no response body

Client Error Codes

  • 400 Bad Request: Invalid request syntax or data
  • 401 Unauthorized: Authentication required
  • 403 Forbidden: Access denied
  • 404 Not Found: Resource not found
  • 409 Conflict: Resource conflict (duplicate)
  • 422 Unprocessable Entity: Validation errors

Server Error Codes

  • 500 Internal Server Error: Unexpected server error
  • 502 Bad Gateway: Upstream service error
  • 503 Service Unavailable: Service temporarily unavailable
// Status code usage examples
export const ApiResponses = {
  // Success responses
  ok: (data: any) => Response.json({ success: true, data }, { status: 200 }),
  created: (data: any) => Response.json({ success: true, data }, { status: 201 }),
  noContent: () => new Response(null, { status: 204 }),
  
  // Error responses
  badRequest: (message: string) => 
    Response.json({ success: false, error: { code: 'BAD_REQUEST', message } }, { status: 400 }),
  unauthorized: () => 
    Response.json({ success: false, error: { code: 'UNAUTHORIZED', message: 'Authentication required' } }, { status: 401 }),
  forbidden: () => 
    Response.json({ success: false, error: { code: 'FORBIDDEN', message: 'Access denied' } }, { status: 403 }),
  notFound: (resource: string) => 
    Response.json({ success: false, error: { code: 'NOT_FOUND', message: `${resource} not found` } }, { status: 404 })
}

Content Negotiation

Request/Response Headers

// Content type handling
const supportedContentTypes = [
  'application/json',
  'application/x-www-form-urlencoded',
  'multipart/form-data' // For file uploads
]

// Accept header handling
const supportedResponseTypes = [
  'application/json',
  'text/csv', // For data exports
  'application/pdf' // For reports
]

API Versioning

// Version strategies
const versioningStrategies = {
  // URL path versioning (preferred)
  urlPath: '/api/v1/products',
  
  // Header versioning
  header: {
    'Accept': 'application/vnd.api+json;version=1',
    'API-Version': '1.0'
  },
  
  // Query parameter versioning
  queryParam: '/api/products?version=1'
}

Implementation Examples

Standard CRUD Operations

// products/route.ts
export async function GET(request: Request) {
  try {
    const { searchParams } = new URL(request.url)
    const query = parseProductQuery(searchParams)
    
    const products = await productService.list(query)
    
    return ApiResponses.ok({
      items: products.data,
      pagination: products.pagination
    })
  } catch (error) {
    return handleApiError(error)
  }
}

export async function POST(request: Request) {
  try {
    const body = await request.json()
    const validated = createProductSchema.parse(body)
    
    const product = await productService.create(validated)
    
    return ApiResponses.created(product)
  } catch (error) {
    if (error instanceof ZodError) {
      return ApiResponses.badRequest('Invalid request data')
    }
    return handleApiError(error)
  }
}

Resource-Specific Operations

// products/[id]/route.ts
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  try {
    const product = await productService.getById(params.id)
    
    if (!product) {
      return ApiResponses.notFound('Product')
    }
    
    return ApiResponses.ok(product)
  } catch (error) {
    return handleApiError(error)
  }
}

export async function PUT(
  request: Request,
  { params }: { params: { id: string } }
) {
  try {
    const body = await request.json()
    const validated = updateProductSchema.parse(body)
    
    const product = await productService.update(params.id, validated)
    
    return ApiResponses.ok(product)
  } catch (error) {
    return handleApiError(error)
  }
}

API Documentation

OpenAPI/Swagger Integration

// api-docs/openapi.ts
export const productApiSpec = {
  openapi: '3.0.0',
  info: {
    title: 'Smart Shelf API',
    version: '1.0.0',
    description: 'RESTful API for Smart Shelf inventory management'
  },
  paths: {
    '/api/products': {
      get: {
        summary: 'List products',
        parameters: [
          {
            name: 'page',
            in: 'query',
            schema: { type: 'integer', minimum: 1 }
          },
          {
            name: 'limit',
            in: 'query',
            schema: { type: 'integer', minimum: 1, maximum: 100 }
          }
        ],
        responses: {
          200: {
            description: 'Products retrieved successfully',
            content: {
              'application/json': {
                schema: { $ref: '#/components/schemas/ProductListResponse' }
              }
            }
          }
        }
      }
    }
  }
}

Best Practices

1. Resource Naming

  • Use nouns for resources, not verbs
  • Use plural forms consistently
  • Keep URLs simple and predictable
  • Use hyphens for multi-word resources

2. Error Handling

  • Provide clear, actionable error messages
  • Include error codes for programmatic handling
  • Maintain consistent error response format
  • Log errors appropriately for debugging

3. Performance

  • Implement pagination for large datasets
  • Support field selection to reduce payload size
  • Use appropriate caching headers
  • Implement request rate limiting

4. Security

  • Validate all input data
  • Use HTTPS for all communications
  • Implement proper authentication and authorization
  • Sanitize output to prevent XSS

This RESTful API design provides the foundation for building scalable, maintainable, and developer-friendly APIs that follow industry best practices and standards.