Routing splitting solution for large-scale projects
Background of Route Splitting
As project scale increases, a single routing file can become bloated and difficult to maintain. In a typical Koa2 project's initial phase, all routes might be written in app.js
. When the number of routes exceeds 50, the file can grow to thousands of lines of code. In such cases, locating specific routes becomes challenging, team collaboration may lead to conflicts, and test cases become harder to pinpoint.
Basic Route Splitting Solution
The most straightforward approach is to split routes by business modules. For example, in an e-commerce project, the following directory structure can be created:
routes/
├── user.js
├── product.js
├── order.js
└── cart.js
Each file exports its own router instance:
// routes/user.js
const router = require('koa-router')();
router.get('/users', async (ctx) => {
ctx.body = await UserService.list();
});
router.post('/users', async (ctx) => {
ctx.body = await UserService.create(ctx.request.body);
});
module.exports = router;
Then, load them uniformly in the main file:
// app.js
const userRouter = require('./routes/user');
const productRouter = require('./routes/product');
app.use(userRouter.routes());
app.use(productRouter.routes());
Automated Route Loading
When the number of route files increases, manually requiring each file becomes tedious. Use the fs
module to load routes automatically:
const fs = require('fs');
const path = require('path');
const routesPath = path.join(__dirname, 'routes');
fs.readdirSync(routesPath).forEach(file => {
const router = require(path.join(routesPath, file));
app.use(router.routes());
});
Multi-Level Route Structure
For more complex projects, a multi-level route structure may be needed. For example, separating admin and user APIs:
routes/
├── admin/
│ ├── user.js
│ └── system.js
└── api/
├── v1/
│ ├── user.js
│ └── product.js
└── v2/
├── user.js
└── order.js
Implement this by using prefix
in sub-routes:
// routes/api/v1/user.js
const router = require('koa-router')({
prefix: '/api/v1'
});
router.get('/users', ...);
Route Middleware Isolation
Different routes may require different middleware combinations. Configure middleware at the route level:
// routes/admin/user.js
const auth = require('../../middlewares/admin-auth');
const router = require('koa-router')();
router.use(auth);
router.get('/users', ...);
Dynamic Route Registration
Some scenarios require dynamic route registration based on configuration:
// routes/dynamic.js
module.exports = function(config) {
const router = require('koa-router')();
config.routes.forEach(route => {
router[route.method](route.path, ...route.middlewares);
});
return router;
}
Route Metadata Management
Add metadata to routes for unified management:
// decorators/route.js
function GET(path) {
return function(target, name, descriptor) {
const handler = descriptor.value;
handler.__route = {
method: 'get',
path,
handler
};
return descriptor;
}
}
// routes/user.js
class UserController {
@GET('/users')
async listUsers(ctx) {
// ...
}
}
Performance Optimization Considerations
When the number of routes grows, matching performance may degrade. Solutions include:
- Prioritizing high-frequency routes
- Using route caching
- Avoiding complex regex paths
// Handle high-frequency routes separately
const frequentRouter = require('koa-router')();
frequentRouter.get('/api/products/hot', ...);
app.use(frequentRouter.routes());
// Other routes
app.use(mainRouter.routes());
Test-Friendly Design
For easier testing, route files should:
- Export the raw router instance
- Avoid auto-registering middleware
- Support dependency injection
// routes/user.js
module.exports = function(userService) {
const router = require('koa-router')();
router.get('/users', async ctx => {
ctx.body = await userService.list();
});
return router;
}
Version Control Solutions
For API versioning, consider these approaches:
- URL path versioning
// routes/v1/user.js
router.prefix('/v1/users');
- Request header versioning
router.get('/users', ctx => {
const version = ctx.headers['x-api-version'] || 'v1';
// Return different data based on version
});
- Query parameter versioning
router.get('/users', ctx => {
const version = ctx.query.version || 'v1';
});
Route Documentation Generation
Generate route documentation using JSDoc:
/**
* @api {get} /users Get user list
* @apiVersion 1.0.0
* @apiName GetUsers
*/
router.get('/users', ...);
Use tools to parse comments and generate API documentation.
Unified Error Handling
Define error handling for each route file:
// routes/user.js
router.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
error: 'User module error',
details: err.message
};
}
});
Route Permission Control
Implement fine-grained permission control:
// routes/admin.js
const permissions = {
'/users': ['admin'],
'/settings': ['superadmin']
};
router.use(async (ctx, next) => {
const userRole = ctx.state.user.role;
const requiredRoles = permissions[ctx.path];
if (!requiredRoles.includes(userRole)) {
ctx.throw(403);
}
await next();
});
Route Performance Monitoring
Add route-level performance tracking:
router.use(async (ctx, next) => {
const start = Date.now();
await next();
const duration = Date.now() - start;
metrics.track(ctx.path, duration);
});
Route Caching Strategy
Set caching based on route characteristics:
// routes/product.js
const cache = require('../middlewares/cache');
router.get('/products/:id',
cache({ ttl: 3600 }),
async ctx => {
// ...
}
);
Route Traffic Control
Limit access to high-frequency routes:
const rateLimit = require('koa-ratelimit');
router.get('/api/search',
rateLimit({
duration: 60000,
max: 30
}),
async ctx => {
// ...
}
);
Route Parameter Validation
Validate parameters using libraries like Joi:
const Joi = require('joi');
router.post('/users',
validate({
body: Joi.object({
name: Joi.string().required(),
email: Joi.string().email().required()
})
}),
async ctx => {
// ...
}
);
Microservice Route Gateway
Act as a gateway router in microservice architectures:
const { proxy } = require('koa-http-proxy');
router.all('/user-service/(.*)',
proxy({
target: 'http://user-service:3000',
rewrite: path => path.replace('/user-service', '')
})
);
Route Configuration Center
Load routes dynamically from a configuration center:
const configClient = require('config-client');
router.use(async (ctx, next) => {
const routes = await configClient.get('routes');
const routeConfig = routes.find(r => r.path === ctx.path);
if (routeConfig?.maintenance) {
ctx.status = 503;
return;
}
await next();
});
Route A/B Testing
Support A/B testing at the route level:
router.get('/new-feature',
abTest({
variants: [
{ weight: 50, handler: variantAHandler },
{ weight: 50, handler: variantBHandler }
]
})
);
Route Data Mocking
Mock route data in development environments:
const isDev = process.env.NODE_ENV === 'development';
router.get('/products',
isDev ? mockHandler : realHandler
);
function mockHandler(ctx) {
ctx.body = [{ id: 1, name: 'Mock Product' }];
}
Route Request Transformation
Uniformly process request data:
router.use(async (ctx, next) => {
// Convert camelCase to snake_case
if (ctx.request.body) {
ctx.request.body = convertKeys(ctx.request.body);
}
await next();
});
Route Response Formatting
Standardize response formats:
router.use(async (ctx, next) => {
await next();
if (ctx.body && !ctx.body.error) {
ctx.body = {
code: 200,
data: ctx.body,
timestamp: Date.now()
};
}
});
Route Health Checks
Add health check endpoints:
router.get('/health', (ctx) => {
ctx.body = {
status: 'UP',
services: {
db: checkDb(),
cache: checkCache()
}
};
});
Route Deprecation Marking
Mark routes for deprecation:
router.get('/old-api',
deprecated({
message: 'Use /new-api instead',
sunset: '2023-12-31'
}),
oldApiHandler
);
Route Experimental Features
Isolate experimental features:
router.get('/experimental/feature-x',
experimental({
flag: 'FEATURE_X_ENABLED'
}),
experimentalHandler
);
Route Traffic Tagging
Tag traffic for specific routes:
router.use('/admin', (ctx, next) => {
ctx.state.tags = ctx.state.tags || [];
ctx.state.tags.push('admin');
return next();
});
Route Data Masking
Automatically mask sensitive data:
router.use('/users', (ctx, next) => {
await next();
if (ctx.body?.email) {
ctx.body.email = maskEmail(ctx.body.email);
}
});
Route Request Recording
Record production requests:
router.use((ctx, next) => {
if (shouldRecord(ctx.path)) {
requestRecorder.record(ctx);
}
return next();
});
Route Intelligent Degradation
Automatically degrade during exceptions:
router.get('/recommendations',
circuitBreaker({
fallback: getCachedRecommendations
}),
getLiveRecommendations
);
Route Blue-Green Deployment
Support blue-green deployment switching:
router.get('/products',
blueGreen({
blue: blueHandler,
green: greenHandler,
selector: ctx => ctx.headers['x-deployment-group']
})
);
Route Geo-Routing
Route requests based on region:
router.get('/content',
geoRouter({
'US': usContentHandler,
'EU': euContentHandler,
'default': globalContentHandler
})
);
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:路由性能监控与分析
下一篇:HTTP 请求方法全面解析