RESTful API Design: Best Practices for Modern Web Applications

Learn RESTful API design principles, error handling, versioning, authentication, and documentation strategies for building robust APIs.

3 min read
By Sagar Tank
API DesignRESTBackendNode.jsBest Practices
R
Image
RESTful API Design: Best Practices for Modern Web Applications

Well-designed APIs are the foundation of modern web applications. Following RESTful principles and best practices ensures your APIs are maintainable, scalable, and developer-friendly.

REST Principles

Resource-Based URLs

// Good: Resource-based
GET    /api/users
GET    /api/users/123
POST   /api/users
PUT    /api/users/123
DELETE /api/users/123

// Bad: Action-based
GET    /api/getUser
POST   /api/createUser
POST   /api/updateUser
POST   /api/deleteUser

HTTP Methods

  • GET: Retrieve resources (idempotent, safe)
  • POST: Create resources
  • PUT: Update/replace resources (idempotent)
  • PATCH: Partial updates (idempotent)
  • DELETE: Remove resources (idempotent)

Request/Response Format

Consistent Response Structure

// Success response
{
  "data": {
    "id": "123",
    "name": "John Doe",
    "email": "john@example.com"
  },
  "meta": {
    "timestamp": "2024-10-30T10:00:00Z"
  }
}

// Error response
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid email format",
    "details": {
      "email": "Must be a valid email address"
    }
  }
}

Error Handling

HTTP Status Codes

// 200 OK - Success
// 201 Created - Resource created
// 400 Bad Request - Client error
// 401 Unauthorized - Authentication required
// 403 Forbidden - Insufficient permissions
// 404 Not Found - Resource doesn't exist
// 422 Unprocessable Entity - Validation error
// 500 Internal Server Error - Server error

Error Response Format

export function errorHandler(error: Error, req: Request, res: Response) {
  if (error instanceof ValidationError) {
    return res.status(422).json({
      error: {
        code: 'VALIDATION_ERROR',
        message: error.message,
        details: error.details,
      }
    })
  }
  
  if (error instanceof NotFoundError) {
    return res.status(404).json({
      error: {
        code: 'NOT_FOUND',
        message: error.message,
      }
    })
  }
  
  // Log unexpected errors
  console.error(error)
  
  return res.status(500).json({
    error: {
      code: 'INTERNAL_ERROR',
      message: 'An unexpected error occurred',
    }
  })
}

Pagination

// Request
GET /api/users?page=1&limit=20

// Response
{
  "data": [...],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 100,
    "totalPages": 5,
    "hasNext": true,
    "hasPrev": false
  }
}

Filtering and Sorting

// Filtering
GET /api/users?status=active&role=admin

// Sorting
GET /api/users?sort=createdAt&order=desc

// Searching
GET /api/users?search=john

Versioning

URL Versioning

/api/v1/users
/api/v2/users

Header Versioning

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

Authentication

JWT Tokens

// Login
POST /api/auth/login
{
  "email": "user@example.com",
  "password": "password123"
}

// Response
{
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "user": { ... }
  }
}

// Authenticated requests
Authorization: Bearer <token>

Rate Limiting

import rateLimit from 'express-rate-limit'

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
})

app.use('/api/', limiter)

Documentation

OpenAPI/Swagger

import swaggerJsdoc from 'swagger-jsdoc'

const swaggerSpec = swaggerJsdoc({
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'My API',
      version: '1.0.0',
    },
  },
  apis: ['./routes/*.ts'],
})

Best Practices

  1. Use nouns, not verbs - /users not /getUsers
  2. Pluralize resources - /users not /user
  3. Nested resources - /users/123/posts
  4. Consistent naming - Use camelCase or snake_case consistently
  5. Version your API - Plan for future changes
  6. Document everything - Use OpenAPI/Swagger
  7. Handle errors gracefully - Provide helpful error messages
  8. Use HTTPS - Always encrypt in production

Conclusion

Well-designed APIs are intuitive, consistent, and well-documented. Following REST principles and these best practices will make your APIs easier to use and maintain.

Enjoyed this article?

Check out more articles on the blog or get in touch to discuss your project.