Routing hierarchy and modular design
Routing Layering and Modular Design
The routing layering and modular design of the Express framework are key to building maintainable and scalable backend applications. Proper routing organization significantly improves code clarity, reduces coupling, and facilitates team collaboration. Below is a step-by-step explanation from basic implementation to advanced patterns.
Basic Route Splitting
The simplest way to split routes is by separating them into files based on functional modules. For example, in an e-commerce system, user, product, and order routes can be stored separately:
// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.send('User list');
});
module.exports = router;
// routes/products.js
const express = require('express');
const router = express.Router();
router.get('/featured', (req, res) => {
res.send('Featured products');
});
module.exports = router;
The main file mounts these routes using app.use
:
const userRoutes = require('./routes/users');
const productRoutes = require('./routes/products');
app.use('/users', userRoutes);
app.use('/products', productRoutes);
Route Parameter Handling
When routes need to handle dynamic parameters, they can be defined using colon syntax and accessed via req.params
in the handler:
// routes/users.js
router.get('/:userId', (req, res) => {
const { userId } = req.params;
res.send(`User ID: ${userId}`);
});
router.get('/:userId/orders/:orderId', (req, res) => {
const { userId, orderId } = req.params;
res.send(`Order ${orderId} for user ${userId}`);
});
Middleware Layering
Middleware can be combined with routes for finer control. For example, adding an authentication middleware:
// middlewares/auth.js
function checkAdmin(req, res, next) {
if (req.user.role === 'admin') return next();
res.status(403).send('Access denied');
}
// routes/admin.js
const { checkAdmin } = require('../middlewares/auth');
router.get('/dashboard', checkAdmin, (req, res) => {
res.send('Admin dashboard');
});
Nested Routing
For complex systems, multi-level nested routing can be used. For example, in API versioning scenarios:
// routes/api/v1/users.js
const router = require('express').Router();
router.get('/premium', (req, res) => {
res.send('v1 premium users');
});
module.exports = router;
// routes/api/v2/users.js
const router = require('express').Router();
router.get('/premium', (req, res) => {
res.send('v2 premium users');
});
module.exports = router;
// Main routing file
const v1Users = require('./api/v1/users');
const v2Users = require('./api/v2/users');
app.use('/api/v1/users', v1Users);
app.use('/api/v2/users', v2Users);
Automated Route Loading
When there are many route files, an automated loading script can be written:
const fs = require('fs');
const path = require('path');
const routesPath = path.join(__dirname, 'routes');
fs.readdirSync(routesPath).forEach(file => {
const route = require(path.join(routesPath, file));
const routePath = `/${file.replace('.js', '')}`;
app.use(routePath, route);
});
Route Metadata Configuration
Use the decorator pattern to add metadata to routes:
// decorators/route.js
function GET(path) {
return function(target, key, descriptor) {
target.routes = target.routes || [];
target.routes.push({
method: 'get',
path,
handler: descriptor.value
});
}
}
// controllers/UserController.js
class UserController {
@GET('/users')
static list(req, res) {
res.send('User list');
}
}
// Register routes
UserController.routes.forEach(route => {
router[route.method](route.path, route.handler);
});
File Upload Routes
Special route configuration is needed for file uploads:
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
router.post('/upload',
upload.single('avatar'),
(req, res) => {
res.send(`File uploaded: ${req.file.filename}`);
}
);
router.post('/gallery',
upload.array('photos', 5),
(req, res) => {
const files = req.files.map(f => f.filename);
res.send(`${files.length} files uploaded`);
}
);
Route Caching Optimization
Add a caching layer for frequently accessed routes:
const apicache = require('apicache');
const cache = apicache.middleware;
router.get('/popular-products',
cache('10 minutes'),
(req, res) => {
// Time-consuming database query
res.json(products);
}
);
Route Performance Monitoring
Add performance tracking via middleware:
router.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
console.log(`${req.method} ${req.path} - ${Date.now() - start}ms`);
});
next();
});
Route Parameter Validation
Use libraries like Joi for parameter validation:
const Joi = require('joi');
const schema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$'))
});
router.post('/register', (req, res) => {
const { error } = schema.validate(req.body);
if (error) return res.status(400).send(error.details);
// Handle registration logic
});
Route Version Management
API version control via HTTP headers or URL paths:
// Version control via Accept header
router.get('/users', (req, res) => {
const acceptVersion = req.get('Accept').includes('vnd.myapp.v2') ? 2 : 1;
if (acceptVersion === 2) {
return res.json({ users: [], meta: { page: 1 } });
}
res.json([]);
});
// Version control via URL path
app.use('/v1', require('./routes/v1'));
app.use('/v2', require('./routes/v2'));
Route Testing Strategy
Example of writing route unit tests:
const request = require('supertest');
const app = require('../app');
describe('User Routes', () => {
it('GET /users should return user list', async () => {
const res = await request(app)
.get('/users')
.expect('Content-Type', /json/)
.expect(200);
expect(Array.isArray(res.body)).toBeTruthy();
});
});
Route Security Protection
Common security measures implementation:
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
// Basic protection
router.use(helmet());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
});
router.use('/login', limiter);
// CSRF protection
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
router.post('/transfer', csrfProtection, (req, res) => {
// Handle transfer logic
});
Route Documentation Generation
Automatically generate API documentation using Swagger:
const swaggerJsdoc = require('swagger-jsdoc');
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'E-commerce API',
version: '1.0.0',
},
},
apis: ['./routes/*.js'], // Path to route files
};
const specs = swaggerJsdoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
Microservice Route Gateway
Example of route configuration in a microservice architecture:
const { createProxyMiddleware } = require('http-proxy-middleware');
router.use('/user-service',
createProxyMiddleware({
target: 'http://user-service:3000',
pathRewrite: { '^/user-service': '' }
})
);
router.use('/product-service',
createProxyMiddleware({
target: 'http://product-service:3000',
changeOrigin: true
})
);
Route Error Handling
Unified error handling middleware:
// Define error handling middleware last
router.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? err.message : undefined
});
});
// Throw errors in business routes
router.get('/error', (req, res, next) => {
try {
throw new Error('Test error');
} catch (err) {
next(err);
}
});
Route Traffic Control
Rate limiting implementation based on token bucket algorithm:
const TokenBucket = require('tokenbucket');
const bucket = new TokenBucket({
size: 100,
tokensToAddPerInterval: 10,
interval: 'minute'
});
router.use((req, res, next) => {
if (bucket.removeTokens(1)) {
return next();
}
res.status(429).send('Too many requests');
});
Route A/B Testing
Implement A/B testing at the route level:
router.get('/new-feature', (req, res) => {
const group = Math.random() > 0.5 ? 'A' : 'B';
if (group === 'A') {
res.send('New feature');
} else {
res.send('Old feature');
}
});
Route Cache Invalidation
Cache invalidation strategies:
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 });
router.get('/products/:id', (req, res) => {
const { id } = req.params;
const cached = cache.get(id);
if (cached) return res.json(cached);
// Simulate database query
const product = { id, name: `Product ${id}` };
cache.set(id, product);
res.json(product);
});
router.put('/products/:id', (req, res) => {
const { id } = req.params;
// Update database...
cache.del(id); // Invalidate cache
res.sendStatus(204);
});
Route Content Negotiation
Return different response formats based on Accept header:
router.get('/resource', (req, res) => {
const accepts = req.accepts(['json', 'html', 'xml']);
switch (accepts) {
case 'json':
return res.json({ data: 'value' });
case 'xml':
res.type('xml');
return res.send('<data>value</data>');
default:
return res.send('<p>value</p>');
}
});
Route Database Transactions
Handle database transactions in routes:
const { sequelize } = require('../models');
router.post('/order', async (req, res) => {
const t = await sequelize.transaction();
try {
// Create order
const order = await Order.create(req.body, { transaction: t });
// Deduct inventory
await Product.decrement('stock', {
where: { id: req.body.productId },
transaction: t
});
await t.commit();
res.status(201).json(order);
} catch (err) {
await t.rollback();
res.status(500).json({ error: err.message });
}
});
Route Queue Processing
Queue time-consuming operations:
const { queue } = require('../workers');
router.post('/report', (req, res) => {
const { userId } = req.body;
queue.add('generateReport', { userId });
res.json({ message: 'Report generation in progress, please check later' });
});
Route WebSocket Integration
Implement real-time routes with WebSocket:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ noServer: true });
router.ws('/chat', (ws, req) => {
ws.on('message', (message) => {
// Broadcast message
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
});
// HTTP server upgrade handling
server.on('upgrade', (request, socket, head) => {
const pathname = url.parse(request.url).pathname;
if (pathname === '/chat') {
wss.handleUpgrade(request, socket, head, ws => {
wss.emit('connection', ws, request);
});
} else {
socket.destroy();
}
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:环境配置与多环境管理
下一篇:控制器与服务的分离