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

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值