koa 路由与请求处理
本教程共 76 篇 · 第 45 篇 · 更新于 2026-07-25 · 约 4 分钟阅读
45. koa 路由与请求处理
本节目标:koa-router 路由、请求解析和响应处理。
上一章的 koa 应用不管访问什么 URL 都返回同样的内容,这显然不够用。本章我们引入 @koa/router 做路由分发,再用 @koa/bodyparser 解析请求体,最后把控制器拆到独立目录,让项目结构清晰起来。
路由基础
koa 核心没有内置路由,需要安装 @koa/router:
npm install @koa/router
基本用法:
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('/hello/:name', async (ctx) => {
ctx.body = `你好,${ctx.params.name}`;
});
router.post('/login', async (ctx) => {
ctx.body = '登录成功';
});
app.use(router.routes());
app.listen(3000);
路由参数通过 ctx.params 获取,查询字符串通过 ctx.query 获取:
// GET /search?q=node
router.get('/search', async (ctx) => {
ctx.body = `搜索:${ctx.query.q}`;
});
Tip
ctx.query返回的是已经解析好的对象,不用自己调URLSearchParams,比原生 http 模块省事多了。
解析请求体
POST 请求发过来的表单或 JSON,koa 默认不会自动解析。装 @koa/bodyparser:
npm install @koa/bodyparser
import { bodyParser } from '@koa/bodyparser';
app.use(bodyParser());
router.post('/login', async (ctx) => {
const { username, password } = ctx.request.body;
ctx.body = { username, msg: '收到' };
});
注意:bodyParser 必须放在 router.routes() 之前注册,否则请求走到路由时 body 还没解析好。
用 curl 测试:
curl -X POST http://localhost:3000/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"123456"}'
中间件的顺序艺术
koa 中间件的注册顺序就是执行顺序。一个典型的 Web 应用通常按这个顺序排列:
import Koa from 'koa';
import { bodyParser } from '@koa/bodyparser';
import Router from '@koa/router';
const app = new Koa();
// 1. 日志与计时
app.use(async (ctx, next) => {
const start = Date.now();
await next();
console.log(`${ctx.method} ${ctx.url} - ${ctx.status} - ${Date.now() - start}ms`);
});
// 2. 解析请求体
app.use(bodyParser());
// 3. 路由
const router = new Router();
router.get('/', async (ctx) => { ctx.body = 'home'; });
app.use(router.routes());
app.listen(3000);
想象一下流水线上的工人:第一个负责打卡计时,第二个负责拆包裹,第三个负责分拣货物。顺序错了,包裹还没拆就分拣,肯定要出错。
拆分控制器
路由一多,全堆在一个文件里会爆炸。我们按功能拆到 controllers 目录。
controllers/user.mjs:
export async function list(ctx) {
ctx.body = [{ id: 1, name: 'Alice' }];
}
export async function detail(ctx) {
ctx.body = { id: ctx.params.id, name: 'Alice' };
}
app.mjs:
import Router from '@koa/router';
import * as userController from './controllers/user.mjs';
const router = new Router();
router.get('/users', userController.list);
router.get('/users/:id', userController.detail);
路由前缀与嵌套
给一组 API 加统一前缀:
const apiRouter = new Router({ prefix: '/api/v1' });
apiRouter.get('/users', async (ctx) => {
ctx.body = { users: [] };
});
app.use(apiRouter.routes());
这样用户列表的实际路径就是 /api/v1/users。
允许的方法
router.routes() 默认只响应对应 HTTP 方法,其他方法会返回 405。如果你想让不匹配的路由直接走 404,可以加 router.allowedMethods():
app.use(router.routes());
app.use(router.allowedMethods());
它会自动处理 OPTIONS 预检请求,并给不支持的 HTTP 方法返回 405,附带 Allow 响应头告诉你支持哪些方法。