Koa(koa.js)中文网 -- 基于 Node.js 平台的下一代 web 开发框架koa (koajs)是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升错误处理的效率。https://www.koajs.com.cn/
1. 创建基本的Web服务器
# 在项目中下载koa包
npm i koa
//1、导入koa
const Koa=require('koa')
//2、创建Web服务器
const app=new Koa()
//第一个中间件
app.use(async(ctx,next)=>{
await next()
console.log('再打印第一个中间件')
ctx.body='Hello world'
})
//第二个中间件
app.use(async(ctx)=>{
console.log('先打印第二个中间件')
})
//3、启动Web服务器,调用app.listen(端口号,启动成功后的回调函数)
app.listen(3000)
2. 路由
路由是客户端的请求与服务器处理函数之间的映射关系。
(1)使用原生方法实现路由:不够简洁,优雅!
//1、导入koa
const Koa = require('koa')
//2、创建Web服务器
const app = new Koa()
//路由中间件
app.use(async (ctx) => {
if (ctx.url === '/') {
ctx.body = 'Home Page'
} else if (ctx.url === '/users') {
if (ctx.method === 'GET') {
ctx.body = 'Users Page'
} else if (ctx.method === 'POST') {
ctx.body = 'Create New User'
} else {
ctx.status = 405 //Not allowed
}
} else if (ctx.url.match(/\/users\/\w+/)) { //用户id由字母数字组成
if (ctx.method === 'GET') {
// macth()返回的数组第一项为整个url,在正则表达式中给我们需要的信息加上小括号,数组第二项就为我们需要的信息
const userId = ctx.url.match(/\/users\/(\w+)/)[1] //获取数组第二项的userId
ctx.body = `用户id为${userId}`
} else {
ctx.status = 405 //Not allowed
}
} else {
ctx.status = 404 //Not Found
}
})
//3、启动Web服务器,调用app.listen(端口号,启动成功后的回调函数)
app.listen(3000)
(2)使用koa-router实现路由:
# 在项目中下载koa-router包
npm i koa-router
# 解析请求体的包
npm i koa-bodyparser
const Koa = require('koa')
const app = new Koa()
// 1、引入koa-router
const Router = require('koa-router')
// 2、创建路由
const router = new Router() //配置无路由前缀的路由
const usersRouter = new Router({ //配置路由前缀为users的路由
prefix: '/users'
})
// 解析请求体
const bodyparser = require('koa-bodyparser') // 引入koa-bodyparser
app.use(bodyparser()) // 注册
// 安全中间件
const auth = async (ctx, next) => {
if (ctx.url !== '/users') {
ctx.throw(401) //Unauthorized
}
await next()
}
// 虚拟数据
const db = [{
name: '小明',
age: '13',
sex: '男'
}, {
name: '小红',
age: '15',
sex: '女'
}]
// 3.挂载路由
router.get('/', (ctx) => { // 首页
ctx.body = '<h1>Home Page</h1>'
})
usersRouter.get('/', auth, (ctx) => { // 查列表
ctx.set('Allow', 'GET,POST') // 设置响应头Headers
ctx.body = db
})
usersRouter.get('/:id', (ctx) => { // 查一个
if (ctx.params.id >= db.length) {
ctx.throw(412, '先决条件错误,id大于数组长度')
}
ctx.body = db[ctx.params.id] // 假设id为数据索引
})
usersRouter.post('/', (ctx) => { // 增
// 要新增的数据通过请求体传递过来
db.push(ctx.request.body)
ctx.body = ctx.request.body
})
usersRouter.put('/:id', (ctx) => { // 改
// 要修改为的数据通过请求体传递过来
db[ctx.params.id] = ctx.request.body
ctx.body = ctx.request.body
})
usersRouter.delete('/:id', (ctx) => { // 删
db.splice(ctx.params.id, 1) // 假设id为数据索引
ctx.status = 204 // No content
})
// 4.注册路由中间件
app.use(router.routes())
app.use(usersRouter.routes())
// allowedMethods作用:
//(1)响应options请求,返回支持的请求方法
//(2)返回405表示不允许访问,返回501表示方法还没实现
app.use(usersRouter.allowedMethods())
app.listen(3000)
(3)模块化路由:将路由抽离为单独的模块,与控制器分离【推荐使用!】
入口文件index.js:
// index.js
const Koa = require('koa')
const app = new Koa()
const bodyparser = require('koa-bodyparser')
app.use(bodyparser())
const router=require('./routes/home.js')
const usersRouter=require('./routes/users.js')
app.use(router.routes())
app.use(usersRouter.routes())
app.use(usersRouter.allowedMethods())
app.listen(3000,()=>{
console.log('koa server running at http://127.0.0.1:3000')
})
路由放在routes文件夹中:
// /routes/home.js
const Router = require('koa-router')
const router = new Router()
const { index } = require('../controllers/home.js')
router.get('/', index)
module.exports = router
// /routes/users.js
const Router = require('koa-router')
const usersRouter = new Router({ //配置路由前缀为users的路由
prefix: '/users'
})
const {getUserList,findUserById,createUser,updateUserById,deleteUserById}=require('../controllers/users.js')
usersRouter.get('/', getUserList)
usersRouter.get('/:id', findUserById)
usersRouter.post('/', createUser)
usersRouter.put('/:id', updateUserById)
usersRouter.delete('/:id', deleteUserById)
module.exports=usersRouter
控制器放在controllers文件夹中:
// /controllers/home.js
class HomeCtrl{
index(ctx){
ctx.body = '<h1>Home Page</h1>'
}
}
module.exports=new HomeCtrl()
// /controllers/users.js
// 虚拟数据
const db = [{
name: '小明',
age: '13',
sex: '男'
}, {
name: '小红',
age: '15',
sex: '女'
}]
class UsersCtrl{
getUserList(ctx){
ctx.set('Allow', 'GET,POST') // 设置响应头Headers
ctx.body = db
}
findUserById(ctx){
if (ctx.params.id >= db.length) {
ctx.throw(412, '先决条件错误,id大于数组长度')
}
ctx.body = db[ctx.params.id] // 假设id为数据索引
}
createUser(ctx){
db.push(ctx.request.body)
ctx.body = ctx.request.body
}
updateUserById(ctx){
db[ctx.params.id] = ctx.request.body
ctx.body = ctx.request.body
}
deleteUserById(ctx){
db.splice(ctx.params.id, 1) // 假设id为数据索引
ctx.status = 204 // No content
}
}
module.exports=new UsersCtrl()
3. 异常处理
1. 状态码Status:
(1)正常:返回200。
(2)运行时错误:返回500。
(3)逻辑错误:无法找到,返回404。先决条件错误,返回412。参数格式错误,返回422。
2. 使用 koa-json-error 进行异常处理:
使用 koa-json-error 进行异常处理,返回值为json格式。
# 在项目中下载koa-json-error包
npm i koa-json-error
3. 使用 koa-parameter 校验参数是否正确:
使用 koa-parameter 校验参数是否正确,返回值为json格式。
# 在项目中下载koa-parameter包
npm i koa-parameter
// 入口文件 index.js
const Koa = require('koa')
const app = new Koa()
const bodyparser = require('koa-bodyparser')
app.use(bodyparser())
// 1.自己编写的异常处理中间件
// app.use(async (ctx, next) => {
// try {
// await next()
// } catch (err) {
// ctx.status = err.status || err.statusCode || 500
// // 返回的错误信息最好是json格式
// ctx.body = {
// message: err.message
// }
// }
// })
// 2.使用koa-json-error中间件进行异常处理
const error = require('koa-json-error')
app.use(error({
// development环境下返回错误栈stack,production环境下不返回错误栈stack
postFormat: (e, {stack,...rest}) => process.env.NODE_ENV === 'production' ? rest : {stack,...rest}
}))
// 3.使用koa-parameter中间件校验参数格式是否正确
const parameter = require('koa-parameter')
app.use(parameter(app)) // app参数使koa-parameter可以全局使用
const router = require('./routes/home.js')
const usersRouter = require('./routes/users.js')
app.use(router.routes())
app.use(usersRouter.routes())
app.use(usersRouter.allowedMethods())
app.listen(3000, () => {
console.log('koa server running at http://127.0.0.1:3000')
})
koa-parameter可以全局使用:
// /controllers/users.js 中部分关键代码
// koa-parameter的全局使用方法
createUser(ctx) {
// 参数校验
ctx.verifyParams({
name: {
type: 'string',
required: true
},
age: {
type: 'number',
required: false
}
})
db.push(ctx.request.body)
ctx.body = ctx.request.body
}
注意:若要使用process.env.NODE_ENV判断当前程序运行环境是开发环境还是生产环境,可以在package.json文件中进行配置。
模拟node.js项目在生产环境中运行:
npm start
模拟node.js项目在开发环境中运行:
npm run dev