Koa2 框架入门与进阶

  • Post author:
  • Post category:其他





前言


什么是框架(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 版权协议,转载请附上原文出处链接和本声明。