阿里云主机折上折
  • 微信号
Current Site:Index > Routing hierarchy and modular design

Routing hierarchy and modular design

Author:Chuan Chen 阅读数:13326人阅读 分类: Node.js

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.