Koa 是一种可以替代 Express 的Web框架。开发思路和 Express 差不多,最大的特点是可以避免异步嵌套,免除重复繁琐的回调函数嵌套。
1. 创建基本的Web服务器
# 在项目中下载koa包
npm i koa
//1、引入koa
const Koa=require('koa')
//2、实例化koa,创建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)
ctx 上下文,包含了 request 和 response 等信息。
Koa与Express的对比:ctx.body用于返回数据相当于res.writeHead() res.send()
Koa用到ES6的语法:箭头函数 ()=>{} 中的this指向上下文。
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'
})
// 获取post提交的数据,解析请求体
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. 传值方式
(1)GET 传值
koa中GET传值通过 request 接收,接收的方法有两种:query 和 querystring。
- query:返回的是格式化好的参数对象。
- querystring:返回的是请求字符串。
http://localhost:3000/newscontent?aid=12345&name=xiaoming
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
router.get('/', function (ctx, next) {
ctx.body="Hello koa";
})
router.get('/newscontent,(ctx,next)=>{
let url =ctx.url; // 获取url地址
//从 request 中获取 GET 请求
let request =ctx.request;
let req_query = request.query; // 获取一个对象
let req_querystring = request.querystring; // 获取一个字符串
//从上下文中直接获取
let ctx_query = ctx.query; // 【最常用!!!!】获取一个对象
let ctx_querystring = ctx.querystring; // 获取的是一个字符串
ctx.body={
url,
req_query,
req_querystring,
ctx_query,
ctx_querystring
}
});
//作用:启动路由
app.use(router.routes());
//作用:当请求出错时的处理逻辑
app.use(router.allowedMethods());
app.listen(3000,()=>{
console.log('starting at port 3000');
});
(2)动态路由
动态路由里面可以传入多个值。
http://localhost:3000/product/12345/678
//请求方式
router.get('/product/:aid/:cid',async (ctx)=>{
//获取动态路由的数据
console.log(ctx.params); //{ aid: '12345' , cid:'678' }
ctx.body='这是商品页面';
});
(3)获取POST提交的数据
使用 koa-bodyparser 中间件获取表单 POST 提交的数据,并将数据转化为对象。
ctx.request.body 即为 POST 提交过来的数据。
var Koa = require('koa');
var app = new Koa();
var bodyParser = require('koa-bodyparser');
app.use(bodyParser());
app.use(async(ctx) => {
ctx.body = ctx.request.body;
});
app.listen(3000,()=>{
console.log('starting at port 3000');
});
4. 中间件
中间件就是匹配路由之前或者匹配路由完成做的一系列的操作。
Koa中间件执行流程和Express不同,采用了洋葱模型:初识洋葱模型,分析中间件执行过程,浅析koa中间件源码_Jioho_的博客-CSDN博客_中间件洋葱模型了解洋葱模型执行顺序分析部分 koa 中间件的源码来加深对中间件的认识https://blog.csdn.net/Jioho_chen/article/details/123167603
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
const bodyparser = require('koa-bodyparser') // 解析请求体
// 4. 第三方中间件
app.use(bodyparser())
// 1. 应用级中间件,可以匹配任何路由
app.use(async(ctx,next)=>{
console.log(new Date()); // 匹配路由之前打印日期
await next(); // 当前路由匹配完成以后继续向下匹配
// 如果不写next这个路由匹配完成后就不会继续向下匹配
})
// 2. 路由级中间件,匹配到news路由后,继续向下匹配路由
router.get('/news', async(ctx, next)=>{
console.log(1);
await next();
})
// 3. 错误处理中间件,利用了洋葱模型的特点
app.use(async (ctx,next)=> {
next(); //先继续执行下面的路由匹配
if(ctx.status==404){ // 执行完下面的路由匹配之后回过头来又判断是否出现了错误
ctx.status = 404;
ctx.body="这是一个 404 页面"
}
});
router.get('/', function (ctx, next) { ctx.body="Hello koa";})
router.get('/news',(ctx,next)=>{ctx.body="新闻页面" });
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000,()=>{
console.log('starting at port 3000');
});
5. 路由模块化
路由模块化:将路由抽离为单独的模块,与控制器分离【推荐使用!】
入口文件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()
6. 异常处理
Koa自带异常处理,返回值为文本(text)。
1. HTTP状态码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
7. 快速创建Koa2工程
(1)全局安装 koa-generator
sudo npm i -g koa-generator
(2)创建koa项目:ejs版本
koa2 -e koa-proj
(3)打开koa项目文件夹后,安装如下包
cd koa-proj
# 初始化项目
npm i
# 设置环境变量的包
npm i cross-env -D
# 使用mongodb数据库
npm i mongoose
# 使用redis
npm i redis
npm i koa-redis
# 使用session
npm i koa-generic-session
# 使用 koa-parameter 校验参数是否正确
npm i koa-parameter
# 测试
npm install --save-dev supertest
npm install --save-dev [email protected]
jest参数:runInBand为顺序执行,forceExit执行完后退出,colors有颜色的输出
(4)运行项目
npm run dev
(5)访问项目
localhost:3000
(6)git
# 还是在koa-proj文件夹中
git init
git remote add origin 远程git仓库地址
git status
git log
git pull origin master
git status
git add .
git commit -m "init project"
git push origin master
之后每次提交代码:
git status
# git diff
git add .
git commit -m "备注信息"
git push origin master
(7)inspect测试
配置:在package.json文件的dev中加上--inspect=9229
在谷歌浏览器地址栏中输入chrome://inspect
加断点:在需要加断点的代码处写上debugger
(8)jest单元测试
单元测试:单个功能或接口,给定输入,得到输出。看输出是否符合要求。需要手动编写用例代码,然后统一执行,验证所有功能是否正常。
注意:jest单元测试需要运行后缀为.test.js的文件。
编写测试用例demo.test.js:
/**
* demo.test.js
* @description test demo
*/
function sum(a, b) {
return a + b
}
// 参数一:描述,参数二:测试用例
test('10+20应该等于30', () => {
const res = sum(10, 20)
expect(res).toBe(30) //期望结果是30
//expect(res).not.toBe(40) //期望结果不等于40
})
jest连接服务器server.js:
/**
* server.js
* @description jest server
*/
const request = require('supertest')
const server = require('../app').callback()
module.exports = request(server)
测试接口,编写测试用例json.test.js:
/**
* json.test.js
* @description json test
*/
const server=require('./server')
test('json接口返回数据格式正确',async()=>{
const res=await server.get('/json')
expect(res.body).toEqual({
title:'koa2 json'
})
})
运行单元测试:
npm run test