Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API
开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa
并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序
注:文章有些长,阅读完大概需要半个小时哈😆~

const http = require('http');
const server = http.createServer((req, res) => {
  res.writeHead(200);
  res.end('hello world');
})
server.listen(3000, () => console.log('server start at 3000'));

相信上面的代码大家都是印象深刻 , 这是Node原生创建一个http服务器的示例 , 这里结合上面介绍, 我们大致猜想一下 koa
应该是什么样的

  • 它应该有 async/await 是异步操作;
  • 它应该有错误处理机制;
  • 它应该有比http模块更优雅的方法;

koa 的第一个示例 ; 当然你要先下载它( 注: node 版本需7.6.0 以上, 方便支持async/await 操作 )

npm install koa --save-dev

const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
  ctx.body = 'hello world'
});
app.listen(3000, () => console.log('server start at 3000'));

上面的代码是 koa 的示例代码 ; 这段代码中可以看出

  • async 异步了 虽然没有出现await
  • req res 变成了 ctx 也就是 context 上下文
  • 错误处理还没有看到, 这个后面说

总结: 底层仍然是http 通过各种封装用着更加顺手了, 当然啦这只是表象, 肯定还是要深入了解的 ;

那么再看 , app 既然是 Koa 的实例 , 那这个实例里面都有哪些内容呢 ?
这里呢实例的属性我就不说了简单看一下我认为比较重要的几个方法吧

  • app.use() : 将给定的中间件方法添加到应用程序中, 并且这个方法会返回 this 也就是说可以链式调用
  • app.callback() : 返回适用于 http.createServer() 方法的回调函数来处理请求,
    可以说是另一种方法创建服务器吧, 为什么这么用不要管, 后面你肯定会用到, 比如创建多个服务器, 暂时就先记住
  • app.listen() : Koa 应用程序不是 HTTP 服务器的1对1展现。 可以将一个或多个 Koa
    应用程序安装在一起以形成具有单个HTTP服务器的更大应用程序。
const Koa = require('koa');
const app = new Koa;
app.use(async ctx => {
    ctx.body = { msg: 'hello koa' };
});
app.listen(3000) // 其实就是下面的 createServer 语法糖
  
// *********************************
  
const http = require('http');
const Koa = require('koa');
const app = new Koa()
  
app.use(async ctx => {
  ctx.body = { msg: 'hello koa' };
});
  
http.createServer(app.callback()).listen(3000);

  • app.on(‘error’, function) : 通过监听 app 上的 error 事件对这些错误做进一步的统一处理和集中管理
app.on('error', async (err, ctx) => {
  console.log('err', err)
})
app.use(async ctx => {
  // 这时运行程序就会出现错误 err ReferenceError: tx is not defind  
  ctx.body = { msg: tx }; 
});

中间件 Middleware

// 伪代码
app.use(async ctx => {
  const user = 'admin'
  const pwd = '123456'
  const { username, password } = ctx.query
  if (user === username && pwd === password) {
    ctx.body = { status: 200, msg: 'success' }
  }
});

代码逻辑看着还行 , 相等就返回成功的 code ; 那现在又出了一个新的需求, 就是对比的时候给程序加上日志
这个时候其实我们就需要中间件的第二个参数 next 了 当一个中间件调用 next()
则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。 怎么理解呢 ?
那咱们优化一下上面的代码

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  ctx.state.user = 'admin'
  ctx.state.pwd = '111'
  console.log(`即将开始对比user: ${ctx.state.user} pwd: ${ctx.state.pwd}`)
  await next()
  console.log(`对比结束username: ${ctx.query.username}, password: ${ctx.query.password}`)
})

app.use(async ctx => {
  const { username, password } = ctx.query
  if (ctx.state.user === username && ctx.state.pwd === password) {
    ctx.body = { status: 200, msg: 'success' }
  }
});
app.listen(3000, () => console.log('server start at 3000'));

Koa上下文 Context

app.use(async ctx => {
  const username = ctx.query.name
  ctx.body = { code: 200, msg: 'success' }
})

看到上面的 ctx 心中肯定也有一个疑问 , 为什么 ctx 既可以有操作 request 的能力也有操作 response 的能力 ;
这个肯定是故意为之的, 也是为了我们开发方便 , 比如你也可以这样

app.use(async ctx => {
  const username = ctx.request.query.name
  ctx.response.body = { code: 200, msg: 'success' }
})

koa 帮我们把一些很常用的方法放到 ctx 上下文中, 具体有哪些呢 ? 还要去官网查看😂 个人觉得太多且没有必要列出来,
大家可以去官网慢慢看, 但是除去 request 和 response 的当然还有ctx 自己的一些属性和方法 ;
那就列举几个比较重要的几项:
ctx.request : Koa 的request 对象

  • ctx.response : Koa 的response对象
  • ctx.state : Koa推荐使用的命名空间, 主要用于中间件传递信息
  • ctx.throw : Koa 手动抛出异常的方法, 也可以使用 http-errors 来创建错误
ctx.throw(404, 'NotFound');
  • ctx.assert : 当断言的值不存在或者出现错误的时候, 就会抛出异常
// 如果 ctx.state.name 没有定义就会出现这个错误
ctx.assert(ctx.state.name, 401, 'user is not defined');

ctx.cookies.get ctx.cookies.set : cookie 的操作, 也可以根据官方的推荐使用 cookies 模块
; 这里我简单示例一下吧, 要深入 cookie 的知识点还是有点多😂

ctx.cookies.get(name, [options])
ctx.cookies.set(name, value, [options])

app.use(async ctx => {
  ctx.cookies.set('login_cookie', '123456', {
    maxAge: 36000 // 这里注意表示从 Date.now() 得到的毫秒数 毫秒级时间戳
  })
  const loginCookie = ctx.cookies.get('login_cookie')
  console.log(loginCookie)
  ctx.response.body = { code: 200, msg: 'success' }
})

Koa Request Response

在Context 上下文之下还有 request 和 response 对象 ; 这两个对象是对 node 原生的 req 和 res
的封装, 提供了诸多对 HTTP 服务器开发有用的功能。

go request

go response

Koa-router

到此阶段, 间接的证明 koa 使用层面的知识已经算是基本介绍完毕了, 但是从起步阶段已经有介绍这个框架的特点 Koa 并没有捆绑任何中间件
那么我们要实现路由可能就是下面这种情况

app.use(async ctx => {
  if (ctx.url === '/') {
    ctx.body = { msg: 'index page' };
  } else if (ctx.url === '/detail') {
    ctx.body = { msg: 'detail page' };
  }
});
// 这...... 代码真的不是很优雅哈, 为此社区也为我们提供了一个比较优秀的中间件库 koa-router
// 注:要先安装扩展哈~~
npm install koa-router --save-dev

// 先来个示例实现上面不优雅的代码

const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();

// 实例化路由
const router = new Router()

// 注册路由
router.get('/', async ctx => {
  ctx.body = { msg: 'index page' };
});
router.get('/detail', async ctx => {
  ctx.body = { msg: 'detail page' };
});

// 注册路由中间件
app.use(router.routes())
app.listen(3000);

通过以上代码的查看可读性明显提升不是一个档次, 下面先看一下它的基本使用方法吧~

  • 路由前缀 ; 在实例化路由的时候添加上prefix 属性就会给路由添加上前缀, 比如上诉代码中如果添加上前缀, 那么再访问 detail 的时候, 完整路径就是 http://localhost:3000/user/detail
const router = new Router({ prefix: '/user' })
  • 请求方式 ; koa-router 支持常用的请求方式, 推荐大家使用 get post put delete patch head
    options
// 示例
router.get(...)
  • 中间件注册 ; 上述示例中可以看出, 在app.use 之前 router 和 koa 还是没有关联的,
    如果想要他们关联起来就需要使用以下代码
   app.use(router.routes())
  • redirect 重定向 ;
    方法的第一个参数是请求来源,第二个参数是目的地,两者都可以用路径模式的别名代替,还有第三个参数是状态码,默认是 302
    这个方法放到了context 上下文中
router.get('/home', async ctx => {
  ctx.redirect('http://baidu.com');
});
  • 路由参数 params 和 querystring 熟悉路由的同学一定知道, 一般参数有两种情况
http://localhost/users/1  // params
http://localhost/users/id=1 // querystring
  • params : 需要注意的是她是一个强路由, 会更改原有的资源地址; 看下面的🌰
// 在:id代表不确定参数
// 访问时 localhost:3000/user/1
// id可以是任意值, 需要注意url中写什么样的 params 就需要怎样获取,必须保持一致

router.get('/:id', async ctx => {
  ctx.body = {
    msg: 'index page',
    params: ctx.params.id // 通过 ctx.params.id 获取到的是 1
  };
});
  • querystring : 此种方式会在获取路由后面携带的参数 user=admin&pwd=123
router.get('/', async ctx => {
  ctx.body = {
    msg: 'index page',
    query: ctx.query // ctx.query会获取url后面携带的参数对象
  };
});
  • allowedMethod : 对于这个方法来说 , 它的作用就是用于处理请求的错误 , 假设我们现在实现了一个 /user 的接口, 为
    get 请求方式
router.get('/user', async ctx => {});
app.use(router.routes());

// 注:如果没有这一行, 当使用其他请求方式请求user时, 会提示404
// 注:如果有这一行, 当使用其他请求方式请求user时, 提示405 method not allowed
app.use(router.allowedMethod());

KoaStatic 静态web托管服务

koa中最常用的静态文件托管中间件 , 比如我们要存图片, 但是个人项目又不想弄云服务, 就可以使用此功能实现我们的需求,
又比如也可以把前端项目托管到我们这里 ;

// 注:先安装扩展!!!
npm install koa-static --save-dev

const staitc = require('koa-static');
// 注:./static 相对于你当前目录填写, 使其最终拼接成绝对路径
app.use(static(path.join(__dirname, './static')));

// 此时如果我们想访问这些资源 ; 在启动服务之后就可以通过以下方式访问;
// http://localhost:8080/xxx.img 或者一些 css js 文件等等

KoaViews 模板渲染引擎中间件

其实这个中间件用的不是很多( 现在都是一些前后端分离的项目 ), 主要用于一些模板渲染, 比如 ejs pug 等 ;
当然也可以不指定模板 ;

// 注:先安装扩展
npm intsall koa-views --save-dev

const views = require('koa-views');
// views 相对于当前目录而创建的views目录, 最近内部的文件会作为模板
app.use(views(path.resolve(__dirname, 'views')));

// 挂载此中间件之后, ctx上下文中会挂载一个render函数
app.get('/', async ctx => {
  // index 是 views 文件夹内的 index.html 文件
  await ctx.render('index')
});

上述代码可能解释的不是很清楚或者还有一些额外需要注意地方

  • render 函数是异步函数 一定要加上 await
  • 如果是 .html 的模板可以不用写后缀名 , 当然啦还支持一些其他的模板, 需要注册中间件的时候提供第二个参数 options
app.use(views(path.resolve(__dirname, 'views')), {
  extension: 'ejs' // 可以是 pug 或者 nunjucks 记得要使用npm安装对应的依赖
});

// 注:如果我们使用的其他的模板那么对应的render 函数中也要做更改

await ctx.render('index.ejs');
  • render 函数也会接受第二个参数, 接收一个对象 ; 然后对应的模板内就可以获取到这个数据 ;

关于模板语法, 这里就不做讲解了, 大家可以去对应官网查看 pug ejs

  • 模板内引用静态资源, 注意 你已经下载了 koa-static 并且在主文件内注册了这个中间件, 那么static
    目录就是你网站的根目录了, 再模板内就不要从 static 开始引入了 ; 假设你的目录是这样的
+---static
|       index.css
|       test.png
|       
\---views
|       index.ejs
|   index.js

  • 错误的示例
<link rel="stylesheet" href="/static/index.css">
  • 正确的示例
<link rel="stylesheet" href="index.css">

KoaBody 请求body数据处理

之前我们如果需要解析json数据的时候, 需要的中间 koa-bodyparser 如果需要上传文件则可以使用 koa-multer
现在呢则可以使用 koa-body 去解决这两个问题 ; 当然不单单是替换的个中间件这么简单的, 貌似听说过 koa-multer 和
koa-route(注意不是 koa-router) 存在不兼容的问题 , 具体我也没有考证, 大家有兴趣可以看看

  • 如果我们不使用这个中间件去做一些事情, 比如客户端随便提交点什么数据, 那么我们为了接受这些数据, 服务端的代码可能就是这样的
router.post('/', async ctx => {
  let bstr = '';
  // 通过 koa 暴露出来的原生方法读取并且合并
  ctx.req.on('data', (data) => {
    bstr += data
  })
  ctx.req.on('end', () => {
    console.log(bstr)
  })
  ctx.body = { code: 200 }
})

试想一下如果每次为了接收这些数据监听 data end 肯定是不合理的 ;

// 注:先安装扩展哈
npm install koa-body --save-dev

const bodyparser = require('koa-body');
// 在注册此中间件之后, 会在 ctx 上下文注入 ctx.request.body 属性 用于获取客户端传递来的数据
app.use(bodyparser());

router.post('/', async ctx => {
  ctx.body = {
    code: 200,
    // body 可以获取form表单数据, json数据
    msg: ctx.request.body
  }
})

如果我们还需要上传文件, 则需要在注册中间的时候增加一些配置

app.use(bodyparser({
  multipart: true, // 支持文件上传
  formidable: {
    // 文件默认保存的默认路径
    uploadDir: path.join(__dirname, 'static/uploads'), 
    // 保留文件拓展名
    keepExtensions: true
  }
}));

注:接收数据时可以这样操作

router.post('/', async ctx => {

  // files 是上传文件后的对象集合, file是对应传递的 key
  const fileInfo= ctx.request.files.file;
  
  // 返回临时路径中的最后一部分作为文件名称
  const filePath = path.basename(fileInfo.path);
  ctx.body = { 
      url: `${ctx.origin}/uploads/${filePath}`, 
      info: ctx.request.files
  }
})

@Koa/cors 处理跨域中间件

相信大家看到这篇文章的时候, 八成自己也是个前端, 跨域的问题我就无需做过多解释了~

// 注:先安装扩展哈~
npm install @koa/cors --save-dev

const cors = require('@koa/cors');
app.use(cors());

Koa-json-error 错误处理中间件

一个处理koa错误的中间件, 使用此中间件之后, koa抛出的异常, 都会被转换成 json 返回给客户端, 很大程度提高了程序的健壮性,
和稳定性

// 注:先安装扩展哈~
npm install koa-json-error --save-dev

const error = require('koa-json-error');
app.use(error({
  // 默认会把堆栈信息也给一起发给客户端, 这样很明显是不合理的所以做了一层简单的处理
  postFormat(e, { stack, ...rest }) {
    return process.env.NODE_ENV === 'production' ? rest : { stack, ...rest}
  }
}));

想要验证此功能, 可以使用 koa 原生提供的抛出错误方法

app.use(async ctx => {
  if (ctx.request.body.name !== 'admin') {
    // 可以参考下面图片, 信息都是根据状态码生成的, 也可以在第二个参数中自定义一些错误数据
    ctx.throw(401, {msg: '鉴权失败'});
  }
});

Koa-parameter 参数校验中间件

在日常的发开工作中, 程序的健壮性还是要比较良好的, 怎么理解呢 ? 假设你想让用户给你传递一个 字符串, 可以用户偏偏给你一个数字,
此时你的程序就出错了, 这样显然是不行的, 但是我们如果要处理, 首先想到的就是 如果 xx == xx 然后 xx
这样的代码会显得很不优雅, 如果我们需要优雅的解决这个问题, 则可以使用这个中间件 ;

// 注:先安装k扩展哈~
npm install koa-parameter --save-dev

const parameter = require('koa-parameter');
parameter(app);

在经过以上操作之后, ctx 上下文下面就多出了一个校验函数 verifyParams

router.post('/', async ctx => {
  // 接收一个对象
  ctx.verifyParams({
    // 校验的简写形式
    username: 'string',
    password: { type: 'string', required: true } // 或者也可以这样
  })
  ctx.body = { code: 1 }
})

当然也提供了一些, 特殊的校验方式

ctx.verifyParams({
    // 必须输入 url 格式
    address: { type: 'url', required: true },
    // 必须输入 email 格式
    email: { type: 'email', required: true }
})

如果是比较负责的也是可以校验的 , 当然也是不能覆盖全场景的, 不过这样已经能为我们省去不少精力~

// 某一项是对象, 并且里面某个字段是数组
ctx.verifyParams({
   obj: { type: 'object', rule: { children: 'array' } }
})
// 某一项是数组, 里面包含对象
ctx.verifyParams({
   // list 是数组, 里面的每一项是object object中必须包含info字段
   list: { type: 'array', itemType: 'object', rule: {info: 'string'} }
})

Mysql2 mysql数据库操作

为什么没有使用mysql 而是使用了mysql2 , 作者也是没有深入的了解过,
大概模糊的记得mysql2对promise的支持还是比较好的, 提供了专门的api用起来更加的方便 ;

// 注:先安装扩展哈~
npm install mysql2 --save-dev

// 使用层面相对来说也是比较简单的 ;

const mysql = require('mysql2');
// 创建连接
const connection = mysql.createConnection({
  host: 'xx.xx.xx.xx',
  user: 'root',
  password: '123456',
  port: 3306,
  database: 'test'
});

// 基本的增删改查
const [row] = await connection.promise().query('SELECT * FROM users WHERE username=? AND password=?', ['lisi', 'li']);
const [info] = await connection.promise().query('UPDATE users SET username=? WHERE password=?', ['hello', 'wang']);
const [info] = await connection.promise().query('DELETE FROM users WHERE username=?', ['hello']);
const [info] = await connection.promise().query('INSERT INTO users (id, username, password) VALUES (?, ?, ?)', ['id1', 'username1', 'password1']);

需要注意的是, 当查询的时候你可以使用 [row] 来将结果解构出来, 但是在更新, 修改和删除操作的时候, 并没有返回你删除的某些值,
而是返回一个操作对象, 里面包含了一些必要的信息 ;

JWT 鉴权

使用层面主要分两步走, 第一 生成 token, 一般情况下都是登录的时候就返回给用户了 ; 生成 token 使用 sign 方法

  • 生成 token
jwt.sign('要加密的内容 string | Buffer | object', '加密的key', {配置对象})
// 配置对象中 两个比较重要的参数
{
  algorithm: "HS256" | "HS384" | "HS512" |
    "RS256" | "RS384" | "RS512" |
    "ES256" | "ES384" | "ES512" |
    "PS256" | "PS384" | "PS512" |
    "none"; // 加密方式
  expiresIn: "2d" // 可以是数字, 如果是数组就是秒为单位, 或者支持 2d/10h 2天 10小时
}

const jwt = require('jsonwebtoken');
router.post('/', async ctx => {
  // 从请求体中结构用户名和密码
  const { username, password } = ctx.request.body;
  
  const token = jwt.sign(
    { username, password },
  	'auth-key',
    { 
      algorithm: 'HS256', // 可以不写
      expiresIn: '2d'
    } 
  );
  ctx.body = { token };
})

// jwt.verify 校验 token

jwt.verify('token', '加密的key', {配置对象})

router.post('/auth', async ctx => {
  const token = ctx.header.authorization
  try {
    // 这个token校验要额外处理 具体可以看下图
    jwt.verify(token.split(' ')[1], 'auth-key', { algorithms: 'HS256' });
  } catch (e) {
    ctx.throw(401);
  }
  ctx.body = { code: 1 };
})

  • koa-jwt 校验 token
const koaJwt = require('koa-jwt');
// 如果有配置跨域 一定要写到跨域相关配置的下方
// koaJwt 的第一个参数是一个对象, 与上面介绍到的配置对象内容基本一致, 
// path 可以接受 字符串, 数组, 正则表达式,用来过滤某些 url 可以不进行鉴权
app.use(koaJwt({ secret: 'auth-key' }).unless({ path: [/\user/] }))

Koa-generator

如果我们想做一个koa的服务端应用 , 每次都要自己从头开始创建文件 , 下载中间件等等 , 不免有些麻烦, generator
就是为了让我们从这繁琐的工作脱离出来, 不过这个工具有些老了, 大家可以参考, 不建议完全使用

// 注:先安装扩展哈~
npm install koa-generator -g

在这里插入图片描述

图中可出大致归为两类 ;

  • 为什么没有后缀 ? 第一行是什么意思(下一篇文章将详细描述~)
  • 看到第二个问题, 这个 port 可以在命令行中设置, 但是 window 和 liunx 中命令又是不太一样的 ; 代码都跨端了, 运行个脚本跨不了端就坑了 ; 这里再次推荐一个包 cross-env
// 注:先安装扩展哈~
npm install cross-env --dev

使用也是非常简单的, 比如之前的启动脚本是这样的 nodemon app.js

那么我们现在改成这样既可 cross-env PORT=3001 nodemon app.js 此时再次运行项目就会在3001端口了

当然也可以同时去设置环境变量比如:

cross-env NODE_ENV=production PORT=3001 nodemon app.js
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐