阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 路由分组与模块化组织

路由分组与模块化组织

作者:陈川 阅读数:9017人阅读 分类: Node.js

路由分组与模块化组织

Koa2作为一款轻量级的Node.js框架,提供了灵活的路由机制。随着项目规模扩大,将所有路由堆砌在单一文件中会导致代码臃肿难维护。通过路由分组和模块化组织,可以显著提升代码的可读性和可维护性。

为什么需要路由分组

假设一个电商系统包含用户、商品、订单三个主要功能模块。如果将所有路由写在app.js中,代码会变得难以管理:

// 反例:未分组的路由
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();

router.get('/users', async (ctx) => { /* 用户列表 */ });
router.post('/users', async (ctx) => { /* 创建用户 */ });
router.get('/users/:id', async (ctx) => { /* 用户详情 */ });
router.get('/products', async (ctx) => { /* 商品列表 */ });
router.post('/products', async (ctx) => { /* 创建商品 */ });
// ...更多路由混杂在一起

这种组织方式会导致:

  1. 单个文件过于庞大
  2. 功能边界模糊
  3. 多人协作时容易冲突
  4. 难以定位特定功能的路由

基本路由分组实现

Koa-router支持路由前缀,这是最简单的分组方式:

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

userRouter.get('/', async (ctx) => { /* 用户列表 */ });
userRouter.post('/', async (ctx) => { /* 创建用户 */ });
userRouter.get('/:id', async (ctx) => { /* 用户详情 */ });

app.use(userRouter.routes());

模块化路由组织

更完善的方案是将路由按功能模块拆分到不同文件中:

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

users.js模块示例:

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

// 用户相关中间件
const authMiddleware = require('../middlewares/auth');

// 路由定义
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;

然后在app.js中统一注册:

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

// 加载路由模块
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());

嵌套路由结构

对于更复杂的系统,可以采用多级路由结构:

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

实现方式:

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

// ...v1用户路由

module.exports = router;

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

// ...v2用户路由

module.exports = router;

注册时:

// 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());

路由中间件的模块化

中间件也可以按模块组织:

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

在路由中引用:

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

router.get('/profile', 
  userAuth,
  userPermission('view'),
  async (ctx) => {
    // 处理逻辑
  }
);

自动化路由加载

当路由文件很多时,可以自动加载:

// 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;

使用方式:

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

路由参数校验

模块化路由可以方便地集成参数校验:

// 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);
  }
  // 创建商品逻辑
});

路由版本控制

通过模块化可以轻松实现API版本控制:

// 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());

路由单元测试

模块化路由更易于测试:

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

describe('用户路由', () => {
  beforeEach(async () => {
    await User.deleteMany({});
  });

  it('应该能创建用户', async () => {
    const res = await request(app)
      .post('/users')
      .send({ name: 'test', email: 'test@example.com' });
    expect(res.status).toBe(201);
  });
});

性能优化考虑

路由模块化后,可以按需加载路由:

// 动态加载管理后台路由
if (process.env.ENABLE_ADMIN) {
  app.use(require('./routes/admin').routes());
}

大型项目实践

在大型项目中,可以结合领域驱动设计(DDD)组织路由:

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

用户模块路由示例:

// 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;

路由元数据管理

可以为路由添加元数据,便于生成文档或权限控制:

// 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;
  };
}

// 使用装饰器
class UserController {
  @GET('/users', { 
    description: '获取用户列表',
    permissions: ['user:read']
  })
  async listUsers(ctx) {
    // 实现逻辑
  }
}

路由与Swagger集成

模块化路由可以方便地生成API文档:

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

/**
 * @swagger
 * /users:
 *   get:
 *     summary: 获取用户列表
 *     tags: [Users]
 */
router.get('/users', async (ctx) => {
  // 实现
});

module.exports = router;

错误处理统一管理

模块化路由可以统一错误处理:

// 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('测试错误');
});

路由缓存策略

可以为特定路由模块添加缓存:

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

router.get('/', 
  cache('products_list', 3600),
  async (ctx) => {
    // 获取商品列表
  }
);

路由流量控制

模块化路由便于实现限流:

// 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) => {
    // 登录逻辑
  }
);

路由与TypeScript集成

使用TypeScript时,可以增强路由类型安全:

// 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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌