简介
Koa 是一个新的基于Node.js平台的下一代 web 开发框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。
安装和创建
npm init -y
npm i koa -s
// 引入
const Koa = require('koa')
// 实例化
const app = new Koa()
// 路由
app.use(async ctx => {
ctx.body = 'Hello Koa!'
})
// 监听
app.listen(3000,() => {
console.log('3000端口监听中');
})
nodemon server.js 运行服务器
访问 localhost:3000 ,路由会通过 ctx.body 向浏览器发送这个值
中间件
app.use( ) 就是一个中间件函数,中间件就是一个异步的函数,主要是拦截客户端请求和响应做预处理直到将服务器响应返回给客户端
- 注册表管理器
- 状态码重定向
- 错误处理程序
- 缓存中间件
- 会话中间件
- 路由中间件
- Pylons App
就像是洋葱,请求从外部穿过一层层中间件,到达服务器,服务器接收到的是被中间件处理过的请求,当服务器作出响应,又会通过这一层层中间件向外传递回客户端
错误处理中间件的了解
// 引入
const Koa = require('koa')
// 实例化app
const app = new Koa()
// 错误处理中间件
app.use(async (ctx, next) => {
try {
await next()
} catch (error) {
// 给客户端显示状态码,如果没有则赋值500
// 这里可以使用 switch判断不同的状态码进行处理,做不同的处理
ctx.status = error.status || 500
// 向客户端返回json格式错误信息
ctx.type = 'json'
ctx.body = {ok: 0, message: error.message}
// 手动出触发绑定的全局error方法,在服务器端打印这个错误
ctx.app.emit('error', error)
}
})
// 主动抛出401异常 相当于中间件,通过条件判断扔出错误,被错误处理中间件拦截并接收错误信息
app.use(async ctx => {
ctx.throw(401, '没有访问权限,需要进行身份认证')
})
// 路由
app.use(async ctx => {
ctx.body = 'Hello Koa!'
})
// koa绑定error方法接收错误信息并在服务端打印
app.on('error', err => {
console.log('Error: ', err.message)
})
// 监听
app.listen(3000, () => {
console.log('3000端口开始监听')
})
服务端打印
Error: 没有访问权限,需要进行身份认证
浏览器端收到返回的json信息,可以进行页面渲染展示
错误处理中间件一定要写在中间件最上面,直接向客户端返回错误信息
koa-error 错误处理中间件
koajs/onerror: an error handler for koa
安装
npm i koa-onerror -s
onerror(app)
server.js
// 引入
const Koa = require('koa')
const onerror = require('koa-onerror')
const fs = require('fs')
// 实例化
const app = new Koa()
// 将app传入onerror
onerror(app)
// 没有此文件,会报错
app.use(async ctx => {
ctx.body = fs.createReadStream('not exist')
})
// 路由
app.use(async ctx => {
ctx.body = 'Hello Koa!'
})
// 监听
app.listen(3000,() => {
console.log('3000端口监听中');
})
第一个中间件因为读取文件失败,会抛出错误,由koa-onerror处理这个错误,向服务器终端打印这个错误,并向客户端返回一个报错页面显示错误信息
Error: ENOENT: no such file or directory, open 'C:\Users\Maxuan\Desktop\Koa\not exist'
option参数
onerror(app, option) 还可以接收第二个参数,用来配置koa-error返回的错误信息模板
- all: 如果 options.all 存在,则忽略协商
- text: text错误处理程序
- json: json错误处理程序
- html: html错误处理程序
- redirect: 重定向,如果接受html,可以重定向到另一个错误页面
这里暂不做了解,用到的时候再看 → koajs/error: Error response middleware (text, json, html) (github.com)
koa-logger 日志打印中间件
koajs/logger: Development style logging middleware
安装
npm i koa-logger -s
server.js
// 引入
const Koa = require('koa')
const logger = require('koa-logger')
// 实例化
const app = new Koa()
// 将app传入onerror
app.use(logger())
// 路由
app.use(async ctx => {
ctx.body = 'Hello Koa!'
})
// 监听
app.listen(3000,() => {
console.log('3000端口监听中');
})
nodemon server.js 运行服务器,打开页面,终端会打印出请求信息日志GET请求、GET响应 状态码 延迟 数据量
如果有错误则打印错误
koa-router 路由中间件
koajs/router: Router middleware for koa
安装使用
npm i koa-router -s
server.js
// 引入
const Koa = require('koa')
const router = require('./router')
// 实例化
const app = new Koa()
// 注册路由
app.use(router.routes()) // 中间件导入路由对象
app.use(router.allowedMethods()) // 根据ctx.status设置response响应头
// 监听
app.listen(3000,() => {
console.log('3000端口监听中');
})
router/index.js
const Router = require('koa-router')
const router = new Router()
// 路由中间件 处理请求
router.get('/',async (ctx, next) => {
ctx.body = '主页'
})
router.get('/login', ctx => {
// 重定向操作
ctx.redirect('/sign')
})
router.get('/sign', ctx => {
ctx.body = '注册页面'
})
// 抛出路由
module.exports = router
访问/ 返回主页,/login 会跳转/sign,/sign 会显示注册页面
post请求路由处理
router/index.js
router.post('/', ctx => {
console.log(ctx.request.body); // 打印请求体
ctx.body = {'ok': 1}
})
打开postman,测试post接口,传入参数,可以看到打印结果为undefind
就像express的post请求要获取body请求体需要调用bodyparser获取
koa需要额外下载koa-bodyparser中间件 插件进行处理,来获取post请求中请求体的参数
npm i koa-bodyparser -s
server.js
const bodyParser = require('koa-bodyparser')
const app = new Koa()
app.use(bodyParser())
现在发送post请求就可以获取到body请求体了
{ name: 'Max' }
路由模块化
路由太多的话可以分模块进行管理
router/user.js
const Router = require('koa-router')
const router = new Router({
prefix: '/user' // 给当前路由加前缀,这样就不需要当前js内的路由都要加/user了
})
router.get('/', (ctx, next) => {
ctx.body = '用户界面'
})
module.exports = router
server.js
模块化路由 集中导入和注册
// 导入路由
const routerIndex = require('./router')
const userRouter = require('./router/user')
// 注册路由
app.use(routerIndex.routes())
app.use(routerIndex.allowedMethods())
// 注册用户路由
app.use(userRouter.routes())
app.use(userRouter.allowedMethods())
params 参数获取
通过这样定义路由来获取params
router.get('/:id', (ctx, next) => {
console.log(ctx);
ctx.body = 'paramas 用法'
})
浏览器访问地址发起请求
服务端打印ctx请求体
{
request: {
method: 'GET',
url: '/user/1',
header: {
host: 'localhost:3000',
connection: 'keep-alive',
pragma: 'no-cache',
'cache-control': 'no-cache',
'sec-ch-ua': '"Microsoft Edge";v="95", "Chromium";v="95", ";Not A Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Edg/95.0.1020.30',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'sec-fetch-site': 'none',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'sec-fetch-dest': 'document',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh,zh-TW;q=0.9,en;q=0.8,en-US;q=0.7,en-GB;q=0.6',
cookie: 'Hm_lvt_c00d5df8a55f7e743259a1f6e1376fa6=1626667918; Hm_lvt_ce268e8f61069a1636a4629acbb82b9a=1633671120; web_uid=18841; UM_distinctid=17ca1c5df0b83-0d19dfbd6ad19d-513c1f42-1fa400-17ca1c5df0c3d9; CNZZDATA1280372952=1553112428-1634801673-null%7C1634801673; language=zh; web_user_token=28CA1F71163E6373'
}
},
response: {
status: 404,
message: 'Not Found',
header: [Object: null prototype] {}
},
app: { subdomainOffset: 2, proxy: false, env: 'development' },
originalUrl: '/user/1',
req: '<original node req>',
res: '<original node res>',
socket: '<original node socket>'
}
可以看到里面没有params属性,但是可以通过ctx.params获取
router.get('/:id', (ctx, next) => {
console.log(ctx.params);
ctx.body = 'params 用法'
})
访问地址,服务端打印
{ id: '1' }
如果是一次请求多个params需要这样写
router.get('/:id/:pid', (ctx, next) => {
console.log(ctx.params);
ctx.body = 'params 用法'
})
服务端打印
{ id: '1', pid: '4' }
query 查询获取
获取query不需要特别设置路由,就可以通过ctx.query获取到
router.get('/', (ctx, next) => {
console.log(ctx.query);
ctx.body = '用户界面'
})
客户端通过 /user?name=Max&age=23来获取
服务端打印
[Object: null prototype] { name: 'Max', age: '23' }
context 命名空间
把想要共享的数据放到空间中,任何的路由都能使用命名空间中的数据,命名空间在顶层路由中处理
server.js
通过 ctx.state.xxx 向命名空间存储数据
app.use(async (ctx, next) => {
// 这里当客户端发起请求时向空间存储数据,来模拟从服务器获取的数据保存到空间
ctx.state.navList = {
a: 1
}
await next()
})
通过 ctx.state.xxx 就可以从各个路由获取到对应数据
router.get('/:id', (ctx, next) => {
console.log(ctx.state.navList);
ctx.body = 'params 用法'
})
客户端发起请求,服务器从数据库通过模型对象.find() 查找相应数据,通过state命名空间来设置为共享数据,一旦设置完成,在任何一个路由内都可以获取到
public 静态资源文件中间件
设置public为静态资源服务器根目录,让文件引入的时候不需要 /public 前缀就可以直接引用静态资源
npm i koa-static -s
server.js
const static = require('koa-static')
// 写在注册路由的前面
// 设置静态资源服务器目录
app.use(static(__dirname + '/public'))
当需要从静态资源中获取图片时就可以通过 src='/images/1.jpg' 获取到
koa-hbs 模板引擎
前面的例子,当访问 localhost:3000 时通过 ctx.body = '主页' 来向客户端发送字符串渲染到整个网页,这种显然不能实用,或者对请求响应发送回一个html,这样代码越来越多,模板越来越多,这是不易维护的
为了项目的可维护性,可扩展性,开发效率更高的操作dom,让写模板开发起来像vue一样插入值,让代码看起来更舒服,推荐在后端中使用模板引擎
常见模板引擎有 hbs pug ejs jade,思想基本一样的,hbs类似于vue,快速的学会一个模板引擎就足够了
npm i koa-hbs@next -s
创建目录和视图模块
server.js 配置引擎
// 引入
const hbs = require('koa-hbs')
// 注册配置模板引擎中间件
app.use(hbs.middleware({
viewPath:__dirname + '/views', // 视图根目录
defaultLayout: 'layout', // 布局模板
partialsPath: __dirname + '/views/partials', // 配置partials目录
disableCache: true // 开发阶段不缓存
}))
layout.hbs中导入视图模块除index.hbs用{{{body}}}导入,其余均为{{>xxx}}导入
组件内只需要直接写标签,像vue一样双花括号使用变量
这个变量是通过 ctx.render() 来传递给模板引擎的,index作为入口,在其他模板内也可以获取到这个数据
router.get('/',async (ctx, next) => {
await ctx.render('index',{name: 'Maxuan'})
})
例如
router.get('/',async (ctx, next) => {
// 连接mongodb find()获取数据
const data = {
title: 'hbs',
subTitle: 'hello hbs',
htmlStr: '<h4>hello Max</h4>',
isShow: true,
username: 'Max',
now: new Date(),
users: [
{
size: 'big',
length: 15,
},
{
size: 'normal',
length: 10,
},
{
size: 'mini',
length: 5,
},
]
}
await ctx.render('index', data)
})
vscode 可以安装 Handlebars 插件作为语法提示和快捷写入
扩展hbs的帮助方法
hbs是一个非常简单的模板引擎,能做的也就是上面这些,对于复杂的操作,比如对数据的格式化,就不行了,这就需要手写工具函数来帮助模板引擎完成这些操作,下面作为一个例子来格式化日期
// 安装日期格式化插件moment
npm i moment -s
utils/date-helpers.js 声明
const hbs = require('koa-hbs')
const moment = require('moment')
// 调用hbs的注册帮助方法,第一个参数为帮助的名字,第二个为回调函数,在这里传入的参数
// 回调函数第一个参数为时间,第二个参数为格式化分隔符
hbs.registerHelper('date', (nowDate, pattern) => {
if(nowDate) {
return moment(nowDate).format(pattern)
} else {
return ''
}
})
server.js 引入,完成
const dateHelper = require('./utils/date-helper.js')
使用方式,第一个为帮助方法的函数名,之后的为需要传入的参数
{{!-- 帮助方法 --}}
{{date now 'YYYY/MM/DD'}}
打开页面刷新,页面上显示的就是格式化完成的时间了
helpers库
helpers/handlebars-helpers: 189 handlebars helpers in ~20 categories
除了自己手写,github一个项目也提供了一个helpers库集成了20类共189个帮助方法方便调用
安装
npm i handlebars-helpers -s
utils/helpers.js 将库导入到utils,以下将使189个helper全部可用,因为是服务端,不需要考虑代码冗余
const hbs = require('koa-hbs')
const helpers = require('handlebars-helpers')
helpers.comparison({
handlebars: hbs.handlebars
})
将帮助方法模块导入到server.js
const helpers = require('./utils/helpers')
使用
比如有以下数据
{
a: true
b: true
arr: [2,5,8,0]
}
模板引擎中的使用方式
<div>
{{#and a b}}
a、b都是true
{{/and}}
</div>
<div>
{{#contains arr 8}}
8在数组中
{{else}}
8不在数组中
{{/contains}}
</div>
全部的帮助和使用方式都在上方链接中
高级应用 - 代码搬家
如果想在模板引擎中使用js、css,可以使用 contentFor
定义代码块 views/index.hbs
{{!-- css --}}
{{#contentFor 'indexCSS'}}
<style>
p{
color: red
}
</style>
{{/contentFor}}
{{!-- js --}}
{{#contentFor 'jquery'}}
<script>
$(function() {
console.log('content for jquery')
})
</script>
{{/contantFor}}
在layout.hbs中引入
{{#block 'indexCSS'}}{{/block}}
<script src='js/jquery.js'></script>
{{#block 'jquery'}}{{/block}}