Koa 简介

Koa 是由 Express 原班团队打造的 Node.js Web 框架,其核心目标是成为更轻量、更具表现力的 Web 应用和 API 开发基础。若您曾因 Express 应用规模扩大后陷入回调地狱而困扰,Koa 通过采用异步函数彻底摆脱回调模式,显著提升错误处理能力。另一项独特特性是级联机制——中间件会先 "向下游" 执行,再逆流 "回溯上游",从而实现更可控的流程管理(本章后续将具体演示)。更多信息请访问 https://koajs.com/

安装和配置 Koa

现在,我们将使用 Backpack 的默认配置(无需创建 Backpack 配置文件)创建一个 Koa 应用,步骤如下:

  1. 通过 npm 安装 Koa

    $ npm i koa
  2. 使用 /src/ 作为 Backpack 默认入口目录,并在该目录下创建入口文件,编写极简的 Koa 风格代码:

    // src/index.js
    const Koa = require('koa')
    const app = new Koa()
    
    app.use(async ctx => {
      ctx.body = 'Hello World'
    })
    
    app.listen(3000)
  3. 以开发模式运行 Koa 应用:

    $ npm run dev

通过浏览器访问 127.0.0.1:3000 即可看到页面显示 "Hello World"。如果您曾使用 Express 开发 Node.js 应用,会发现 Koa 能以更简洁的代码实现相同功能。接下来,我们将在后续章节中探讨 Koa 的上下文(Context)机制和级联(Cascading)工作原理。

什么是 ctx?

您可能已经注意到,我们在上一节编写的极简代码中出现的 ctx 究竟是什么,以及 Express 应用中常见的 reqres 对象去了哪里。实际上,在 Koa 中它们并未消失,而是被封装到了一个统一对象中——即 Koa 上下文(Context),简称 ctx。我们可以通过以下方式访问请求和响应对象:

app.use(async ctx => {
  ctx.request  // Node.js 请求对象
  ctx.response // Node.js 响应对象
})

由此可见,通过 ctx.requestctx.response 依然能轻松访问这两个核心 HTTP 对象。它们并非被移除,只是被优雅地整合到了 Koa 的上下文对象 ctx 中。接下来,我们将在下一节探讨 Koa 的级联(Cascading)机制如何运作。

理解 Koa 级联的工作方式

简单来说,Koa 的级联机制通过顺序调用下游中间件,再控制其反向回溯上游来实现。我们通过一个简单的 Koa 应用来演示这一核心特性:

  1. /src/ 目录创建 index.js 文件(如前文所示基础结构):

    // src/index.js
    const Koa = require('koa')
    const app = new Koa()
    
    app.use(async ctx => {
      console.log('Hello World')
      ctx.body = 'Hello World'
    })
    
    app.listen(3000)
  2. Hello World 中间件前插入三个中间件:

    app.use(async (ctx, next) => {
      console.log('Time started at: ', Date.now())
      await next()
    })
    
    app.use(async (ctx, next) => {
      console.log('I am the first')
      await next()
      console.log('I am the last')
    })
    
    app.use(async (ctx, next) => {
      console.log('I am the second')
      await next()
      console.log('I am the third')
    })
  3. 运行开发模式后终端将输出:

    Time started at: 1554647742894
    I am the first
    I am the second
    Hello World
    I am the third
    I am the last

执行流程说明:

请求依次流经 Time started at: → I am the first → I am the second 最终到达 Hello World。当没有更多下游中间件时,会按 I am the third → I am the last 顺序回溯上游。

完整示例可在 GitHub 仓库的 /chapter-8/koa/cascading/ 目录查看。

接下来,我们将介绍开发全栈 Koa 应用需要安装的关键依赖(使其具备 Express 类似功能)。

为 Koa 应用安装依赖

Koa 的设计理念是极简主义,其核心不包含任何中间件。与 Express 自带路由功能不同,Koa 默认不提供路由系统,这要求开发者从第三方包中选择解决方案(可参考 Koa GitHub 主页 https://github.com/koajs )。经过实践测试,部分路由包可能不完全符合需求。本书主要使用 koa-router 进行路由管理,同时结合其他关键依赖构建 Koa API 开发骨架。以下是具体实现步骤:

  1. 安装 koa-router 路由模块

    $ npm i koa-router

    在入口文件中导入 koa-router,并添加一个根路由 /,如下所示:

    // src/index.js
    const Router = require('koa-router')
    const router = new Router()
    
    router.get('/', (ctx, next) => {
      ctx.body = 'Hello World'
    })
    
    app
      .use(router.routes())
      .use(router.allowedMethods())

    你可以在 KoaGitHub 仓库 https://github.com/koajs/koa-router 找到关于这个中间件的更多信息。这个模块是从 ZijianHe/koa-router (https://github.com/ZijianHe/koa-router) fork 过来的,是 Koa 社区中最广泛使用的路由模块。它提供了类似 Express 的路由方式,使用 app.getapp.putapp.post 等。它还支持其他重要特性,例如多路由中间件以及多个和可嵌套的路由器。

  2. 安装 koa-bodyparser 请求体解析模块

    $ npm i koa-bodyparser

    在入口文件中导入 koa-bodyparser,注册它,并创建一个 /post 根路由,如下所示:

    // src/index.js
    const bodyParser = require('koa-bodyparser')
    app.use(bodyParser())
    
    router.post('/post', (ctx, next) => {
      ctx.body = ctx.request.body // 获取表单数据
    })

    你可以在 KoaGitHub 仓库 https://github.com/koajs/bodyparser 找到关于这个中间件的更多信息。你可能想知道:什么是 body parser 呢?当我们处理 HTML 表单时,我们使用 application/x-www-form-urlencodedmultipart/form-data 在客户端和服务器端之间传输数据,例如:

    // application/x-www-form-urlencoded
    <form action="/update" method="post">
      //...
    </form>
    
    // multipart/form-data
    <form action="/update" method="post" enctype="multipart/form-data">
      //...
    </form>

    HTML 表单的默认类型是 application/x-www-urlencoded。如果我们想读取 HTTP POST、PATCH 和 PUT 请求的数据,我们需要使用 body parser。body parser 是一种中间件,它可以解析传入的请求,将包含表单数据的块组装起来,然后创建一个包含表单数据的 body 对象,这样我们就可以通过 ctx 对象的 request 属性来访问这些数据,如下所示:

    ctx.body = ctx.request.body
  3. 安装 koa-favicon 网站图标模块

    $ npm i koa-favicon

    在入口文件中导入 koa-favicon,并使用 favicon 的路径注册它,如下所示:

    // src/index.js
    const favicon = require('koa-favicon')
    app.use(favicon('public/favicon.ico')) // 需提前创建 favicon.ico 文件

    你可以在 KoaGitHub 仓库 https://github.com/koajs/favicon 找到关于这个中间件的更多信息。它是一个用于提供网站图标(favicon)的中间件。所以,让我们创建一个 favicon.ico 文件,并将其保存在项目根目录下的 /public 文件夹中。当你刷新首页时,你应该能在浏览器的标签页上看到这个网站图标。

  4. 安装 koa-static 静态资源模块

    $ npm i koa-static

    在入口文件中导入 koa-static,并使用以下路径注册它,如下所示:

    const serve = require('koa-static')
    app.use(serve('.'))         // 开放根目录访问
    app.use(serve('static/fixtures'))    // 开放/static目录访问

你可以在 KoaGitHub 仓库 https://github.com/koajs/static 找到关于这个中间件的更多信息。Koa 默认情况下不允许你提供静态文件。因此,这个中间件将允许你从你的 API 提供静态文件。例如,我们刚才设置的路径将允许我们从项目根目录下的 /static 文件夹访问以下文件:

  • GET /package.json 位于 127.0.0.1:3000/package.json

  • GET /hello.txt 位于 127.0.0.1:3000/hello.txt

在接下来的章节中,当我们使用 Koa 创建 API 时,我们将使用这个骨架。现在,让我们在下一节中了解如何将 KoaNuxt 集成。

你可以在我们的 GitHub 仓库的 /chapter-8/koa/skeleton/ 找到这个骨架应用。