路由分组与模块化组织
路由分组与模块化组织
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) => { /* 创建商品 */ });
// ...更多路由混杂在一起
这种组织方式会导致:
- 单个文件过于庞大
- 功能边界模糊
- 多人协作时容易冲突
- 难以定位特定功能的路由
基本路由分组实现
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
上一篇:路由参数与查询参数的获取
下一篇:路由前缀的配置方法