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

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
- Use nouns, not verbs -
/usersnot/getUsers - Pluralize resources -
/usersnot/user - Nested resources -
/users/123/posts - Consistent naming - Use camelCase or snake_case consistently
- Version your API - Plan for future changes
- Document everything - Use OpenAPI/Swagger
- Handle errors gracefully - Provide helpful error messages
- 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.