Koa 简介

路由

学会使用 @koa/router 定义路由、获取路由参数与查询参数,掌握路由模块化拆分的方法。

🎯 引言

学完这篇文章,你能给 Koa 应用装上路由,让 GET /usersPOST /users 各司其职,拿到路径里的动态参数,还能把路由拆分到独立文件里管理。Koa 本身不带路由,这篇我们把它补齐。


🧱 安装 @koa/router

前面说过,Koa 核心非常精简,路由功能由官方的 @koa/router 库提供。本文基于 Node.js v22 LTS + @koa/router 15.7.0 编写和验证。

npm install @koa/router

基本用法分三步:创建路由实例、定义路由、把路由注册成中间件:

app.js
import Koa from 'koa';
import Router from '@koa/router';

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

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

router.get('/about', async (ctx) => {
    ctx.body = '关于页面';
});

// 把路由注册到应用上
app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000);

现在访问 //about 会返回不同内容,访问其他地址会得到 404。router.get() 的写法和 Express 路由几乎一样,唯一的区别是回调参数从 (req, res) 换成了 (ctx)


🛠 常用请求方法

RESTful 风格的接口需要区分请求方法,@koa/router 都支持:

app.js
router.get('/users', async (ctx) => {
    ctx.body = '获取用户列表';
});

router.post('/users', async (ctx) => {
    ctx.body = '创建用户';
});

router.put('/users/1', async (ctx) => {
    ctx.body = '更新用户';
});

router.delete('/users/1', async (ctx) => {
    ctx.body = '删除用户';
});

同一个路径配上不同方法,就是 RESTful 接口的经典形态。GET 和 DELETE 可以在浏览器或地址栏测试,POST 和 PUT 建议用 Apifox、Postman 这类接口工具来发请求。

接口调试工具的页面会不定期更新,界面入口可能与文中描述略有不同。核心思路不变:选择请求方法、填写地址、查看响应,操作时以工具的最新页面为准。

🧱 路由参数与查询参数

路由参数是路径里的动态部分,用 :参数名 定义,通过 ctx.params 获取:

app.js
router.get('/users/:id', async (ctx) => {
    const id = ctx.params.id; // 访问 /users/42 时,id 是 '42'
    ctx.body = { id, name: '小明' };
});

查询参数? 后面的部分,通过 ctx.query 获取(上一篇学过):

app.js
router.get('/users', async (ctx) => {
    // 访问 /users?page=2&size=10
    const { page, size } = ctx.query;
    ctx.body = { page, size };
});
两个参数的名字容易混,记一个区分法:ctx.params 管路径里的(/users/:id),ctx.query 管问号后的(?page=2)。两者的值都是字符串,需要数字时记得转换。

💡 allowedMethods 有什么用

app.use(router.allowedMethods()) 这行代码看着不起眼,作用有两个:

  • 404 语义更准:路径存在但方法不对时(比如只定义了 GET /users,却发来了 POST /users),返回 405 Method Not Allowed 而不是 404,告诉客户端“地址有,但你方法用错了”。
  • 自动响应 OPTIONS 请求:浏览器跨域预检时发来 OPTIONS 请求,它会自动回复该路径支持哪些方法。

简单说,加上它,你的接口对“错误请求”的反馈会更规范,建议每次都带上。


🧰 Router 模块化拆分

所有路由都堆在 app.js 里,项目一大就没法维护了。实际开发会把不同模块的路由拆到独立文件:

routes/users.js
import Router from '@koa/router';

const router = new Router({
    prefix: '/users', // 路由前缀,下面的路径都会自动加上 /users
});

router.get('/', async (ctx) => {
    ctx.body = '用户列表';
});

router.get('/:id', async (ctx) => {
    ctx.body = `用户详情:${ctx.params.id}`;
});

export default router;

在主文件里引入并注册:

app.js
import Koa from 'koa';
import usersRouter from './routes/users.js';

const app = new Koa();

app.use(usersRouter.routes());
app.use(usersRouter.allowedMethods());

app.listen(3000);

注意 prefix: '/users' 配合 router.get('/'),最终匹配的路径是 /usersrouter.get('/:id') 匹配的是 /users/1 这样的地址。每个业务模块一个路由文件,结构清晰,和 Express 里 express.Router() 的拆分思路完全一致。


🧾 小节总结

  • Koa 不带路由,需安装官方库 @koa/router,创建实例后用 router.get() 等方法定义路由。
  • 路由通过 app.use(router.routes()) 注册,建议同时加上 router.allowedMethods()
  • 路由参数 :idctx.params 取,查询参数 ?page=2ctx.query 取,值都是字符串。
  • allowedMethods() 让方法不匹配时返回 405,并自动处理 OPTIONS 请求。
  • new Router({ prefix }) 加分文件导出,实现路由模块化拆分。

❓ 知识问答

Q1:路由定义的先后顺序有影响吗?

有。@koa/router 按注册顺序匹配,先匹配到的先生效。比如 /users/new 要定义在 /users/:id 前面,否则 new 会被当成 id 参数匹配走。

Q2:一个路由能同时处理多种方法吗?

可以,但一般不这么写。按 RESTful 规范,每个方法各定义一条路由,逻辑分开更清晰。

Q3:ctx.params.id 是数字吗?

不是,是字符串。路径本来就是文本,取到后需要数字时用 Number(ctx.params.id) 转换,记得处理 NaN 的情况。

Q4:忘记注册 allowedMethods 会怎样?

功能上接口照常工作,只是方法不匹配时统一返回 404 而不是 405,也无法自动响应 OPTIONS 请求。建议养成两个都注册的习惯。


🧪 小练习

  1. 新建路由文件 routes/articles.js,定义两个接口:GET /articles 返回文章列表(假数据即可),GET /articles/:id 返回指定 id 的文章,并在 app.js 中注册。
routes/articles.js
import Router from '@koa/router';

const router = new Router({ prefix: '/articles' });

// 请在这里编写代码

export default router;
  1. 分别用浏览器访问 /articles/articles/1,确认两个路由都能正确响应。

🎉 恭喜你已经给 Koa 装上了路由!下一篇我们处理两个实际问题:解析前端发来的请求体数据,以及优雅地处理接口错误。