Koa
koa 是一个轻量、基于中间件的 Node.js Web 框架,核心特点是洋葱模型(async (ctx, next))与极简内核(路由、body 解析等通常依赖社区中间件)。
Installation
bash
npm i koabash
yarn add koabash
pnpm add koabash
bun add koa常用中间件(按需安装):
@koa/router:路由koa-bodyparser:解析 JSON / 表单koa-static:静态资源@koa/cors:CORSkoa-session:Sessionkoa-logger:请求日志
Hello World
js
import Koa from 'koa'
const app = new Koa()
app.use(async (ctx) => {
ctx.status = 200
ctx.body = { ok: true, message: 'hello koa' }
})
app.listen(3000)Middleware(洋葱模型)
中间件签名:async (ctx, next) => { await next() }
js
app.use(async (ctx, next) => {
const start = Date.now()
await next()
const ms = Date.now() - start
ctx.set('X-Response-Time', String(ms))
})
app.use(async (ctx, next) => {
if (ctx.path === '/health') {
ctx.body = 'ok'
return
}
await next()
})ctx 速查
Node原生:
req + res
Koa:
ctx = request + response 的封装Request 相关
ctx.method:获取请求的方法:GET,POSTctx.url:完整请求路径,/user?id=1&name=zs,输出/ user?id=1&name=zsctx.path:只有路径不含query参数,/user?id=1&name=zs,输出/userctx.query:query参数对象,/user?id=1&name=zs,ctx.query输出{id: '1', name: 'zs'}ctx.querystring:query 参数字符串,输出id=1&name=zs,返回的是字符串ctx.headers:请求头对象,输出{host: 'localhost:3000',connection: 'keep-alive','user-agent': 'Chrome'}ctx.get(name):专门获取某个请求头,ctx.get('user-agent'),输出Mozilla/5.0 ...,虽然等价于ctx.headers['user-agent'],但Koa推荐ctx.get()ctx.request:更底层的 request 对象(比如ctx.request.body)
Response 相关
ctx.status:状态码ctx.body:获取POST请求体ctx.type:设置响应类型,ctx.type = 'application/json'ctx.set(name, value):设置响应头,ctx.set('token', '123456')ctx.redirect(url):重定向,ctx.redirect('/login'),可以用于未登录跳到登录页
状态共享
ctx.state:建议放“当前请求上下文状态”,例如用户信息、traceId
Router(@koa/router)
路由
bash
npm i @koa/routerbash
yarn add @koa/routerbash
pnpm add @koa/routerbash
bun add @koa/routerjs
import Koa from 'koa'
import Router from '@koa/router'
const app = new Koa()
// 给每个路由加个前缀
const router = new Router({ prefix: '/api' })
router.get('/users', (ctx) => {
const page = Number(ctx.query.page ?? 1)
ctx.body = { page, list: [] }
})
router.get('/users/:id', (ctx) => {
ctx.body = { id: ctx.params.id }
})
router.post('/users', (ctx) => {
ctx.body = { ok: true }
})
app.use(router.routes())
app.use(router.allowedMethods())
app.listen(3000)Body Parser(koa-bodyparser)
Koa默认不解析body,直接访问ctx.request.body是undefined
bash
npm i koa-bodyparserbash
yarn add koa-bodyparserbash
pnpm add koa-bodyparserbash
bun add koa-bodyparserjs
import bodyParser from 'koa-bodyparser'
app.use(bodyParser())
app.use(async (ctx) => {
if (ctx.method === 'POST' && ctx.path === '/api/echo') {
ctx.body = { received: ctx.request.body }
return
}
})错误处理
try/catch 捕获链路异常
js
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
const status = err?.status ?? 500
ctx.status = status
ctx.body = {
message: status === 500 ? 'Internal Server Error' : err.message
}
ctx.app.emit('error', err, ctx)
}
})
app.on('error', () => {})ctx.throw
js
router.get('/forbidden', (ctx) => {
ctx.throw(403, 'Forbidden')
})静态资源(koa-static)
bash
npm i koa-staticbash
yarn add koa-staticbash
pnpm add koa-staticbash
bun add koa-staticjs
import serve from 'koa-static'
app.use(serve('public'))CORS(@koa/cors)
处理跨域
bash
npm i @koa/corsbash
yarn add @koa/corsbash
pnpm add @koa/corsbash
bun add @koa/corsjs
import cors from '@koa/cors'
app.use(
cors({
origin: 'http://localhost:5173',
credentials: true
})
)Cookies / Session(koa-session)
bash
npm i koa-sessionbash
yarn add koa-sessionbash
pnpm add koa-sessionbash
bun add koa-sessionjs
import session from 'koa-session'
app.keys = ['replace-with-a-secure-secret']
app.use(
session(
{
key: 'sid',
maxAge: 7 * 24 * 60 * 60 * 1000,
httpOnly: true,
sameSite: 'lax'
},
app
)
)
router.get('/login', (ctx) => {
ctx.session.userId = '1'
ctx.body = { ok: true }
})
router.get('/me', (ctx) => {
ctx.body = { userId: ctx.session.userId ?? null }
})JWT 鉴权(一个常见写法)
bash
npm i jsonwebtokenbash
yarn add jsonwebtokenbash
pnpm add jsonwebtokenbash
bun add jsonwebtokenjs
import jwt from 'jsonwebtoken'
const JWT_SECRET = 'replace-with-a-secure-secret'
const auth = async (ctx, next) => {
const header = ctx.get('authorization')
const token = header.startsWith('Bearer ') ? header.slice(7) : ''
if (!token) ctx.throw(401, 'Unauthorized')
try {
const payload = jwt.verify(token, JWT_SECRET)
ctx.state.user = payload
} catch {
ctx.throw(401, 'Unauthorized')
}
await next()
}
router.get('/private', auth, (ctx) => {
ctx.body = { user: ctx.state.user }
})上传文件(一个常见选择:koa-body)
bash
npm i koa-bodybash
yarn add koa-bodybash
pnpm add koa-bodybash
bun add koa-bodyjs
import { koaBody } from 'koa-body'
app.use(
koaBody({
multipart: true,
formidable: {
keepExtensions: true
}
})
)
router.post('/upload', (ctx) => {
ctx.body = { files: ctx.request.files ?? null }
})日志(koa-logger)
bash
npm i koa-loggerbash
yarn add koa-loggerbash
pnpm add koa-loggerbash
bun add koa-loggerjs
import logger from 'koa-logger'
app.use(logger())常用组织方式(备忘)
- 按层拆分:
routes/(路由定义)→controllers/(参数/响应)→services/(业务)→repos/(数据访问) - 统一响应结构:成功
{ code, data },失败{ code, message }(配合全局错误中间件) - 统一注入:在最前面生成
traceId放到ctx.state,并写入响应头便于排查