REST APIWeb ServicesHTTPAuthenticationAPI Design
REST API Design 2026: Building Scalable Web Services
Miraclin Technologies•

REST API Design 2026: Building Scalable Web Services
REST (Representational State Transfer) APIs remain the backbone of modern web applications, providing standardized communication between clients and servers. This guide covers essential REST API design principles, best practices, and implementation strategies for building robust, scalable web services.
REST API Fundamentals
HTTP Methods and Usage
// GET - Retrieve resources
app.get('/api/users', async (req, res) => {
const users = await User.find();
res.json(users);
});
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
});
// POST - Create resources
app.post('/api/users', async (req, res) => {
try {
const user = new User(req.body);
await user.save();
res.status(201).json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// PUT - Update entire resource
app.put('/api/users/:id', async (req, res) => {
const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
});
// PATCH - Partial update
app.patch('/api/users/:id', async (req, res) => {
const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
});
// DELETE - Remove resource
app.delete('/api/users/:id', async (req, res) => {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) return res.status(404).json({ error: 'User not found' });
res.status(204).send();
});
URL Structure and Naming
// Good URL patterns
GET /api/users // Get all users
GET /api/users/123 // Get specific user
POST /api/users // Create user
PUT /api/users/123 // Update user
DELETE /api/users/123 // Delete user
// Nested resources
GET /api/users/123/posts // Get user's posts
POST /api/users/123/posts // Create post for user
GET /api/posts/456/comments // Get post comments
// Query parameters for filtering
GET /api/users?status=active&role=admin&page=2&limit=10
GET /api/posts?author=123&published=true&sort=createdAt:desc
Response Design and Status Codes
Standardized Response Format
// Success response structure
const successResponse = (data, message = 'Success') => ({
success: true,
message,
data,
timestamp: new Date().toISOString()
});
// Error response structure
const errorResponse = (message, errors = null) => ({
success: false,
message,
errors,
timestamp: new Date().toISOString()
});
// Usage examples
app.get('/api/users', async (req, res) => {
const users = await User.find();
res.json(successResponse(users, 'Users retrieved successfully'));
});
app.post('/api/users', async (req, res) => {
try {
const user = new User(req.body);
await user.save();
res.status(201).json(successResponse(user, 'User created successfully'));
} catch (error) {
res.status(400).json(errorResponse('Validation failed', error.errors));
}
});
HTTP Status Codes
// Success codes
200 // OK - Request successful
201 // Created - Resource created
202 // Accepted - Request accepted for processing
204 // No Content - Successful deletion
// Client error codes
400 // Bad Request - Invalid request data
401 // Unauthorized - Authentication required
403 // Forbidden - Access denied
404 // Not Found - Resource not found
409 // Conflict - Resource conflict
422 // Unprocessable Entity - Validation errors
// Server error codes
500 // Internal Server Error - Server error
502 // Bad Gateway - Upstream server error
503 // Service Unavailable - Server overloaded
// Implementation
const handleErrors = (error, req, res, next) => {
if (error.name === 'ValidationError') {
return res.status(422).json(errorResponse('Validation failed', error.errors));
}
if (error.name === 'CastError') {
return res.status(400).json(errorResponse('Invalid ID format'));
}
res.status(500).json(errorResponse('Internal server error'));
};
Authentication and Authorization
JWT Authentication
const jwt = require('jsonwebtoken');
// Generate JWT token
const generateToken = (user) => {
return jwt.sign(
{
id: user._id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
};
// Authentication middleware
const authenticate = (req, res, next) => {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json(errorResponse('Access denied. No token provided.'));
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json(errorResponse('Invalid token'));
}
};
// Authorization middleware
const authorize = (roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json(errorResponse('Access denied. Insufficient permissions.'));
}
next();
};
};
// Usage
app.get('/api/admin/users', authenticate, authorize(['admin']), getUsersHandler);
API Key Authentication
const authenticateApiKey = async (req, res, next) => {
const apiKey = req.header('X-API-Key');
if (!apiKey) {
return res.status(401).json(errorResponse('API key required'));
}
const client = await ApiClient.findOne({ apiKey, active: true });
if (!client) {
return res.status(401).json(errorResponse('Invalid API key'));
}
// Rate limiting per API key
const usage = await ApiUsage.findOne({
clientId: client._id,
date: new Date().toDateString()
});
if (usage && usage.requests >= client.dailyLimit) {
return res.status(429).json(errorResponse('API rate limit exceeded'));
}
req.client = client;
next();
};
Input Validation and Sanitization
Request Validation
const { body, param, query, validationResult } = require('express-validator');
// Validation rules
const userValidationRules = () => {
return [
body('name').isLength({ min: 2, max: 50 }).trim().escape(),
body('email').isEmail().normalizeEmail(),
body('age').isInt({ min: 18, max: 120 }),
body('password').isLength({ min: 8 }).matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/),
];
};
const validate = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json(errorResponse('Validation failed', errors.array()));
}
next();
};
// Usage
app.post('/api/users', userValidationRules(), validate, createUserHandler);
// Custom validation
const customValidation = (req, res, next) => {
const { email } = req.body;
if (email && !email.endsWith('@company.com')) {
return res.status(422).json(errorResponse('Only company emails allowed'));
}
next();
};
Data Sanitization
const sanitizeInput = (req, res, next) => {
// Remove potentially dangerous characters
const sanitize = (obj) => {
for (let key in obj) {
if (typeof obj[key] === 'string') {
obj[key] = obj[key].replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
} else if (typeof obj[key] === 'object') {
sanitize(obj[key]);
}
}
};
sanitize(req.body);
sanitize(req.query);
next();
};
Pagination and Filtering
Pagination Implementation
const paginate = async (model, query = {}, options = {}) => {
const page = parseInt(options.page) || 1;
const limit = parseInt(options.limit) || 10;
const skip = (page - 1) * limit;
const [data, total] = await Promise.all([
model.find(query).skip(skip).limit(limit).sort(options.sort || { createdAt: -1 }),
model.countDocuments(query)
]);
const totalPages = Math.ceil(total / limit);
return {
data,
pagination: {
currentPage: page,
totalPages,
totalItems: total,
itemsPerPage: limit,
hasNextPage: page < totalPages,
hasPrevPage: page > 1
}
};
};
// Usage
app.get('/api/users', async (req, res) => {
const { page, limit, sort, ...filters } = req.query;
const result = await paginate(User, filters, { page, limit, sort });
res.json(successResponse(result));
});
Advanced Filtering
const buildQuery = (queryParams) => {
const query = {};
// Text search
if (queryParams.search) {
query.$or = [
{ name: { $regex: queryParams.search, $options: 'i' } },
{ email: { $regex: queryParams.search, $options: 'i' } }
];
}
// Date range
if (queryParams.startDate || queryParams.endDate) {
query.createdAt = {};
if (queryParams.startDate) query.createdAt.$gte = new Date(queryParams.startDate);
if (queryParams.endDate) query.createdAt.$lte = new Date(queryParams.endDate);
}
// Numeric range
if (queryParams.minAge) query.age = { ...query.age, $gte: parseInt(queryParams.minAge) };
if (queryParams.maxAge) query.age = { ...query.age, $lte: parseInt(queryParams.maxAge) };
// Array contains
if (queryParams.tags) {
query.tags = { $in: queryParams.tags.split(',') };
}
return query;
};
API Versioning
URL Versioning
// Version in URL path
app.use('/api/v1', v1Routes);
app.use('/api/v2', v2Routes);
// Version-specific handlers
const v1Routes = express.Router();
const v2Routes = express.Router();
v1Routes.get('/users', (req, res) => {
// V1 implementation
res.json({ version: 'v1', users: [] });
});
v2Routes.get('/users', (req, res) => {
// V2 implementation with additional fields
res.json({ version: 'v2', users: [], metadata: {} });
});
Header Versioning
const versionMiddleware = (req, res, next) => {
const version = req.headers['api-version'] || 'v1';
req.apiVersion = version;
next();
};
app.get('/api/users', versionMiddleware, (req, res) => {
switch (req.apiVersion) {
case 'v1':
return handleV1Users(req, res);
case 'v2':
return handleV2Users(req, res);
default:
return res.status(400).json(errorResponse('Unsupported API version'));
}
});
Rate Limiting and Throttling
Rate Limiting Implementation
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
const createRateLimiter = (windowMs, max, message) => {
return rateLimit({
store: new RedisStore({
sendCommand: (...args) => redis.call(...args),
}),
windowMs,
max,
message: errorResponse(message),
standardHeaders: true,
legacyHeaders: false,
});
};
// Different limits for different endpoints
app.use('/api/auth', createRateLimiter(15 * 60 * 1000, 5, 'Too many auth attempts'));
app.use('/api/users', createRateLimiter(15 * 60 * 1000, 100, 'Rate limit exceeded'));
app.use('/api', createRateLimiter(15 * 60 * 1000, 1000, 'API rate limit exceeded'));
Advanced Throttling
const throttle = async (req, res, next) => {
const key = `throttle:${req.ip}:${req.path}`;
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, 60); // 1 minute window
}
const remaining = Math.max(0, 10 - current);
res.set({
'X-RateLimit-Limit': 10,
'X-RateLimit-Remaining': remaining,
'X-RateLimit-Reset': new Date(Date.now() + 60000).toISOString()
});
if (current > 10) {
return res.status(429).json(errorResponse('Rate limit exceeded'));
}
next();
};
Caching Strategies
Response Caching
const cache = require('memory-cache');
const cacheMiddleware = (duration) => {
return (req, res, next) => {
const key = req.originalUrl;
const cached = cache.get(key);
if (cached) {
return res.json(cached);
}
res.sendResponse = res.json;
res.json = (body) => {
cache.put(key, body, duration * 1000);
res.sendResponse(body);
};
next();
};
};
// Usage
app.get('/api/users', cacheMiddleware(300), getUsersHandler); // Cache for 5 minutes
Redis Caching
const getCachedData = async (key) => {
const cached = await redis.get(key);
return cached ? JSON.parse(cached) : null;
};
const setCachedData = async (key, data, ttl = 300) => {
await redis.setex(key, ttl, JSON.stringify(data));
};
app.get('/api/users/:id', async (req, res) => {
const cacheKey = `user:${req.params.id}`;
let user = await getCachedData(cacheKey);
if (!user) {
user = await User.findById(req.params.id);
if (user) {
await setCachedData(cacheKey, user, 600); // Cache for 10 minutes
}
}
if (!user) {
return res.status(404).json(errorResponse('User not found'));
}
res.json(successResponse(user));
});
Error Handling and Logging
Centralized Error Handling
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
const errorHandler = (error, req, res, next) => {
logger.error({
message: error.message,
stack: error.stack,
url: req.url,
method: req.method,
ip: req.ip,
userAgent: req.get('User-Agent')
});
if (error.name === 'ValidationError') {
return res.status(422).json(errorResponse('Validation failed', error.errors));
}
if (error.name === 'UnauthorizedError') {
return res.status(401).json(errorResponse('Unauthorized'));
}
res.status(500).json(errorResponse('Internal server error'));
};
app.use(errorHandler);
API Documentation
OpenAPI/Swagger Documentation
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'User API',
version: '1.0.0',
description: 'A simple Express User API'
},
servers: [
{
url: 'http://localhost:3000/api',
description: 'Development server'
}
]
},
apis: ['./routes/*.js']
};
const specs = swaggerJsdoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
/**
* @swagger
* /users:
* get:
* summary: Get all users
* tags: [Users]
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* description: Page number
* responses:
* 200:
* description: List of users
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: array
* items:
* $ref: '#/components/schemas/User'
*/
Career Opportunities
High-Demand Roles
- API Developer: $85,000 - $125,000
- Backend Engineer: $95,000 - $135,000
- API Architect: $120,000 - $160,000
- Full-Stack Developer: $90,000 - $130,000
Essential Skills
- HTTP protocol and REST principles
- Authentication and authorization
- Database integration
- Caching strategies
- Error handling and logging
- API documentation
- Testing and validation
- Performance optimization
Conclusion
REST API design is fundamental to modern web development. Master these principles, patterns, and best practices to build robust, scalable, and maintainable web services. Well-designed APIs form the backbone of successful applications and create excellent career opportunities in backend development.