RESTful API Best Practices
1. Use Nouns for Resources
// ✅ Good
GET /api/users
POST /api/users
GET /api/users/123
PUT /api/users/123
DELETE /api/users/123
// ❌ Bad
GET /api/getUsers
POST /api/createUser
GET /api/getUserById/1232. Use Plural Names
// ✅ Good
/api/users
/api/products
/api/orders
// ❌ Bad
/api/user
/api/product
/api/order3. Use HTTP Methods Correctly
// CRUD operations
GET /api/users // Read all
GET /api/users/123 // Read one
POST /api/users // Create
PUT /api/users/123 // Update (replace)
PATCH /api/users/123 // Update (partial)
DELETE /api/users/123 // Delete4. Use Proper Status Codes
// Success
200 OK // Successful GET, PUT, PATCH
201 Created // Successful POST
204 No Content // Successful DELETE
// Client Errors
400 Bad Request // Invalid input
401 Unauthorized // Not authenticated
403 Forbidden // Not authorized
404 Not Found // Resource doesn't exist
409 Conflict // Resource conflict
422 Unprocessable // Validation error
// Server Errors
500 Internal Error // Server error
503 Unavailable // Service down5. Version Your API
// URL versioning (recommended)
/api/v1/users
/api/v2/users
// Header versioning
GET /api/users
Accept: application/vnd.myapi.v2+json
// Query parameter
/api/users?version=26. Use Filtering, Sorting, Pagination
// Filtering
GET /api/users?role=admin&status=active
// Sorting
GET /api/users?sort=-createdAt
// Pagination
GET /api/users?page=2&limit=20
// Combined
GET /api/users?role=admin&sort=-createdAt&page=1&limit=107. Provide Consistent Error Responses
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{
"field": "email",
"message": "Email is required"
}
],
"timestamp": "2024-01-01T00:00:00Z",
"path": "/api/users"
}
}8. Use HTTPS
// Always use HTTPS in production
https://api.example.com/users
// Redirect HTTP to HTTPS
app.use((req, res, next) => {
if (!req.secure && process.env.NODE_ENV === 'production') {
return res.redirect(`https://${req.headers.host}${req.url}`);
}
next();
});9. Implement Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Too many requests'
});
app.use('/api', limiter);10. Use Authentication & Authorization
// JWT authentication
const authenticate = (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
// Role-based authorization
const authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
};
app.delete('/api/users/:id', authenticate, authorize('admin'), deleteUser);11. Document Your API
/**
* @swagger
* /api/users:
* get:
* summary: Get all users
* tags: [Users]
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* - in: query
* name: limit
* schema:
* type: integer
* responses:
* 200:
* description: List of users
*/
app.get('/api/users', getUsers);12. Use Caching
// Cache-Control headers
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
res.set('Cache-Control', 'public, max-age=300');
res.set('ETag', generateETag(user));
res.json(user);
});
// Check ETag
if (req.headers['if-none-match'] === etag) {
return res.status(304).send();
}13. Handle CORS Properly
const cors = require('cors');
app.use(cors({
origin: process.env.ALLOWED_ORIGINS.split(','),
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));14. Validate Input
const Joi = require('joi');
const userSchema = Joi.object({
name: Joi.string().min(2).max(100).required(),
email: Joi.string().email().required(),
age: Joi.number().min(0).max(150)
});
app.post('/api/users', async (req, res) => {
const { error } = userSchema.validate(req.body);
if (error) {
return res.status(422).json({
error: 'Validation failed',
details: error.details
});
}
const user = await User.create(req.body);
res.status(201).json(user);
});15. Use Compression
const compression = require('compression');
app.use(compression());16. Implement Logging
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
app.use((req, res, next) => {
logger.info({
method: req.method,
path: req.path,
ip: req.ip
});
next();
});17. Use Idempotency Keys for Critical Operations
app.post('/api/payments', async (req, res) => {
const idempotencyKey = req.headers['idempotency-key'];
if (!idempotencyKey) {
return res.status(400).json({ error: 'Idempotency-Key required' });
}
const existing = await Payment.findOne({ idempotencyKey });
if (existing) {
return res.status(201).json(existing);
}
const payment = await Payment.create({
idempotencyKey,
...req.body
});
res.status(201).json(payment);
});18. Return Appropriate Response Bodies
// POST - Return created resource
app.post('/api/users', async (req, res) => {
const user = await User.create(req.body);
res.status(201).json(user);
});
// PUT - Return updated resource
app.put('/api/users/:id', async (req, res) => {
const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
res.json(user);
});
// DELETE - Return 204 No Content
app.delete('/api/users/:id', async (req, res) => {
await User.findByIdAndDelete(req.params.id);
res.status(204).send();
});19. Use Nested Resources Wisely
// ✅ Good - Clear relationship
GET /api/users/123/orders
// ❌ Bad - Too deep
GET /api/users/123/orders/456/items/789/reviews
// Better - Flat structure
GET /api/reviews?itemId=78920. Implement Health Checks
app.get('/health', async (req, res) => {
try {
await db.ping();
res.status(200).json({
status: 'healthy',
timestamp: new Date().toISOString()
});
} catch (error) {
res.status(503).json({
status: 'unhealthy',
error: error.message
});
}
});Interview Tips
- Explain principles: RESTful design principles
- Show examples: Good vs bad practices
- Demonstrate security: Auth, HTTPS, validation
- Discuss performance: Caching, compression
- Mention documentation: Swagger/OpenAPI
- Show error handling: Consistent error responses
Summary
RESTful API best practices include using nouns for resources, proper HTTP methods and status codes, API versioning, filtering/sorting/pagination, consistent error responses, HTTPS, rate limiting, authentication/authorization, documentation, caching, CORS, input validation, compression, logging, idempotency, appropriate response bodies, wise use of nested resources, and health checks. Essential for production-ready REST APIs.
Test Your Knowledge
Take a quick quiz to test your understanding of this topic.