Introduction
Node.js is ideal for building RESTful APIs. Its event-driven, non-blocking I/O model handles concurrent requests efficiently. This guide covers building production-ready APIs with Node.js and Express.
Setting Up the Project
Initialization
mkdir my-api
cd my-api
npm init -y
npm install express cors helmet morgan
npm install --save-dev nodemon
Project Structure
src/
โโโ controllers/
โ โโโ userController.js
โโโ models/
โ โโโ userModel.js
โโโ routes/
โ โโโ userRoutes.js
โโโ middleware/
โ โโโ auth.js
โโโ services/
โ โโโ userService.js
โโโ config/
โ โโโ database.js
โโโ utils/
โ โโโ helpers.js
โโโ app.js
โโโ server.js
Express Basics
Creating the Server
// server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get('/', (req, res) => {
res.json({ message: 'Welcome to my API' });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Routing
Basic Routes
// routes/users.js
const express = require('express');
const router = express.Router();
// Get all users
router.get('/', async (req, res) => {
const users = await User.find();
res.json(users);
});
// Get user by ID
router.get('/: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);
});
// Create user
router.post('/', async (req, res) => {
const user = new User(req.body);
await user.save();
res.status(201).json(user);
});
// Update user
router.put('/:id', async (req, res) => {
const user = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true }
);
res.json(user);
});
// Delete user
router.delete('/:id', async (req, res) => {
await User.findByIdAndDelete(req.params.id);
res.status(204).send();
});
module.exports = router;
Mounting Routes
// app.js
const userRoutes = require('./routes/users');
app.use('/api/users', userRoutes);
Middleware
Custom Middleware
// middleware/logger.js
function logger(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} - ${duration}ms`);
});
next();
}
// middleware/errorHandler.js
function errorHandler(err, req, res, next) {
console.error(err.stack);
res.status(500).json({
error: 'Internal server error',
message: err.message
});
}
Using Middleware
app.use(logger);
app.use(errorHandler);
Data Validation
With Joi
const Joi = require('joi');
const userSchema = Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
password: Joi.string().min(8).required(),
age: Joi.number().integer().min(0).max(150)
});
// Validation middleware
function validate(schema) {
return (req, res, next) => {
const { error } = schema.validate(req.body);
if (error) {
return res.status(400).json({
error: 'Validation failed',
details: error.details
});
}
next();
};
}
router.post('/', validate(userSchema), createUser);
Authentication
JWT Implementation
// middleware/auth.js
const jwt = require('jsonwebtoken');
function authenticate(req, res, next) {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Access denied' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
res.status(401).json({ error: 'Invalid token' });
}
}
// Generate token
function generateToken(user) {
return jwt.sign(
{ id: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
}
Protected Routes
router.get('/profile', authenticate, (req, res) => {
res.json(req.user);
});
Error Handling
Async Error Handling
// Better error handling wrapper
const asyncHandler = fn => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
// Usage
router.get('/:id', asyncHandler(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);
}));
RESTful Best Practices
URL Design
GET /api/users # List users
GET /api/users/123 # Get user
POST /api/users # Create user
PUT /api/users/123 # Update user
DELETE /api/users/123 # Delete user
GET /api/users/123/posts # Get user's posts
POST /api/users/123/follow # Follow user
Status Codes
| Code | Meaning |
|---|---|
| 200 | OK |
| 201 | Created |
| 204 | No Content |
| 400 | Bad Request |
| 401 | Unauthorized |
| 404 | Not Found |
| 500 | Server Error |
Response Format
// Success response
{
"success": true,
"data": { ... },
"meta": { "page": 1, "total": 100 }
}
// Error response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input"
}
}
Pagination
// middleware/pagination.js
function paginate(schema) {
return async (req, res, next) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
const results = {};
try {
results.data = await schema.find()
.skip(skip)
.limit(limit);
const total = await schema.countDocuments();
results.meta = {
page,
limit,
total,
pages: Math.ceil(total / limit)
};
res.paginatedResults = results;
next();
} catch (err) {
next(err);
}
};
}
// Usage
router.get('/', paginate(User), (req, res) => {
res.json(res.paginatedResults);
});
Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP
message: 'Too many requests'
});
app.use('/api', limiter);
Testing APIs
Supertest
const request = require('supertest');
const app = require('../app');
describe('Users API', () => {
it('should get all users', async () => {
const res = await request(app)
.get('/api/users')
.expect(200);
expect(Array.isArray(res.body)).toBe(true);
});
it('should create user', async () => {
const res = await request(app)
.post('/api/users')
.send({
name: 'John Doe',
email: '[email protected]',
password: 'password123'
})
.expect(201);
});
});
Conclusion
Building RESTful APIs with Node.js involves more than just handling routes. Focus on proper error handling, validation, authentication, and following REST conventions. Use middleware effectively and always consider security.
Comments