RESTful APIs are the backbone of modern web applications. This guide will walk you through building a complete REST API using Express.js with proper architecture and best practices.
1. REST Fundamentals
REST Principles
- Client-Server: Separation of concerns
- Stateless: Each request contains all needed information
- Cacheable: Responses define cacheability
- Uniform Interface: Consistent resource identification
HTTP Methods
GET
: Retrieve resourcesPOST
: Create resourcesPUT/PATCH
: Update resourcesDELETE
: Remove resources
2. Basic API Structure
const express = require('express');
const app = express();
// Middleware
app.use(express.json());
// Routes
app.get('/api/v1/products', (req, res) => {
// Return list of products
});
app.post('/api/v1/products', (req, res) => {
// Create new product
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`API running on port ${PORT}`));
3. Proper Resource Routing
Route Organization
// routes/products.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => { /* GET all */ });
router.post('/', (req, res) => { /* POST create */ });
router.get('/:id', (req, res) => { /* GET one */ });
router.put('/:id', (req, res) => { /* PUT update */ });
router.delete('/:id', (req, res) => { /* DELETE */ });
module.exports = router;
// In main app.js
const productsRouter = require('./routes/products');
app.use('/api/v1/products', productsRouter);
4. CRUD Operations Implementation
// Example using in-memory array (replace with DB in production)
let products = [
{ id: 1, name: 'Product 1', price: 10.99 }
];
// GET all products
router.get('/', (req, res) => {
res.json(products);
});
// GET single product
router.get('/:id', (req, res) => {
const product = products.find(p => p.id === parseInt(req.params.id));
if (!product) return res.status(404).json({ message: 'Product not found' });
res.json(product);
});
// POST create product
router.post('/', (req, res) => {
const { name, price } = req.body;
if (!name || !price) {
return res.status(400).json({ message: 'Name and price are required' });
}
const newProduct = {
id: products.length + 1,
name,
price
};
products.push(newProduct);
res.status(201).json(newProduct);
});
5. Request Validation and Sanitization
Manual Validation
router.post('/', (req, res) => {
const { name, price } = req.body;
// Validation
if (!name || name.trim().length === 0) {
return res.status(400).json({ error: 'Product name is required' });
}
if (!price || isNaN(price) || price <= 0) {
return res.status(400).json({ error: 'Valid price is required' });
}
// Proceed with creation...
});
Using express-validator
const { body, validationResult } = require('express-validator');
router.post('/',
[
body('name').trim().notEmpty().withMessage('Name is required'),
body('price').isFloat({ gt: 0 }).withMessage('Price must be positive')
],
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Proceed with valid data...
}
);
6. API Best Practices
Response Standards
{
"status": "success",
"data": {
"id": 1,
"name": "Product 1"
},
"meta": {
"timestamp": "2023-05-20T12:00:00Z",
"version": "1.0"
}
}
Essential Practices
- Use proper HTTP status codes
- Version your API (
/api/v1/...
) - Implement pagination for large datasets
- Use consistent naming conventions
- Document your API (Swagger/OpenAPI)
7. Complete API Example
// File: app.js
const express = require('express');
const morgan = require('morgan');
const helmet = require('helmet');
const cors = require('cors');
const { notFound, errorHandler } = require('./middleware');
const app = express();
// Middleware
app.use(morgan('dev'));
app.use(helmet());
app.use(cors());
app.use(express.json());
// Routes
app.use('/api/v1/products', require('./routes/products'));
// Error handling
app.use(notFound);
app.use(errorHandler);
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`API running on port ${PORT}`));
// File: routes/products.js
const express = require('express');
const router = express.Router();
const {
getProducts,
getProduct,
createProduct,
updateProduct,
deleteProduct
} = require('../controllers/products');
router.route('/')
.get(getProducts)
.post(createProduct);
router.route('/:id')
.get(getProduct)
.put(updateProduct)
.delete(deleteProduct);
module.exports = router;
// File: controllers/products.js
exports.getProducts = async (req, res) => {
// Implementation...
};
exports.createProduct = async (req, res) => {
// Implementation...
};
Next: Middleware in Express.js →
API Development Checklist
- ✅ Proper HTTP methods and status codes
- ✅ Request validation and sanitization
- ✅ Consistent response format
- ✅ Error handling middleware
- ✅ Security headers (CORS, Helmet)
- ✅ API documentation