# 一、Koa 基础
# 1、基本概述
Koa 同样是由TJ Holowaychuk (opens new window) 开发的,也是现在他在维护的框架,express 已经交给团队在维护了。
Koa 的产生是为了让 express 变得轻量级(核心代码只有 1600+行),并且 Koa 中不再提供内置中间件,一切中间件按需安装使用即可。
Koa 的使用和 express 基本一致,不同的是:Koa 放弃了 express 中采用回调的方式触发 中间件,转而使用 asyn/await 的异步方式触发 中间件。
express 采用 callback 来处理异步,Koa1 采用 generator,Koa2 采用 asyn/await
# 2、常规使用
安装:
npm install koa
使用:
与 express 框架对比
const express = require('express')
const app = express()
// 逻辑处理 —— " 中间件💖 "
// 例如:
app.post('/login', (req, res, next) => {
//
res.send()
})
app.listen(6061)
const Koa = require('koa')
const app = new Koa() // 类
// 逻辑处理 —— " 中间件💖 "
// 例如:
app.use(async (ctx, next) => {
//
ctx.res.end()
})
app.listen(6061)
# 3、ctx 上下文 🎈
context 上下文这个概念,其实在 pinia(actions 的 context 参数)、react(Context 共享机制) 也有相关的东西。
Koa 中的 context 包含了 request 对象 和 response 对象
app.context.db = db()
app.use(async (ctx, next) => {
//
console.log(ctx.request) // koa封装的请求对象,可以简写为:ctx
console.log(ctx.req) // 原生node的请求对象
console.log(ctx.response) // koa封装的请求对象,可以简写为:ctx
console.log(ctx.res) // 原生node的请求对象
//
console.log(ctx.db) // 自定义的ctx属性🚩
})
请求响应图示
# 4、错误处理
利用底层的 event 模块(EventEmitter )进行事件监听
ctx.app.emit('error', -1002, ctx)
const Koa = require('koa')
const app = new Koa()
app.on('error', (err, ctx) => {
const errCode = typeof err === 'number' ? err : 0
let message = '未知的错误信息'
switch (errCode) {
case -1001:
message = '没有输入用户名和密码'
break
case -1002:
message = '输入用户名和密码错误'
break
default:
break
}
ctx.body = {
code: errCode,
msg: message
}
})
app.listen(6061)
# 二、开发应用
# 1、koa 路由
在 koa 中,并没有集成 express 中的 app.METHODS
。
因此,如果我们要完成对app.use
的进一步细分,可以采用一些出色的开源库,例如:koa-router (opens new window)、@koa/router (opens new window)(两种使用一致)
使用上和 express 中的 router 中间件(
express().Router()
)类似
代码示例
const Koa = require('koa')
const Router = require('./routes/index.js')
const app = new Koa()
// 注册koa路由
app.use(Router.routes()).use(Router.allowedMethods())
app.listen(6061)
// routes/index.js
const Router = require('@koa/router')
const router = new Router()
const postsRouter = require('./postsRouter.js')
// 注册router局部路由
router.use(postsRouter.routes(), postsRouter.allowedMethods())
// ……
module.exports = router
// routes/postsRouter.js
const Router = require('@koa/router')
const postsRouter = new Router({ prefix: '/posts' })
postsRouter
.get('/', (ctx, next) => {})
.get('/:id', (ctx, next) => {})
.post('/', (ctx, next) => {})
.path('/:id', (ctx, next) => {})
.delete('/:id', (ctx, next) => {})
module.exports = postsRouter
# 2、请求处理 ✨
在 express 中,我们可以使用内置中间件express.urlencoded()
、express.json()
等来处理不同类型的请求数据,那在 koa 中我们可以使用什么库来代替呢?
- @koa/bodyparser (opens new window):处理 json/form/text/xml 格式的请求数据并将其放在 request 中
- @koa/multer (opens new window):于处理 multipart/form-data 类型的表单数据(具体使用同 express 官方第三方库 multer )
提示
Apifox 在模拟 multipart/form-data
(new FormData()方式) 和 x-www-form-urlencoded
(原始 form 表单方式)请求时,注意要手动添加 Content-Type,不然 Apifox 不会识别到 😭
使用 postman 则不会出现这样的问题,观察默认 Headers 数量 就知道了
代码示例
const Koa = require('koa')
const Router = require('@koa/router')
const multer = require('@koa/multer')
const { bodyParser } = require('@koa/bodyparser')
const app = new Koa()
// 处理multipart/form-data数据
const formParser = new multer()
// 处理 json/form/text/xml数据
app.use(bodyParser())
// --------------
const router = new Router({
prefix: '/posts'
})
router
.get('/', (ctx, next) => {
const pathname = ctx.path
const query = ctx.query
console.log(pathname, query) // /posts { id: '1' }
ctx.res.end()
})
.get('/:id', (ctx, next) => {
const pathname = ctx.path
const params = ctx.params
console.log(pathname, params) // /posts/1 { id: '1' }
ctx.res.end()
})
.post('/', (ctx, next) => {
const pathname = ctx.path
const body = ctx.request.body // json数据(不能简写为ctx.body) 👀
console.log(pathname, body) // /posts { title: 'foo', body: 'bar', userId: 1 }
ctx.res.end()
})
// ------
.post('/urlencoded', (ctx, next) => {
console.log(ctx.request.body) // urlencoded数据
})
.post('/formdata', formParser.any(), (ctx, next) => {
console.log(ctx.request.body) // form-data数据
})
app.use(router.routes()).use(router.allowedMethods())
// --------------
app.listen(6061)
# 3、文件上传
在 express 中,我们可以使用官方第三方库 multer (opens new window) 来处理文件上传的需求,那在 koa 中我们可以使用什么库来代替呢?
- @koa/multer (opens new window):除了用于处理 multipart/form-data 类型的表单数据的库,还可以处理文件上传
提示
后缀名(png/jpg 等)跟图片内部本身的编码格式是没有任何关系的;只要是编辑器等工具支持的图片后缀名即可。
const Koa = require('koa')
const Router = require('@koa/router')
const multer = require('@koa/multer')
const app = new Koa()
// multer 处理文件
const upload = multer({
storage: multer.diskStorage({
destination: function (req, file, cb) {
cb(null, __dirname + '/tmp/uploads')
},
filename: function (req, file, cb) {
cb(null, Date.now() + '_' + file.originalname) // 自定义文件名
// cb(null, file.fieldname + '_' + Date.now() + '.' + file.mimetype.split('/')[1])
}
})
})
// --------------
const router = new Router()
router.post('/upload', upload.single('avatar'), (ctx, next) => {
// ctx.request.file // ctx.request.body
})
app.use(router.routes()).use(router.allowedMethods())
// --------------
app.listen(6061)
# 4、响应处理 ✨
const Koa = require('koa')
const Router = require('@koa/router')
const { createReadStream } = require('node:fs')
const app = new Koa()
// --------------
const router = new Router({
prefix: '/posts'
})
router.post('/', (ctx, next) => {
ctx.status = 403
ctx.set('Content-Type', 'multipart/form-data;charset=utf-8')
// ……
// String
ctx.body = 'lencamo'
// Buffer
ctx.body = Buffer.from('你好呀! node.js')
// Stream
const readStream = createReadStream('./icon.png')
ctx.type = 'image/jpeg'
ctx.body = readStream
// Object
ctx.body = {
code: 1,
message: '获取文章列表成功',
data: []
}
})
app.use(router.routes()).use(router.allowedMethods())
// --------------
app.listen(6061)
# 三、其他
# 1、静态资源
在 express 中,我们可以使用内置中间件express.static
可以指定哪些文件夹为静态文件夹,那在 koa 中我们可以使用什么库来代替呢?
- koa-static (opens new window):指定哪些文件夹为静态文件夹
const static = require('koa-static')
// 给静态资源访问设置一个访问前缀
app.use('/imitation-tencent', static(path.join(__dirname, 'build'))) // http://localhost:6061/imitation-tencent/build/bg.png
# 2、模板引擎 ejs
在 express 中,我们可以使用其提供的 app.engine (opens new window) 方法指定并注册模板引擎,那在 koa 中我们可以使用什么库来代替呢?
- koa-views (opens new window):指定并注册模板引擎
var views = require('koa-views')
// 必须放在所有路由前面
app.use(
views(__dirname + '/views', {
extension: 'html',
map: {
html: 'ejs'
}
})
)
koa 中 app.context 和 ctx.state 的区别
相同点:
都是用于 存储和共享数据
不同点:
app.context 的全局共享的,而 ctx.state 可以针对特定的请求为其下游共享数据
const Koa = require('koa')
const { createReadStream } = require('node:fs')
const Router = require('koa-router')
const views = require('koa-views')
const app = new Koa()
// 运行 ejs 引擎
app.use(views(__dirname + '/views', { extension: 'html', map: { html: 'ejs' } }))
app.use(async (ctx, next) => {
// 存储全局状态
ctx.state.appName = '任先生的笔记'
ctx.state.currentUser = { copyRight: 'lencamo', startTime: '2022' }
await next()
})
// --------------
const router = new Router()
router.get('/about', async (ctx) => {
// 静态页面
const readStream = createReadStream(__dirname + '/views/about.html')
ctx.type = 'html'
ctx.body = readStream
// 动态页面
await ctx.render('about', { title: '自我介绍' }) // 使用/views/about.html文件(前面已经配置好了)
// 重定向
ctx.redirect('/hello/anime')
ctx.redirect('http://www.example.com')
})
app.use(router.routes()).use(router.allowedMethods())
// --------------
// 启动应用
app.listen(6061)
<!DOCTYPE html>
<head>
<title><%= appName %></title>
</head>
<body>
<h2><%= title %></h2>
<footer><%= currentUser.copyRight %>-<%= currentUser.startTime %></footer>
</body>
# 3、cookie 相关
在 express 中,我们可以使用官方第三方库 cookie-parser (opens new window)来处理 HTTP 请求中的 Cookie ,那在 koa 中我们可以使用什么库来代替呢?
- koa 内置对象 ctx.cookies
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
// --------------
const router = new Router()
// 前端:
// 🚩document.cookie="username=lencamo"
// 后端:
router.get('/login', async (ctx, next) => {
// 读取Cookie
console.log(ctx.cookies.get('password'))
// 设置Cookie
ctx.cookies.set('username', 'lencamo')
ctx.res.send()
})
app.use(router.routes()).use(router.allowedMethods())
// --------------
// 启动应用
app.listen(6061)