Routing grouping and modular organization
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:
- Overly large single files
- Blurred functional boundaries
- Conflicts in collaborative development
- 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
上一篇:路由参数与查询参数的获取
下一篇:路由前缀的配置方法