Koa2 框架入门与进阶
前言
什么是框架(frame):
- 封装原生代码的 API
- 规范流程和格式
- 让开发人员更加关注于业务代码,提高开发效率
框架(frame)和库(lib)的区别:
- 框架是唯一的,库就可以共存
- 框架关注全流程,库关注单个功能
- 框架和库类比于 Vue 和 lodash
一、Koa 简介
1、koa2 是什么
-
Koa 是一个新的
web 框架
,致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 -
利用
async 函数
丢弃传统的回调函数,并增强错误处理。Koa 没有
任何预置
的中间件,可快速而愉快地编写服务端应用程序。 - koa 是基于 nodejs 平台的下一代 web 开发框架(koa2 是 nodejs web server 框架)
-
koa2 的官网:
https://koa.bootcss.com
- koa2 可以通过 async/await 语法高效编写 web server
- 使用中间件机制,能合理拆分业务代码
2、koa 核心概念
- Koa Application(应用程序)
- Context(上下文)
- Request(请求)、Response(响应)
3、koa 的安装与基本使用
-
初始化:
npm init
// or
npm init -y
-
安装:
npm install koa --save
-
基本使用:
const Koa = require('koa')
const app = new Koa()
// ctx 读 context 上下文的意思
app.use(async (ctx) => {
ctx.body = 'hello world'
})
app.listen(3000)
二、Koa2 安装&配置
1、koa2 环境搭建
(1)使用脚手架 koa-generator 创建 koa2 项目
-
安装 koa2 脚手架:
实际项目,不会从 0 开始 搭建 koa2 环境 - 类比于 @vue-cli 或 @vue/cli
// 安装 koa2 脚手架
npm install -g koa-generator
// 验证是否安装成功
koa2 --version
-
使用脚手架 koa-generator 创建 koa2 项目:koa2 项目名
// koa2 项目名
koa2 test
-
运行 koa2 框架
// 进入该项目
cd test
// 下载安装项目依赖
npm install
// 运行 koa2 项目
npm run dev
(2)介绍项目目录和文件
- 入口文件 app.js
- 已安装的 npm 插件
- 各个目录和文件
(3)在项目中新建一个路由
- 新建路由文件:在 routers 目录文件下创建一个新的 js 文件(例如:comment.js)
-
新建路由:
// comment.js
const Router = require('koa-router')
const router = new Router()
// 或者写为
// const router = require('koa-router')()
// 定义路由前缀,没有可不添加
router.prefix('/api')
// 定义路由:模拟获取
router.get('/list', async (ctx, next) => {
ctx.body = 'get /api/list'
})
// 定义路由:模拟创建
router.post('/create', async (ctx, next) => {
ctx.body = 'post /api/create'
})
// 输出路由
module.exports = router
-
在 app.js 中引入和注册路由:
// app.js
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')
// 引入路由
const index = require('./routes/index')
const users = require('./routes/users')
const comments = require('./router/comments')
// error handler:错误处理器
onerror(app)
// middlewares:中间件
app.use(bodyparser({
enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))
app.use(views(__dirname + '/views', {
extension: 'pug'
}))
// logger 打印当前请求所花费的时间
app.use(async (ctx, next) => {
const start = new Date()
await next()
const ms = new Date() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
// routes 注册路由
// comments.allowedMethods() 是对于 404 或者 返回是空的情况的一种补充
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())
app.use(comments.routes(), comments.allowedMethods())
// error-handling
app.on('error', (err, ctx) => {
console.error('server error', err, ctx)
});
module.exports = app
3、koa2 处理 http 请求
如何接受数据和返回数据:
- ctx 即 req 和 res 的集合
- querystring 和 Request Body
- Response Body 和 Content-type
// 定义路由:模拟获取
router.get('/list', async (ctx, next) => {
const query = ctx.query // req 的功能
// ctx.body = 'get /api/list' // res 的功能
// 如果要返回 JSON 格式,可以直接返回 json 格式
ctx.body = {
errno: 0,
data: [
{ user: 'zs', content: 'content1' },
{ user: 'ls', content: 'content2' }
]
}
})
// 定义路由:模拟创建
router.post('/create', async (ctx, next) => {
const body = ctx.request.body // 得到 request body
// ctx.body = 'post /api/create'
ctx.body = {
errno: 0,
message: 'success'
}
})
对比 koa2 代码和 nodejs 原生代码:
- nodejs 原生代码:
// 模拟获取信息的 url
if(path === '/api/list' && method === 'GET') {
const result = {
errno: 0,
data: [
{ username: 'zll', content: 'content1'},
{ username: 'lgk', content: 'content2'}
]
}
res.writeHead(200, { 'Content-type': 'application/json' })
// JSON.stringify(result) 将 JSON 转为 字符串形式
res.end(JSON.stringify(result))
}
// 模拟发送信息的 url
if(path === '/api/create' && method === 'POST') {
const result = {
errno: 0,
message: '发送信息成功'
}
res.writeHead(200, { 'Content-type': 'application/json' })
// JSON.stringify(result) 将 JSON 转为 字符串形式
res.end(JSON.stringify(result))
}
4、koa2 洋葱圈模型
-
洋葱圈模型执行:一个请求首先会经过最外层的中间件,这个中间件还没有执行完成又继续向内一层执行,依次向内,直到最内层执行完成,然后才会逐层向外继续执行未完成的操作
@图片来源于网络
5、async(异步) 和 await(等待)
- async 作为一个关键字放在函数的前面,表示该函数是一个异步函数,意味着该函数的执行不会阻塞后面代码的执行
- await 只能在异步函数 async function中使用,否则会报错;使用 await 后会暂停当前 async function 的执行,等待 await 对应的 Promise 处理完成。
三、Koa 常用中间件介绍
1、koa 中间件介绍
什么是中间件:
- 一个流程上,独立的业务模块
- 可扩展,可插拔
- 类似于工厂的流水线
为何使用中间件:
- 拆分业务模块,使代码清晰
- 统一使用中间件,使得各业务代码都规范标准
- 扩展性好,易添加、易删除
koa2 中间件:
- 回顾 koa2 框架中的 app.js
- 所有的 app.use(…) 都是中间件:所有的网络请求都经过 app.use()
-
路由也是中间件(只不过限制了 url 规则):只有符合规则的请求才通过
中间件的执行顺序:按照洋葱模型执行
- 中间件的执行顺序与 app.use() 的调用顺序有关,如果调用靠前的中间件没有执行 next() ,就会导致该中间件之后的中间件不再执行
模拟登陆验证功能:
- 登陆校验,可使用中间件来实现
- 一种情况是:所有的接口(路由)都需要登陆校验
- 另一种情况是:只有一部分接口需要登陆校验
// 模拟所有的接口(路由)都需要登陆校验
app.use(async (ctx, next) => {
const query = ctx.query
if(query.user === 'zs') {
// 模拟登陆成功
await next() // 执行下一步中间件,下一步一般到路由
} else {
// 模拟登陆失败
ctx.body = '请登陆'
}
})
2、常用中间件介绍
路由:koa-router
- 安装 koa-router
npm install koa-router --save
- 使用 koa-router
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
const router = new Router()
// 可以在路由之前设置前缀
router.prefix('/api')
// 获取请求参数
router.get('/list1', (ctx) => {
// name: 'lgk', age: '12'
const params = ctx.request.query
ctx.body = {
name: params.name,
age: params.age
}
})
router.get('/list', (ctx) => {
let { body } = ctx.request
ctx.body = {
...body
}
})
app.use(router.routes()).use(router.allowedMethods())
协议解析:koa-body
- 安装
npm install --save koa-body
- 使用
const Koa = require('koa')
const app = new Koa()
const koaBody = require('koa-body')
app.use(koaBody())
跨域处理:@koa/cors
- 安装
npm install --save @koa/cors
- 使用
const Koa = require('koa')
const app = new Koa()
const CORS = require('@koa/cors')
app.use(CORS())
格式化数据:koa-json
- 浏览器在每次发送 GET 请求时,会在浏览器显示格式化后的 JSON 数据
- 安装
npm install --save koa-json
- 使用
const Koa = require('koa')
const app = new Koa()
const json = require('koa-json')
// 参数的意思是:只有在URL中添加参数的时候才会使用
// 并且在参数后面再添加 &pretty
app.use(json({ pretty: false, param: 'pretty' }))
合并路由:koa-combine-routers
- 合并路由
- 安装
npm install --save koa-combine-routers
- 使用
// routes.js
const Router = require('koa-router')
const combineRouters = require('koa-combine-routers')
const dogRouter = new Router()
const catRouter = new Router()
dogRouter.get('/dogs', async ctx => {
ctx.body = 'ok'
})
catRouter.get('/cats', async ctx => {
ctx.body = 'ok'
})
// 在路由之后使用
const router = combineRouters(
dogRouter,
catRouter
)
module.exports = router
const Koa = require('koa')
const app = new Koa()
const combineRouters = require('./routes')
app.use(combineRouters())
安全header:koa-helmet
- 会在请求中加入安全头,使应用更加安全
- 安装
npm install --save koa-helmet
- 使用
const Koa = require('koa')
const app = new Koa()
const helmet = require('koa-helmet')
// 在路由之前使用
app.use(helmet())
静态资源:koa-static
- 用于引入静态资源
- 安装
npm install --save koa-static
- 使用
const Koa = require('koa')
const app = new Koa()
const statics = require('koa-static')
const path = require('path')
// 在路由之前使用
// __dirname 表示当前目录
app.use(statics(path.join(__dirname, '../public')))
整合中间件:koa-compose
- koa-compose 集成中间件
- 安装
npm install -S koa-compose
- 使用
const koaBody = require('koa-body');
const cors = require('@koa/cors');
const jsonUtil = require('koa-json');
const helmet = require('koa-helmet');
const statics = require('koa-static');
const koaCompose = require('koa-compose');
const router = require('./routes/routes')
// app.use(helmet())
// app.use(statics(path.join(__dirname, '../public')))
// app.use(router())
const middleware = koaCompose([
koaBody(),
statics(path.join(__dirname, '../public')),
cors(),
jsonUtil({ pretty: false, param: 'pretty' }),
helmet()
])
app.use(middleware)
app.use(router())
合并 Webpack 配置文件:webpack-merge
- 安装
npm install -D webpack-merge
- 使用
const webpackMerge = require('webpack-merge');
const TerserWebpackPlugin = require('terser-webpack-plugin');
const webpackConfig = require('./webpack.config.base');
const prodWebpackConfig = webpackMerge.merge(webpackConfig, {
mode: 'production',
optimization: {
minimizer: [
new TerserWebpackPlugin({
terserOptions: {
warnings: false,
compress: {
warnings: false,
// 是否注释掉 console
drop_console: false,
dead_code: true,
drop_debugger: true
},
output: {
comments: false,
beautify: false
}
},
// parallel: true,
// sourceMap: false
})
],
splitChunks: {
cacheGroups: {
commons: {
name: 'commons',
chunks: 'initial',
minChunks: 3,
enforce: true
}
}
}
}
})
module.exports = prodWebpackConfig
压缩中间件:koa-compress
- 安装
npm install koa-compress -S
- 使用
const koaCompress = require('koa-compress');
import router from './routes/routes';
const isDevMode = process.env.NODE_ENV === 'production' ? false : true
// app.use(helmet())
// app.use(statics(path.join(__dirname, '../public')))
// app.use(router())
const middleware = koaCompose([
koaBody(),
statics(path.join(__dirname, '../public')),
cors(),
jsonUtil({ pretty: false, param: 'pretty' }),
helmet()
])
if(!isDevMode) {
app.use(koaCompress())
}
app.use(middleware)
app.use(router())
app.listen(3000)
四、Koa 开发配置
1、Koa 配置开发热加载ES6语法支持&webpack配置
监测 JS 变化(热更新):nodemon
- 安装
npm install --save-dev nodemon
- 配置
// package.json
// src/index.js 是监测的文件
"scripts": {
"dev": "nodemon src/index.js"
}
- 使用
npm run dev
支持ES6语法:
- 安装
npm install --save-dev webpack webpack-cli
- 安装
// 每次打包都会重新清理 dist 目录
npm install -save-dev clean-webpack-plugin
// 对 node_modules 目录下的目录做排除处理
npm install -D webpack-node-externals
//
npm install -D @babel/core
npm install -D @babel/node
npm install -D @babel/preset-env
npm install -D babel-loader
// 设置环境变量
npm install -D cross-env
- 创建 webpack.config.js 文件
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpackConfig = {
target: 'node',
mode: 'development',
devtool: 'eval-source-map',
entry: {
server: path.join(__dirname, './src/app.js')
},
output: {
filename: '[name].bundle.js',
path: path.join(__dirname, './dist')
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: {
loader: 'babel-loader'
},
exclude: [path.join(__dirname, '/node_modules')]
}
]
},
externals: [
nodeExternals()
],
plugins: [
new CleanWebpackPlugin()
],
// node: {
// console: true,
// global: true,
// process:true,
// Buffer: true,
// __filename: true,
// __dirname: true,
// setImmediate: true,
// path: true
// }
}
module.exports = webpackConfig
- 创建 .babelrc 文件
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
]
}
- 配置 package.json 文件
{
"name": "community-api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev:commonjs": "nodemon src/app.js",
"dev:es6": "nodemon --exec babel-node src/app.js",
"webpack:debug": "node --inspect-brk ./node_modules/.bin/webpack --inline --progress"
},
"author": "lgk",
"license": "ISC",
"dependencies": {
"@koa/cors": "^3.1.0",
"koa": "^2.13.4",
"koa-body": "^4.2.0",
"koa-combine-routers": "^4.0.2",
"koa-helmet": "^6.1.0",
"koa-json": "^2.0.2",
"koa-router": "^10.1.1",
"koa-static": "^5.0.0"
},
"devDependencies": {
"@babel/core": "^7.15.8",
"@babel/node": "^7.15.8",
"@babel/preset-env": "^7.15.8",
"babel-loader": "^8.2.3",
"clean-webpack-plugin": "^4.0.0",
"cross-env": "^7.0.3",
"nodemon": "^2.0.14",
"webpack": "^5.59.1",
"webpack-cli": "^4.9.1",
"webpack-node-externals": "^3.0.0"
}
}
五、调试
1、console
- 使用 console.log() 进行调试
2、debug
- 设置 debugger 进行调试
- 然后在 Chrome 浏览器点击 inspect 进行逐步调试
npx node --inspect-brk ./node_modules/.bin/webpack --inline --progress
3、VsCode
- 点击左侧的 debug 配置后进行调试
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"name": "nodemon",
"program": "${workspaceFolder}src/app.js",
"request": "launch",
"restart": true,
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/nodemon",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node",
"runtimeArgs": ["--exec", "babel-node"]
}
]
}
六、其他
1、npm-check-updates
- 用于检查 npm 依赖包是否有新的版本
- 全局安装 npm-check-updates
npm install -g npm-check-updates
- 使用 ncu 命令检查 package.json 是否有可更新版本
ncu
- 删除原来的 node_modules
rm -rf node_modules/
- 重新安装
npm install
总结
community-api 项目源码:
https://gitee.com/lgk2021/community-api
版权声明:本文为weixin_49809163原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。