Koa 简介

Context 上下文

理解 Koa 的 ctx 上下文对象,掌握常用属性和别名,学会获取请求信息与设置响应。

🎯 引言

学完这篇文章,你能说清楚 ctx 到底是什么、里面装了什么,会用 ctx.queryctx.bodyctx.status 这些常用别名获取请求信息、设置响应内容,这是写 Koa 代码每天都要打交道的基础功。


🧱 ctx 是什么

在原生 http 和 Express 里,请求信息和响应信息分成 reqres 两个对象。Koa 把它们俩封装进了一个对象,这个对象就是 ctx(Context,上下文)。

可以把 ctx 想象成快递员的配送单:一张单子上既写着“收件人信息”(请求方发来的数据),也留着“回执栏”(我们要返回的响应)。处理一次请求,从头到尾只需要拿着这一张单子,不用在两个对象之间来回切换。

每个请求进来,Koa 都会为它创建一个独立的 ctx,请求之间互不影响。中间件的写法还记得吗:

app.js
app.use(async (ctx) => {
    // ctx 就是这次请求的“配送单”
});

🧱 ctx.request 与 ctx.response

ctx 内部又把信息分成两块:

  • ctx.request:请求相关信息,比如请求地址、请求方法、查询参数。
  • ctx.response:响应相关信息,比如返回内容、状态码、响应头。

看个例子:

app.js
app.use(async (ctx) => {
    console.log(ctx.request.path); // 请求路径,如 /users
    console.log(ctx.request.method); // 请求方法,如 GET

    ctx.response.body = '你好'; // 设置响应内容
    ctx.response.status = 200; // 设置状态码
});

但这样写有点啰嗦。Koa 提供了别名机制,把高频属性直接挂到 ctx 上,让我们少敲几个字。


💡 常用别名速查

别名是实际开发中的主流写法,最常用的有这些:

别名等价于作用
ctx.queryctx.request.query查询参数对象
ctx.pathctx.request.path请求路径
ctx.methodctx.request.method请求方法
ctx.bodyctx.response.body读取/设置响应内容
ctx.statusctx.response.status读取/设置状态码
ctx.set()ctx.response.set()设置响应头

综合起来用一次:

app.js
app.use(async (ctx) => {
    // 访问 /search?keyword=koa 时
    const keyword = ctx.query.keyword; // 拿到查询参数 koa

    ctx.set('X-Powered-By', 'Koa'); // 自定义响应头
    ctx.status = 200;
    ctx.body = {
        message: `你搜索的是:${keyword}`,
        path: ctx.path,
        method: ctx.method,
    };
});

浏览器访问 http://localhost:3000/search?keyword=koa,就能收到一段包含搜索词的 JSON 响应。注意查询参数不需要自己解析 ?keyword=koa 这段字符串,ctx.query 直接给的是解析好的对象。

记住一个规律:取请求数据用 ctx.xxx 读取,回响应数据给 ctx.bodyctx.status 赋值。这套别名能覆盖日常开发的大部分场景。

⚠️ 原生 req 和 res 还能用吗

能。Koa 底层仍然基于 Node.js 的 http 模块,原生对象挂在 ctx.reqctx.res 上:

app.js
app.use(async (ctx) => {
    console.log(ctx.req.url); // 原生请求对象的 url
});

不建议混用。Koa 封装好的 ctx 已经能处理几乎所有场景,直接用原生对象操作响应(比如 ctx.res.end())会绕过 Koa 的响应处理流程,容易引发响应状态混乱。

一条原则:响应一律通过 ctx.body 设置,不要去碰 ctx.res.end()。只有极少数底层场景(如特殊的流处理)才需要动原生对象。

🧾 小节总结

  • ctx 是 Koa 为每个请求创建的上下文对象,把请求和响应封装在一起,请求之间互相独立。
  • ctx.request 存请求信息,ctx.response 存响应信息。
  • 高频属性都有 ctx 上的别名:ctx.queryctx.pathctx.methodctx.bodyctx.statusctx.set()
  • 查询参数通过 ctx.query 直接拿到解析好的对象,不需要手动解析。
  • 原生 ctx.reqctx.res 仍然存在,但不要混用,响应统一走 ctx.body

❓ 知识问答

Q1:ctx.query 拿到的参数是什么类型?

都是字符串。比如访问 ?page=2ctx.query.page 是字符串 '2' 而不是数字 2。需要数字时用 Number() 转一下。

Q2:同一个查询参数传了多次怎么办?

比如 ?tag=a&tag=bctx.query.tag 会是数组 ['a', 'b']。只传一次时是字符串,处理前注意判断类型。

Q3:ctx.body 可以读吗?

可以。在后续中间件里能读到前面中间件设置的值,不过实际开发中“读 body”的场景很少,一般都是最后设置一次。

Q4:ctx.status 不设置会怎样?

默认是 404。但只要给 ctx.body 赋了值,Koa 会自动把状态码改成 200。所以一般不需要手动设置 200。


🧪 小练习

  1. 写一个接口 /user,接收 nameage 两个查询参数,返回 JSON:{ "greeting": "你好,xxx,今年 xx 岁" }
app.js
import Koa from 'koa';

const app = new Koa();

app.use(async (ctx) => {
    if (ctx.path === '/user') {
        // 请在这里编写代码
    }
});

app.listen(3000);
  1. 用浏览器访问 http://localhost:3000/user?name=小明&age=18 验证结果。

🎉 恭喜你已经掌握了 ctx 的常用操作!下一篇我们挑战 Koa 的精华:中间件与洋葱模型,搞懂它你就真正理解 Koa 了。