「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。
- 一句话简介
- 基于node.js的下一代框架
- 基于Node模块
- 框架
- 下一代,比第一代express
- 官网:koa.bootcss.com/
- web应用和api开发领域
- 更小,更富有表现力,更健壮
一.初始化项目
1.安装koa
- 初始化项目
npm init
- 安装koa
npm i koa -S // 记录到package中
- 新建index.js文件,编写代码
const Koa = require('koa')
const app = new Koa()
- 返回hello
// use使用中间件
// 参数是一个函数
// 函数传入一个参数,上下文ctx
app.use((ctx)=>{
ctx.body = 'hello world'
})
// 监听端口
app.listen(3000)
- node index.js进行运行
- 修改后自动重启
npm i nodemon -D
// package.json
script:{
"start:mon":"nodemon index.js"
}
二.koa的中间件
1.koa多个中间件的执行顺序
- 中间件洋葱模型
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx,next)=>{
await next()
console.log(1)
ctx.body = 'hello world'
})
app.use(async()=>{
console.log(2)
})
// 监听端口
app.listen(3000)
// 执行顺序
// 2
// 1
三.路由
- 决定了不同的URL是如何被不同的执行的
- 在Koa中,路由的本质就是一个中间件
- 路由的作用:
-
- 区分不同请求,所做的事情不同
- 处理不同的URL
- 处理不同的请求方法
- 解析URL上的参数
1.编写koa路由中间件
-
作用:
- 处理不同的URL
const Koa = require('koa')
const app = new Koa()
app.use((ctx)=>{
if(ctx.url === '/'){
ctx.body = '这是主页'
}else if(ctx.url === '/users'){
ctx.body = '这是用户列表页
}else {
ctx.status = 404
}
})
// 监听端口
app.listen(3000)
- 处理不同的请求方法
const Koa = require('koa')
const app = new Koa()
app.use((ctx)=>{
if(ctx.method === 'GET'){
ctx.body = '这是查询列表'
}else if(ctx.method === 'POST'){
ctx.body = '这是新增操作
}else {
ctx.status = 405 // 不允许这个方法
}
})
// 监听端口
app.listen(3000)
- 解析URL上的参数
// 用正则表达式来取出参数
const Koa = require('koa')
const app = new Koa()
app.use((ctx)=>{
if(ctx.url.match(//user/\w+/){
// 匹配'/user/:id的情况
const userId = ctx.url.match(//user/(\w+)/)[1]
ctx.body = `这是用户${userId}`
}else {
ctx.status = 404
}
})
// 监听端口
app.listen(3000)
2.使用koa-router
- 路由前缀
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
const router = new Router()
// 路由前缀
const userRouter = new Router({ prefix:'/users'})
router.get('/', (ctx) => {
ctx.body = "这是主页"
})
// 等价于下面
userRouter.get('/', (ctx) => {
ctx.body = "这是用户列表页"
})
// router.get('/users', (ctx) => {
// ctx.body = "这是用户列表"
// })
router.post('/users', (ctx) => {
ctx.body = "创建用户"
})
// 获取参数
router.post('/users/:id', (ctx) => {
// 获取参数
ctx.body = `这是用户${ctx.params.id}`
})
app.use(router.routes())
app.use(userRouter.routes())
app.listen(3000)
- 多中间件,比如用户鉴权
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
const router = new Router()
// 路由前缀
const userRouter = new Router({ prefix:'/users'})
// 鉴权
const auth = async(ctx,next)=>{
if(ctx.url !== '/users'){
ctx.throw(401)
}
await next()
}
router.get('/', (ctx) => {
ctx.body = "这是主页"
})
// 注意用户鉴权添加的地方
// 可以传递多个,无限传递
userRouter.get('/',auth, (ctx) => {
ctx.body = "这是用户列表页"
})
userRouter.post('/',auth, (ctx) => {
ctx.body = "创建用户"
})
// 获取参数
userRouter.post('/:id',auth, (ctx) => {
// 获取参数
ctx.body = `这是用户${ctx.params.id}`
})
app.use(router.routes())
app.use(userRouter.routes())
app.listen(3000)
3.HTTP中的options方法
- 检测服务器所支持的方法
- 可以在CORS里面进行预检请求
- allowedMethods方法的作用
// 加上他,所有的接口都能支持options请求
app.use(userRouter.allowedMethods())
- 可以相应的返回405(不允许)和501(没实现)两个状态码
4. 增删改查的响应
- 新增用户应该返回新增的数据
- 修改应该返回最后修改的哪一项
- 删除返回204,成功但是没有信息
- 查询返回一个列表数据
四.控制器
- 路由是根据不同的URL,分配不同的任务
- 控制器就是拿到路由分配的任务,并执行
- 所以控制器也是一个中间件
- 断点调试
-
- F5进行运行
- postman或者rest发起请求
-
- 打上断点
- 查看断点处的信息
- 断点一般设置在有断言的地方 设置的是一个变量的赋值 是有可能断不到的 可以尝试 在 if 判断、foreach、类、函数中去断点调试
- 控制器主要作用有以下三部分
1.获取请求参数
- Query String,比如?query=keyword,通常是可选的
-
- 通过ctx.query(Object)获得,断点调试可以看到
- Router param,比如/users/:id,通常是必选的
-
- ctx.params(Object)上获取
- Body,比如{ key: "value" }
-
- 需要额外的安装中间件koa-bodyparser
const app = new Koa()
const Router = require('koa-router')
// 引入
const bodyParser = require('koa-bodyparser')
const router = new Router()
// 注册到app上
app.use(bodyParser())
-
- 然后就可以在ctx.request.body(Object)上看到请求的body了
- Header,如Accept,Cookie等
-
- ctx.header(Object)获取
2.发送HTTP响应
- 发送状态Status,如200/400等
-
- ctx.status = 204
- 发送Body,如{ key: value }
-
- ctx.body = { key: value } 或者是字符串,甚至是html的字符串
- 发送Header,如Allow,Content-Type
-
- ctx.set('Allow','GET,POST')// 通过set设置
3.处理业务逻辑
- 编写控制器的最佳实践
-
- 每个控制器放在不同的文件里
- 尽量使用类+类方法编写控制器
-
- 要有严谨的错误处理
五.合理的目录结构
- 将路由单独放到一个目录
- 将控制器单独放到一个目录
- 使用类+类的方法组织控制器
六.异常/错误处理
- 编程语言或者计算机硬件中的一种机制
- 处理软件或者信息系统中的异常情况
1.常见的异常状况
- 运行时错误,指的是语法没有错,但运行的时候报错。比如求undefined中的属性。返回500,代表服务器内部错误。
- 常见的逻辑错误
-
- 找不到,找不到某个网页,找不到某个接口,404。
- 先决条件失败,412
-
- 无法处理的实体,422
2.为什么要用错误处理
- 防止程序挂掉
- 告诉用户错误信息
- 便于开发者调试
3.koa自带的错误处理
- 404:404不用做任何处理,404是客户端的问题,只要请求一个不存在的接口,就会报错404(如果是不被允许的方法,也会返回相应的错误)
- 412:需要手动抛出
findById = (ctx) => {
if(ctx.param.id * 1 >= this.db.length){
ctx.throw(412,'返回的文本')
}
ctx.body = this.db[ctx.params.id * 1];
};
- 500:不用做处理
findById = (ctx) => {
if(ctx.param.id * 1 >= this.db.length){
ctx.throw(412,'返回的文本')
}
// 设置一个不存在的变量,出现500错误,不用任何抛出
a.b
ctx.body = this.db[ctx.params.id * 1];
};
4.编写错误处理中间件
- 要放到所有中间件的最前面
- 把next函数放到trycatch中即可
- 通过断点调试,查看err中的属性,ctx.throw抛出的错误
app.use(async(ctx,next)=>{
try {
await next()
} catch(err) {
ctx.status = err.status || err.statusCode || 500
ctx.body = {
message:err.message
}
})
5.koa-json-error处理错误
- 安装koa-json-error
- 使用其配置处理错误
const error = require('koa-json-error')
app.use(error(
{
postFormat:(e,{stack, ...rest})=>process.env.NODE_ENV === 'production' ? rest : { stack, ...rest }
}
))
- 生产环境禁用错误堆栈返回
"scripts": {
"mon": "nodemon app/index.js",
"pro":"cross-env NODE_ENV=production node app/index.js"
},
6.koa-parameter校验参数
const Koa = require("koa");
const app = new Koa();
const routing = require("./routes");
const bodyParser = require("koa-bodyparser");
const error = require('koa-json-error')
const parameter = require('koa-parameter')
const port = 3000;
app.use(error(
{
postFormat:(e,{stack, ...rest})=>process.env.NODE_ENV === 'production' ? rest : { stack, ...rest }
}
))
app.use(bodyParser());
app.use(parameter(app))
routing(app);
app.listen(port,()=>{
console.log(`程序启动在${port}端口`)
});
class UserCtl {
db = [];
find = (ctx) => {
ctx.body = this.db;
};
findById = (ctx) => {
ctx.body = this.db[ctx.params.id * 1];
};
create = (ctx) => {
ctx.verifyParams({
name: { type: "string", require: true },
age: { type: "number", require: false },
});
this.db.push(ctx.request.body);
ctx.body = ctx.request.body;
};
update = (ctx) => {
this.db[ctx.params.id * 1] = ctx.request.body;
ctx.body = ctx.request.body;
};
del = (ctx) => {
this.db.splice(ctx.params.id * 1, 1);
ctx.status = 204;
};
}
module.exports = new UserCtl();
遇到的问题
1.TS中使用const a = require('a')报错
- 因为在 Commonjs 规范里,没有像 ESModule 能形成闭包的「模块」概念,所有的模块在引用时都默认被抛至全局,因此当再次声明某个模块时,TypeScript 会认为重复声明了两次相同的变量进而抛错。
- 使用import解决
- 文件添加export {},欺骗编译器是一个模块解决,但babel会无法编译export{}
- 参考文档