一起来写个node服务,koa框架

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情

koa–基于Node.js平台的下一代Web开发框架;这标语把我整的明明白白的,试试怎么玩?

看完本文,你也能用koa,实现一个基础的可用的Node服务了

当前环境
node -v     v14.16.1
npm -v      6.14.12

起步

  1. 创建一个文件夹,我这里命名为koa-mock(因为我这个是用来写mock数据的),君且随意

  2. 进入文件夹,初始化一个package.json文件

  3. 安装koa,npm install koa -S

  4. 创建一个src的目录,并在下面创建一个index.js文件

  5. 写个例子看看

    const Koa = require('koa');
    const app = new Koa();
    ​
    app.use(async ctx => {
      ctx.body = 'Hello World,My first koa';
    });
    ​
    app.listen(3000);
    // 如果端口被占用了,请杀死进程或更换端口
    
  6. 终端运行node src/index.js 在浏览器访问,http://localhost:3000/,即可看到Hello World,My first koa

image-20221206105613694.png

  1. 到这里第一阶段就顺利完成了,你也启动了一个node服务了,是不是很简单

挂挡

洋葱模型

Koa应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行(先进后出)

当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。

看个例子

const Koa = require('koa');
const app = new Koa();
​
const middleware1 = function async(ctx, next) {
  console.log('this is middleware1');
  next()
}
​
const middleware2 = function async(ctx, next) {
  console.log('this is middleware2');
  next()
  console.log('this is middleware2 end');
}
​
const middleware3 = function async(ctx, next) {
  console.log('this is middleware3');
  next()
  console.log('this is middleware3 end');
}
​
app.use(middleware1)
app.use(middleware2)
app.use(middleware3)
​
app.listen(3000);

碰到next(),我们就把控制器交给下一个中间件,直到下游没有更多中间件,我们就反向执行,所以打印的是

  • this is middleware1
  • this is middleware2
  • this is middleware3
  • this is middleware3 end
  • this is middleware2 end

如图

image-20221206112354002.png
这个例子非常重要,是koa最大的特点,中间件流程控制;Koa的洋葱模型是以next()函数为分割点,先由外到内执行Request的逻辑,然后再由内到外执行Response的逻辑,这里的request的逻辑,我们可以理解为是next之前的内容,response的逻辑是next函数之后的内容,也可以说每一个中间件都有两次处理时机。

koa路由

Koa 并没有捆绑任何中间件,所以我们使用的每一个中间件都需要自己安装;Web服务中,我们一般用URL来区分应该执行哪些方法,将数据返回给客服端,这个路径和执行函数的关系映射,就是路由,即构建一套关系映射,用来处理不同的请求。

  • 安装koa路由中间件,npm install koa-router -S

  • 写代码

    const koa = require('koa')
    const Router = require('koa-router')
    const app = new koa();
    const router = new Router()
    ​
    // 设置路由前缀
    router.prefix('/api')
    ​
    // 访问路由路径`/api/`的处理函数
    router.get('/', ctx => {
      ctx.body = 'Hello World,My first koa router';
    })
    ​
    // 访问路由路径`/api/second`的处理函数
    router.get('/second', ctx => {
      ctx.body = 'Hello World,My second koa router';
    })
    ​
    ​
    // 使用路由和路由方法
    app.use(router.routes())
      .use(router.allowedMethods)
      .listen(3000)
    // 将给定的中间件方法添加到此应用程序。app.use() 返回 this, 因此可以链式表达
    ​
    
  • 分别访问http://localhost:3000/api/http://localhost:3000/api/second,分别输出Hello World,My first koa router,Hello World,My second koa router

前面说了koa没有绑定任何中间件,那要实现一些功能,怎么知道它有没有对应的中间件呢,这个时候可以去百度或者去npm去找对应的中间件,本文也会把常用的中间件都用上一用

image-20221206120411688.png

参数解析

koa-body 是一个可以帮助解析 http 中 body 的部分的中间件,包括 json、表单、文本、文件等

  • npm install koa-body -S
const { koaBody } = require('koa-body');
​
app.use(koaBody())
  • 获取参数就可以使用ctx.request.body了,获取上传后文件的信息,则需要在 ctx.request.files 中获取

这是最基本的使用,后面例子中再讲接收文件等静态资源时koa-body应该怎么配置,例如

app.use(koaBody({
  multipart:true, // 支持文件上传
  encoding:'gzip',
  formidable:{
    uploadDir:path.join(__dirname,'public/upload/'), // 设置文件上传目录
    keepExtensions: true,    // 保持文件的后缀
    maxFieldsSize:2 * 1024 * 1024, // 文件上传大小
    onFileBegin:(name,file) => { // 文件上传前的设置      
    },
  }
}));

跨域处理

  • npm install @koa/cors -S
const cors = require('@koa/cors')
app.use(cors())

是不是特别简单,哈哈

安全头

  • npm install koa-helmet -S
const helmet = require('koa-helmet')
app.use(helmet())

静态资源

  • npm intall koa-static -S
const static = require('koa-static')
const path = require('path')
​
app.use(static(path.join(__dirname, '../public')))

输出json格式化

  • npm install koa-json -S
app.use(json({pretty: false, param: 'pretty'})) // 输出参数带pretty可以格式化成json

至此我们就有了这样一份代码index.js,接下来我们进行合理的分配和文件整理

const koa = require('koa');
const Router = require('koa-router');
const static = require('koa-static');
const path = require('path');
const jsonutil = require('koa-json');
const cors = require('@koa/cors');
const helmet = require('koa-helmet');
const { koaBody } = require('koa-body');
​
​
const app = new koa();
const router = new Router();
​
// 设置路由前缀
router.prefix('/api')
​
// 访问路由路径`/api/`的处理函数
router.get('/', ctx => {
  console.log(ctx)
  console.log(ctx.request);
  ctx.body = 'Hello World,My first koa router';
})
​
// 访问路由路径`/api/second`的处理函数
router.get('/second', ctx => {
  console.log(ctx)
  console.log(ctx.request);
  ctx.body = 'Hello World,My second koa router';
})
​
​
// 使用路由中间件和路由方法
app.use(koaBody())
app.use(static(path.join(__dirname, '../public')))
app.use(cors())
app.use(jsonutil({ pretty: false, param: 'pretty' }),)
app.use(helmet())
app.use(router.routes())
app.use(router.allowedMethods)
app.listen(3000)
// 将给定的中间件方法添加到此应用程序。app.use() 返回 this, 因此可以链式表达

加速

ES6支持

都2022年了,肯定是要用ES6的,而使用ES6,需要借助Babel进行编译;如果通过webpck在lodaer中配置通过使用babel-loader使用Babel;会更加美妙些

npm install webpack@4 webpack-cli@3 -D
​
// webpack4.0之后 webpack 和webpack-cli 分离了,所以需要安装两个依赖包

建立webpack.config.js文件,安装常用插件

npm install -D clean-webpack-plugin webpack-node-externals @babel/core @babel/node @babel/preset-env babel-loader cross-env 
​
// 清理dist下面的文件、对node_modules下的文件做排除处理、babel相关(windows上@babel/node进行全局安装,否则后续会提示:‘babel-node’不是内部或外部命令)、设置环境变量

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',
  entry: {
    server: path.join(__dirname, './src/index.js')
  },
  output: {
    filename: 'app.js',
    path: path.join(__dirname, './dist')
  },
  module: {
    rules: [{
      test: /.js$/,
      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"
        }
      }
    ]
  ]
}

接下来就是把之前导入的语法全部改出ES6导入写法

import koa from 'koa'
import Router from 'koa-router';
import statics from 'koa-static';
import path from 'path';
import jsonutil from 'koa-json';
import cors from '@koa/cors';
import helmet from 'koa-helmet';
import koaBody from 'koa-body';

使用npx babel-node src/index.js进行编译就可以啦

路由合并

  1. 首先我们把路由和路由对应的实现方法分开了,在src下分别创建controllerroutes两个文件夹

    1. controller下存放着路由对应的是实现方法
    2. routes存放着各个模块的路由
  2. routes下创建一个modules文件夹,用于存放区分各模块,假设我们现在有两个模块,一个用户模块、一个文章模块

    1. modules下创建userRouter.js;在controller下创建userController.js

      • userRouter.js,假设有两个路由,获取用户信息和修改密码
      import Router from 'koa-router'
      import userController from '../../controller/userController'
      const router = new Router()
      ​
      router.prefix("/user")
      ​
      // 获取用户基本信息
      router.get('/basicInfo', userController.getBasicInfo)
      ​
      ​
      // 获取用户基本信息
      router.get('/changePassword', userController.changePasswd)
      ​
      export default router
      
      • userController.js,就有对应的实现
      class UserController {
      ​
        // 获取用户信息
        async getBasicInfo(ctx) {    
          ctx.body = {
            code: 200,
            data: {userName:'George'},
            msg: '查询成功!'
          }
        }
      ​
        // 修改密码
        async changePasswd(ctx) {
          ctx.body = {
            code: 200,      
            msg: '修改成功!'
          }
        }
      ​
      }
      ​
      export default new UserController()
      
    2. modules下创建contenRouter.js;在controller下创建contenController.js

      • contenRouter.js,假设有一个路由,发表文章
      import Router from 'koa-router'
      import contentController from '../../controller/contenController'
      const router = new Router()
      ​
      router.prefix('/content')
      ​
      // 发表文章
      router.post('/addPost', contentController.addPost)
      ​
      export default router
      
      • contenController.js,就有对应的实现
      class ContentController {
        async addPost(ctx) {
          ctx.body = {
            code: 200,      
            msg: '添加文章成功!'
          }
        }
      }
      ​
      export default new ContentController()
      
  3. 这样我们就有了两个路由:用户路由和文章路由;可能之后还有很多路由,我们需要把这些路由进行集中,进行合并暴露出去,写在一起肯定是下下之选了,所以我们使用另一个中间件koa-combine-routers进行合并

  4. routes下创建一个route.js,该文件进行合并路由,作为统一出口, npm install koa-combine-routers

    import combineRoutes from 'koa-combine-routers'
    ​
    import userRouter from './modules/userRouter'
    import contenRouter from './modules/contenRouter'
    ​
    export default combineRoutes(userRouter, contenRouter)
    
  5. 修改index.js

    import koa from 'koa'
    import router from './routes/route'
    import statics from 'koa-static';
    import path from 'path';
    import jsonutil from 'koa-json';
    import cors from '@koa/cors';
    import helmet from 'koa-helmet';
    import koaBody from 'koa-body';
    ​
    const app = new koa();
    ​
    ​
    app.use(koaBody())
    app.use(statics(path.join(__dirname, '../public')))
    app.use(cors())
    app.use(jsonutil({ pretty: false, param: 'pretty' }),)
    app.use(helmet())
    app.use(router())
    app.listen(3000)
    

热更新

不想每修改一点东西就重新启动一下,那就使用热更新,用nodemon监听文件变化,自动重启

  • npm install -D nodemon

  • 用npx执行npx nodemon --exec babel-node src/index.js

  • 或者修改pagejson的文件

     "scripts": {   
        "start:es6": "nodemon --exec babel-node src/index.js"
      },
    

    npm run start:es6

整合koa中间件

  • npm install koa-compose -S

    import koa from 'koa'
    import router from './routes/route.js'
    import path from 'path'
    import helmet from 'koa-helmet'
    import statics from 'koa-static'
    import koaBody from 'koa-body'
    import jsonutil from 'koa-json'
    import cors from '@koa/cors'
    import compose from 'koa-compose'
    const app = new koa();
    ​
    const middleware = compose(
      [
        koaBody({
          multipart: true,
          encoding: 'utf-8',
          formidable: {
            keepExtensions: true,
            maxFieldsSize: 5 * 1024 * 1024
          },
          onError: err => {
            console.log('koabody Err', err);
          }
        }),
        statics(path.join(__dirname, '../public')),
        cors(),
        jsonutil({ pretty: false, param: 'pretty' }),
        helmet(),
      ]
    )
    ​
    app.use(middleware)
    app.use(router())
    app.listen(3000)
    ​
    

到这里为止,我们用koa已经实现了一个基础的可用的Node服务了,当我们访问localhost:3000/user/basicInfo等接口时,能返回我们写的结果;但是这仅仅只是起步、挂挡、加速;我们还需要会其他很多技巧,换挡、倒车入库、侧方停车等等;不要着急,慢慢积累,多花时间练习,一定可以很好的掌握的。

继续行驶

  • 怎么和数据库打交道
  • 怎么加入鉴权机制
  • 怎么实现websocket长链接
  • 怎么实现邮件服务
  • 怎么统一处理错误
  • 怎么实现生产环境和开发环境的区分和支持
  • ……..

如果你看到这里了,烦请大佬点个赞,鼓励小弟学习,不胜感激,谢谢。

本次学习项目gitee地址

https://gitee.com/zhuang_quan_fa/koa-node.git

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容