Skip to content
On this page

Koa路由管理

Koa本身不提供路由功能,但可以通过第三方中间件如@koa/router来实现路由管理。路由是指如何处理不同的URL路径和HTTP方法。

安装路由中间件

bash
npm install @koa/router

基础路由

基本路由定义

javascript
const Koa = require('koa');
const Router = require('@koa/router');

const app = new Koa();
const router = new Router();

// GET路由
router.get('/', (ctx, next) => {
  ctx.body = 'Home page';
});

// POST路由
router.post('/users', (ctx, next) => {
  ctx.body = 'Create user';
});

// PUT路由
router.put('/users/:id', (ctx, next) => {
  ctx.body = `Update user ${ctx.params.id}`;
});

// DELETE路由
router.delete('/users/:id', (ctx, next) => {
  ctx.body = `Delete user ${ctx.params.id}`;
});

// 应用路由中间件
app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000);

多种HTTP方法路由

javascript
// 处理多种HTTP方法
router
  .get('/users', (ctx) => {
    ctx.body = 'Get all users';
  })
  .post('/users', (ctx) => {
    ctx.body = 'Create user';
  })
  .put('/users/:id', (ctx) => {
    ctx.body = `Update user ${ctx.params.id}`;
  })
  .delete('/users/:id', (ctx) => {
    ctx.body = `Delete user ${ctx.params.id}`;
  });

// 或者使用all方法处理所有HTTP方法
router.all('/users', (ctx) => {
  ctx.body = 'Handle all methods for /users';
});

路由参数

路径参数

javascript
// 单个参数
router.get('/users/:id', (ctx) => {
  ctx.body = `User ID: ${ctx.params.id}`;
});

// 多个参数
router.get('/users/:userId/posts/:postId', (ctx) => {
  ctx.body = `User: ${ctx.params.userId}, Post: ${ctx.params.postId}`;
});

// 可选参数
router.get('/users/:id?', (ctx) => {
  if (ctx.params.id) {
    ctx.body = `User ID: ${ctx.params.id}`;
  } else {
    ctx.body = 'All users';
  }
});

// 带修饰符的参数
router.get('/users/:id{[0-9]+}', (ctx) => {
  // 只匹配数字ID
  ctx.body = `User ID: ${ctx.params.id}`;
});

// 零个或多个参数
router.get('/files/*', (ctx) => {
  ctx.body = `File path: ${ctx.params[0]}`;
});

查询参数

javascript
router.get('/search', (ctx) => {
  const { q, page = 1, limit = 10, sort = 'createdAt' } = ctx.query;
  
  ctx.body = {
    query: q,
    pagination: {
      page: parseInt(page),
      limit: parseInt(limit)
    },
    sort
  };
});

// 查询字符串
router.get('/api', (ctx) => {
  ctx.body = {
    query: ctx.query,           // 解析后的查询参数对象
    queryString: ctx.querystring // 原始查询字符串
  };
});

路由前缀和嵌套

路由前缀

javascript
// 为一组路由设置公共前缀
const apiRouter = new Router({ prefix: '/api/v1' });

apiRouter.get('/users', (ctx) => {
  ctx.body = 'API: Get all users';
});

apiRouter.get('/posts', (ctx) => {
  ctx.body = 'API: Get all posts';
});

app.use(apiRouter.routes());

路由嵌套

javascript
const userRouter = new Router();
const postRouter = new Router();

// 用户相关路由
userRouter.get('/', (ctx) => { ctx.body = 'Get all users'; });
userRouter.get('/:id', (ctx) => { ctx.body = `Get user ${ctx.params.id}`; });

// 帖子相关路由
postRouter.get('/', (ctx) => { ctx.body = 'Get all posts'; });
postRouter.get('/:id', (ctx) => { ctx.body = `Get post ${ctx.params.id}`; });

// 主路由
const mainRouter = new Router();
mainRouter.use('/users', userRouter.routes());
mainRouter.use('/posts', postRouter.routes());

app.use(mainRouter.routes());

路由中间件

路由级中间件

javascript
// 认证中间件
const auth = async (ctx, next) => {
  const token = ctx.headers.authorization;
  if (!token) {
    ctx.status = 401;
    ctx.body = { error: 'Unauthorized' };
    return;
  }
  await next();
};

// 为特定路由应用中间件
router.get('/protected', auth, (ctx) => {
  ctx.body = 'This is protected content';
});

// 为多个路由应用中间件
const apiRoutes = new Router();
apiRoutes.use(auth); // 应用到所有路由
apiRoutes.get('/profile', (ctx) => { ctx.body = 'User profile'; });
apiRoutes.get('/settings', (ctx) => { ctx.body = 'User settings'; });

参数验证中间件

javascript
// 参数验证中间件
const validateUserId = async (ctx, next) => {
  const { id } = ctx.params;
  if (!id || isNaN(id)) {
    ctx.status = 400;
    ctx.body = { error: 'Invalid user ID' };
    return;
  }
  await next();
};

router.get('/users/:id', validateUserId, (ctx) => {
  ctx.body = `User ID: ${ctx.params.id}`;
});

// 使用router.param为特定参数添加验证
router.param('userId', async (id, ctx, next) => {
  if (!/^[0-9a-fA-F]{24}$/.test(id)) {
    ctx.status = 400;
    ctx.body = { error: 'Invalid user ID format' };
    return;
  }
  await next();
});

router.get('/users/:userId', (ctx) => {
  ctx.body = `Valid user ID: ${ctx.params.userId}`;
});

路由分组和模块化

路由模块化

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

// 用户验证中间件
const validateUser = async (ctx, next) => {
  // 验证逻辑
  await next();
};

router.get('/', async (ctx) => {
  ctx.body = await getUserList();
});

router.get('/:id', async (ctx) => {
  ctx.body = await getUserById(ctx.params.id);
});

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

router.put('/:id', validateUser, async (ctx) => {
  ctx.body = await updateUser(ctx.params.id, ctx.request.body);
});

router.delete('/:id', async (ctx) => {
  ctx.body = await deleteUser(ctx.params.id);
});

module.exports = router;

// app.js
const userRouter = require('./routes/users');
app.use(userRouter.routes());

路由权限控制

// 权限中间件
const requireRole = (role) => {
  return async (ctx, next) => {
    if (!ctx.state.user || ctx.state.user.role !== role) {
      ctx.status = 403;
      ctx.body = { error: 'Insufficient permissions' };
      return;
    }
    await next();
  };
};

// 应用权限控制
router.get('/admin', requireRole('admin'), (ctx) => {
  ctx.body = 'Admin panel';
});

router.get('/moderator', requireRole('moderator'), (ctx) => {
  ctx.body = 'Moderator panel';
});

高级路由功能

正则表达式路由

// 使用正则表达式定义路由
router.get(/^\/users\/(\d+)$/, (ctx) => {
  // ctx.params[0] 包含匹配的数字
  ctx.body = `User ID: ${ctx.params[0]}`;
});

// 复杂的正则表达式路由
router.get(/^\/files\/(images|videos)\/(.*)/, (ctx) => {
  const [type, filename] = ctx.params;
  ctx.body = `Type: ${type}, File: ${filename}`;
});

嵌套路由参数

// 嵌套资源路由
const router = new Router();

// 用户的帖子
router.get('/users/:userId/posts', (ctx) => {
  ctx.body = `Posts for user ${ctx.params.userId}`;
});

// 用户的特定帖子
router.get('/users/:userId/posts/:postId', (ctx) => {
  ctx.body = `Post ${ctx.params.postId} for user ${ctx.params.userId}`;
});

// 用户的帖子的评论
router.get('/users/:userId/posts/:postId/comments', (ctx) => {
  ctx.body = `Comments for post ${ctx.params.postId} of user ${ctx.params.userId}`;
});

路由错误处理

404处理

// 404中间件应该在路由之后
app.use(router.routes());
app.use(async (ctx, next) => {
  if (ctx.status === 404) {
    ctx.body = { error: 'Route not found' };
    ctx.status = 404;
  }
  await next();
});

路由特定错误处理

// 路由级别的错误处理
router.get('/users/:id', async (ctx) => {
  try {
    const user = await findUser(ctx.params.id);
    if (!user) {
      ctx.status = 404;
      ctx.body = { error: 'User not found' };
      return;
    }
    ctx.body = user;
  } catch (err) {
    ctx.status = 500;
    ctx.body = { error: 'Internal server error' };
  }
});

路由性能优化

路由缓存

// 简单的路由响应缓存
const routeCache = new Map();

const cachedRoute = (ttl = 60) => {
  return async (ctx, next) => {
    const key = `${ctx.method}:${ctx.path}`;
    const cached = routeCache.get(key);
    
    if (cached && Date.now() - cached.timestamp < ttl * 1000) {
      ctx.body = cached.body;
      ctx.status = cached.status;
      return;
    }
    
    await next();
    
    // 只缓存成功响应
    if (ctx.status >= 200 && ctx.status < 300) {
      routeCache.set(key, {
        body: ctx.body,
        status: ctx.status,
        timestamp: Date.now()
      });
    }
  };
};

router.get('/expensive-operation', cachedRoute(300), async (ctx) => {
  // 耗时操作
  ctx.body = await performExpensiveOperation();
});

路由分组优化

// 将相关路由组织在一起以提高性能
const apiRouter = new Router({ prefix: '/api' });

// 批量定义相似路由
const defineCRUDRoutes = (router, resource, controller) => {
  router.get(`/${resource}`, controller.list);
  router.get(`/${resource}/:id`, controller.get);
  router.post(`/${resource}`, controller.create);
  router.put(`/${resource}/:id`, controller.update);
  router.delete(`/${resource}/:id`, controller.delete);
};

defineCRUDRoutes(apiRouter, 'users', userController);
defineCRUDRoutes(apiRouter, 'posts', postController);

app.use(apiRouter.routes());

路由最佳实践

1. RESTful路由设计

// 遵循RESTful设计原则
const resourceRouter = new Router();

// GET /resource - 获取资源列表
resourceRouter.get('/', listResources);

// GET /resource/:id - 获取单个资源
resourceRouter.get('/:id', getResource);

// POST /resource - 创建资源
resourceRouter.post('/', createResource);

// PUT /resource/:id - 更新资源
resourceRouter.put('/:id', updateResource);

// DELETE /resource/:id - 删除资源
resourceRouter.delete('/:id', deleteResource);

// GET /resource/:id/related - 获取相关资源
resourceRouter.get('/:id/related', getRelatedResources);

2. 路由验证

// 输入验证
const validateInput = (schema) => {
  return async (ctx, next) => {
    try {
      const validatedData = await schema.validateAsync(ctx.request.body);
      ctx.validatedBody = validatedData;
      await next();
    } catch (error) {
      ctx.status = 400;
      ctx.body = { error: error.details[0].message };
    }
  };
};

// 使用验证中间件
const userSchema = {
  // Joi或其他验证库的模式
};

router.post('/users', validateInput(userSchema), (ctx) => {
  ctx.body = ctx.validatedBody;
});

通过合理的路由管理,可以构建结构清晰、易于维护的Koa应用。