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

Routing grouping and modular organization

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

Routing Grouping and Modular Organization

Koa2, as a lightweight Node.js framework, provides a flexible routing mechanism. As project scale grows, cramming all routes into a single file leads to bloated and hard-to-maintain code. Through routing grouping and modular organization, code readability and maintainability can be significantly improved.

Why Routing Grouping is Needed

Consider an e-commerce system with three main functional modules: users, products, and orders. If all routes are written in app.js, the code becomes difficult to manage:

// Anti-pattern: Ungrouped routes
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();

router.get('/users', async (ctx) => { /* User list */ });
router.post('/users', async (ctx) => { /* Create user */ });
router.get('/users/:id', async (ctx) => { /* User details */ });
router.get('/products', async (ctx) => { /* Product list */ });
router.post('/products', async (ctx) => { /* Create product */ });
// ...More routes mixed together

This organization leads to:

  1. Overly large single files
  2. Blurred functional boundaries
  3. Conflicts in collaborative development
  4. Difficulty locating specific functional routes

Basic Routing Grouping Implementation

Koa-router supports route prefixes, the simplest grouping method:

const userRouter = new Router({
  prefix: '/users'
});

userRouter.get('/', async (ctx) => { /* User list */ });
userRouter.post('/', async (ctx) => { /* Create user */ });
userRouter.get('/:id', async (ctx) => { /* User details */ });

app.use(userRouter.routes());

Modular Route Organization

A more robust approach is splitting routes into different files by functional modules:

project/
├── routes/
│   ├── users.js
│   ├── products.js
│   └── orders.js
└── app.js

Example users.js module:

// routes/users.js
const Router = require('koa-router');
const router = new Router({
  prefix: '/users'
});

// User-related middleware
const authMiddleware = require('../middlewares/auth');

// Route definitions
router.get('/', authMiddleware, async (ctx) => {
  ctx.body = await UserService.listUsers();
});

router.post('/', async (ctx) => {
  const user = await UserService.createUser(ctx.request.body);
  ctx.body = user;
});

module.exports = router;

Then register uniformly in app.js:

// app.js
const Koa = require('koa');
const app = new Koa();

// Load route modules
const userRouter = require('./routes/users');
const productRouter = require('./routes/products');
const orderRouter = require('./routes/orders');

app.use(userRouter.routes());
app.use(productRouter.routes());
app.use(orderRouter.routes());

Nested Route Structure

For more complex systems, a multi-level route structure can be adopted:

project/
├── routes/
│   ├── api/
│   │   ├── v1/
│   │   │   ├── users.js
│   │   │   └── admin.js
│   │   └── v2/
│   └── web/
│       ├── home.js
│       └── auth.js
└── app.js

Implementation:

// routes/api/v1/users.js
const Router = require('koa-router');
const router = new Router({
  prefix: '/api/v1/users'
});

// ...v1 user routes

module.exports = router;

// routes/api/v2/users.js
const Router = require('koa-router');
const router = new Router({
  prefix: '/api/v2/users'
});

// ...v2 user routes

module.exports = router;

Registration:

// app.js
const v1Users = require('./routes/api/v1/users');
const v2Users = require('./routes/api/v2/users');
const webHome = require('./routes/web/home');

app.use(v1Users.routes());
app.use(v2Users.routes());
app.use(webHome.routes());

Modular Middleware for Routes

Middleware can also be organized by module:

middlewares/
├── users/
│   ├── auth.js
│   └── permission.js
└── products/
    └── validate.js

Referenced in routes:

// routes/users.js
const userAuth = require('../middlewares/users/auth');
const userPermission = require('../middlewares/users/permission');

router.get('/profile', 
  userAuth,
  userPermission('view'),
  async (ctx) => {
    // Logic
  }
);

Automated Route Loading

When there are many route files, automatic loading can be used:

// utils/load-routes.js
const fs = require('fs');
const path = require('path');

function loadRoutes(app, dir) {
  fs.readdirSync(dir).forEach((file) => {
    const fullPath = path.join(dir, file);
    if (fs.lstatSync(fullPath).isDirectory()) {
      loadRoutes(app, fullPath);
    } else if (file.endsWith('.js')) {
      const router = require(fullPath);
      app.use(router.routes());
    }
  });
}

module.exports = loadRoutes;

Usage:

// app.js
const loadRoutes = require('./utils/load-routes');
loadRoutes(app, path.join(__dirname, 'routes'));

Route Parameter Validation

Modular routes can easily integrate parameter validation:

// routes/products.js
const Joi = require('joi');

const createProductSchema = Joi.object({
  name: Joi.string().required(),
  price: Joi.number().min(0).required()
});

router.post('/', async (ctx) => {
  const { error } = createProductSchema.validate(ctx.request.body);
  if (error) {
    ctx.throw(400, error.details[0].message);
  }
  // Create product logic
});

Route Version Control

Modular organization makes API versioning easy:

// app.js
const v1Router = new Router({ prefix: '/api/v1' });
const v2Router = new Router({ prefix: '/api/v2' });

v1Router.use(require('./routes/v1/users').routes());
v2Router.use(require('./routes/v2/users').routes());

app.use(v1Router.routes());
app.use(v2Router.routes());

Route Unit Testing

Modular routes are easier to test:

// test/users.routes.test.js
const request = require('supertest');
const app = require('../app');
const User = require('../models/User');

describe('User Routes', () => {
  beforeEach(async () => {
    await User.deleteMany({});
  });

  it('should create a user', async () => {
    const res = await request(app)
      .post('/users')
      .send({ name: 'test', email: 'test@example.com' });
    expect(res.status).toBe(201);
  });
});

Performance Optimization Considerations

With modular routes, routes can be loaded on demand:

// Dynamically load admin routes
if (process.env.ENABLE_ADMIN) {
  app.use(require('./routes/admin').routes());
}

Large Project Practices

In large projects, combine with Domain-Driven Design (DDD) for route organization:

src/
├── modules/
│   ├── user/
│   │   ├── infrastructure/
│   │   │   └── routes.js
│   │   ├── application/
│   │   └── domain/
│   └── product/
│       ├── infrastructure/
│       │   └── routes.js
│       ├── application/
│       └── domain/
└── shared/
    └── middlewares/

User module route example:

// src/modules/user/infrastructure/routes.js
const Router = require('koa-router');
const userController = require('../application/controllers/user');

const router = new Router({
  prefix: '/users'
});

router.get('/', userController.listUsers);
router.post('/', userController.createUser);

module.exports = router;

Route Metadata Management

Add metadata to routes for documentation or permission control:

// decorators/route.js
function GET(path, options = {}) {
  return (target, name, descriptor) => {
    const route = {
      method: 'get',
      path,
      handler: descriptor.value,
      options
    };
    if (!target.routes) target.routes = [];
    target.routes.push(route);
    return descriptor;
  };
}

// Using decorators
class UserController {
  @GET('/users', { 
    description: 'Get user list',
    permissions: ['user:read']
  })
  async listUsers(ctx) {
    // Implementation
  }
}

Route and Swagger Integration

Modular routes facilitate API documentation generation:

// routes/users.js
const Router = require('koa-router');
const router = new Router();

/**
 * @swagger
 * /users:
 *   get:
 *     summary: Get user list
 *     tags: [Users]
 */
router.get('/users', async (ctx) => {
  // Implementation
});

module.exports = router;

Unified Error Handling

Modular routes enable centralized error handling:

// routes/base-router.js
const Router = require('koa-router');

class BaseRouter extends Router {
  constructor(options) {
    super(options);
    this.use(this.errorMiddleware);
  }

  errorMiddleware = async (ctx, next) => {
    try {
      await next();
    } catch (err) {
      ctx.status = err.status || 500;
      ctx.body = { 
        error: err.message,
        code: err.code 
      };
    }
  };
}

module.exports = BaseRouter;

// routes/users.js
const BaseRouter = require('./base-router');
const router = new BaseRouter({ prefix: '/users' });

router.get('/', async (ctx) => {
  throw new Error('Test error');
});

Route Caching Strategies

Add caching for specific route modules:

// routes/products.js
const cache = require('../middlewares/cache');

router.get('/', 
  cache('products_list', 3600),
  async (ctx) => {
    // Get product list
  }
);

Route Traffic Control

Modular routes simplify rate limiting:

// routes/auth.js
const rateLimit = require('koa-ratelimit');

const limiter = rateLimit({
  driver: 'memory',
  db: new Map(),
  duration: 60000,
  max: 10
});

router.post('/login', 
  limiter,
  async (ctx) => {
    // Login logic
  }
);

Route and TypeScript Integration

With TypeScript, enhance route type safety:

// src/routes/users.ts
import Router from 'koa-router';
import { Context } from 'koa';

interface User {
  id: number;
  name: string;
}

const router = new Router();

router.get('/:id', async (ctx: Context) => {
  const userId = parseInt(ctx.params.id);
  const user: User = await getUser(userId);
  ctx.body = user;
});

export default router;

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱: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 ☕.