Introduction
RESTful API design is crucial for building scalable, maintainable applications. In this lesson, you'll learn REST principles, API versioning strategies, proper HTTP status code usage, error handling patterns, and how to document your APIs professionally with Swagger/OpenAPI.
Learning Objectives
- Understand REST architectural principles
- Design resource-based API endpoints
- Implement proper HTTP status codes
- Version your APIs effectively
- Document APIs with Swagger/OpenAPI
- Apply API best practices and conventions
Prerequisites
- Completion of Lessons 01-09
- Strong understanding of HTTP methods
- Experience building Express applications
Estimated Time
4 hours (including practice exercises)
REST Principles
REST (Representational State Transfer) is an architectural style with six constraints:
- Client-Server - Separation of concerns
- Stateless - Each request contains all necessary information
- Cacheable - Responses must define themselves as cacheable or not
- Uniform Interface - Consistent resource identification
- Layered System - Client cannot tell if connected directly to server
- Code on Demand - Optional: servers can extend client functionality
Resource Naming Conventions
Best Practices
// Good - Use nouns, not verbs
GET /api/users
GET /api/users/123
POST /api/users
PUT /api/users/123
DELETE /api/users/123
// Good - Use plural nouns
GET /api/products
GET /api/orders
// Good - Nested resources
GET /api/users/123/orders
GET /api/posts/456/comments
// Bad - Don't use verbs
GET /api/getUsers
POST /api/createUser
DELETE /api/deleteUser
// Bad - Don't use mixed case
GET /api/UserProfiles
GET /api/user_profiles
HTTP Status Codes
Success Codes (2xx)
200 OK- Request succeeded201 Created- Resource created successfully204 No Content- Success but no content to return
Client Error Codes (4xx)
400 Bad Request- Invalid request data401 Unauthorized- Authentication required403 Forbidden- Authenticated but not authorized404 Not Found- Resource doesn't exist409 Conflict- Request conflicts with current state422 Unprocessable Entity- Validation errors429 Too Many Requests- Rate limit exceeded
Server Error Codes (5xx)
500 Internal Server Error- Generic server error503 Service Unavailable- Server temporarily unavailable
API Versioning
URL Versioning (Recommended)
JavaScript
// Version in URL path
app.use('/api/v1', require('./routes/v1'));
app.use('/api/v2', require('./routes/v2'));
// Example endpoints
GET /api/v1/users
GET /api/v2/users
Header Versioning
JavaScript
// Custom header
Accept-Version: v1
// Middleware to handle version
app.use((req, res, next) => {
const version = req.headers['accept-version'] || 'v1';
req.apiVersion = version;
next();
});
Standardized Response Format
utils/response.js
// Success response
exports.success = (res, data, message = 'Success', statusCode = 200) => {
return res.status(statusCode).json({
success: true,
message,
data
});
};
// Error response
exports.error = (res, message = 'Error', statusCode = 500, errors = null) => {
return res.status(statusCode).json({
success: false,
message,
errors
});
};
// Paginated response
exports.paginated = (res, data, page, limit, total) => {
return res.json({
success: true,
data,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
});
};
Pagination, Filtering & Sorting
controllers/productController.js
exports.getProducts = async (req, res) => {
try {
const { page = 1, limit = 10, sort = '-createdAt', category, minPrice, maxPrice, search } = req.query;
// Build query
const query = {};
if (category) query.category = category;
if (minPrice || maxPrice) {
query.price = {};
if (minPrice) query.price.$gte = Number(minPrice);
if (maxPrice) query.price.$lte = Number(maxPrice);
}
if (search) {
query.$or = [
{ name: { $regex: search, $options: 'i' } },
{ description: { $regex: search, $options: 'i' } }
];
}
// Execute query
const products = await Product.find(query)
.sort(sort)
.limit(limit * 1)
.skip((page - 1) * limit);
const count = await Product.countDocuments(query);
res.json({
success: true,
data: products,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: count,
pages: Math.ceil(count / limit)
}
});
} catch (error) {
res.status(500).json({ success: false, message: error.message });
}
};
Error Handling Middleware
middleware/errorHandler.js
class ApiError extends Error {
constructor(statusCode, message, errors = null) {
super(message);
this.statusCode = statusCode;
this.errors = errors;
}
}
const errorHandler = (err, req, res, next) => {
let { statusCode, message, errors } = err;
// Mongoose validation error
if (err.name === 'ValidationError') {
statusCode = 422;
errors = Object.values(err.errors).map(e => ({
field: e.path,
message: e.message
}));
}
// Mongoose duplicate key error
if (err.code === 11000) {
statusCode = 409;
message = 'Duplicate field value';
}
// JWT errors
if (err.name === 'JsonWebTokenError') {
statusCode = 401;
message = 'Invalid token';
}
res.status(statusCode || 500).json({
success: false,
message: message || 'Internal server error',
errors
});
};
module.exports = { ApiError, errorHandler };
API Documentation with Swagger
Terminal
npm install swagger-jsdoc swagger-ui-express
config/swagger.js
const swaggerJsdoc = require('swagger-jsdoc');
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'My API',
version: '1.0.0',
description: 'API documentation'
},
servers: [
{ url: 'http://localhost:3000', description: 'Development' },
{ url: 'https://api.example.com', description: 'Production' }
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
}
}
},
apis: ['./routes/*.js']
};
module.exports = swaggerJsdoc(options);
server.js
const swaggerUi = require('swagger-ui-express');
const swaggerSpec = require('./config/swagger');
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
routes/users.js (with Swagger annotations)
/**
* @swagger
* /api/users:
* get:
* summary: Get all users
* tags: [Users]
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* description: Page number
* - in: query
* name: limit
* schema:
* type: integer
* description: Items per page
* responses:
* 200:
* description: Success
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: array
* items:
* $ref: '#/components/schemas/User'
*/
router.get('/', userController.getAll);
Rate Limiting
middleware/rateLimiter.js
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP',
standardHeaders: true,
legacyHeaders: false
});
const strictLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Too many attempts, please try again later'
});
module.exports = { apiLimiter, strictLimiter };
Key Takeaways
- Use nouns for resources, not verbs
- Implement proper HTTP status codes
- Version your API from the start
- Provide consistent response formats
- Document your API with Swagger/OpenAPI
- Implement pagination for large datasets
- Use rate limiting to prevent abuse
- Handle errors consistently
Practice Exercises
Exercise 1: Design a Blog API
Task
// Design RESTful endpoints for:
// - Posts (CRUD)
// - Comments (nested under posts)
// - Categories
// - Tags
// Include proper status codes and responses
Exercise 2: Implement API Versioning
Task
// Create v1 and v2 of an API
// v1: Returns user with all fields
// v2: Returns user with selected fields only
// Implement both versions side by side
Exercise 3: Add Swagger Documentation
Task
// Document your API with Swagger
// Include all endpoints
// Add request/response schemas
// Test with Swagger UI
Exercise 4: Advanced Filtering
Task
// Implement advanced filtering:
// - Multiple field filters
// - Range queries (price, date)
// - Full-text search
// - Sorting by multiple fields
Additional Resources
What's Next?
In Lesson 11, you'll learn testing and deployment strategies. You'll write unit tests with Jest, implement integration tests, set up CI/CD pipelines, and deploy your applications to production environments.