基于Koa搭建一个nodejs实践

一、Koa简介

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。
https://koa.bootcss.com/#

二、环境搭建

先安装koa框架

npm i koa -S

创建app.js文件,必须的hello,world

const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => {
  await next()
  ctx.response.type = "text/html"
  ctx.response.body = "<h1>Hello World</h1>"
})

app.listen(3000, () => {
  console.log('server is running at http://localhost:3000')
})

三、中间件

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

app.use(async (ctx, next) => {
  await next()
  ctx.response.type = "text/html"
  ctx.response.body = "<h1>Hello World</h1>"
})

ctx

ctx就是上下文(Context),Context 将 node 的 request 和 response 对象封装到单个对象中,还对 Koa 内部对一些常用的属性或者方法做了代理操作,使得我们可以直接通过 ctx 获取。比如,ctx.request.url 可以写成 ctx.url。除此之外,Koa 还约定了一个中间件的存储空间 ctx.state。通过 state 可以存储一些数据,比如用户数据,版本信息等。

next

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

// 按照官方示例
const Koa = require('koa')
const app = new Koa()

// 记录执行的时间
app.use(async (ctx, next) => {
  let stime = new Date().getTime()
  console.log('开始时间'+stime);
  await next()
  let etime = new Date().getTime()
  ctx.response.type = 'text/html'
  ctx.response.body = '<h1>Hello World</h1>'
  console.log('结束时间'+etime);
  console.log(`请求地址: ${ctx.path},响应时间:${etime - stime}ms`)
});

app.use(async (ctx, next) => {
  console.log('中间件1 doSoming');
  await next();
  console.log('中间件1 end');
})

app.use(async (ctx, next) => {
  console.log('中间件2 doSoming');
  await next();
  console.log('中间件2 end');
})

app.use(async (ctx, next) => {
  console.log('中间件3 doSoming');
  await next();
  console.log('中间件3 end');
})

app.listen(3000, () => {
  console.log('server is running at http://localhost:3000')
})

四、路由koa-router

如果我们不引入koa-router的话,我们只能通过中间件进行配置

app.use(async (ctx, next) => {
    if (ctx.request.path === '/') {
        ctx.response.body = '<h1>index page</h1>';
    } else {
        await next();
    }
});
app.use(async (ctx, next) => {
    if (ctx.request.path === '/home') {
        ctx.response.body = '<h1>home page</h1>';
    } else {
        await next();
    }
});
app.use(async (ctx, next) => {
    if (ctx.request.path === '/404') {
        ctx.response.body = '<h1>404 Not Found</h1>';
    } else {
        await next();
    }
});

引入koa-router

npm i koa-router -S

安装完成后执行

const Koa = require('koa')
// 注意 require('koa-router') 返回的是函数:
const router = require('koa-router')()
const app = new Koa()

// 添加路由
 router.get('/', async (ctx, next) => {
    ctx.response.body = `<h1>index page</h1>`
})

router.get('/home', async (ctx, next) => {
    ctx.response.body = '<h1>HOME page</h1>'
})

router.get('/404', async (ctx, next) => {
    ctx.response.body = '<h1>404 Not Found</h1>'
})

// 调用路由中间件
app.use(router.routes())

app.listen(3000, ()=>{
  console.log('server is running at http://localhost:3000')
})

koa-router支持get、post、put、delete等用法
此外还支持url命名、单路由多层嵌套中间件、路由嵌套、路由前缀、url参数等等

var router = new Router({
  prefix: '/users'
});

router.get('/:category/:title', function (ctx, next) {
  console.log(ctx.params);
  // => { category: 'programming', title: 'how-to-node' } 
});

五、视图Nunjucks

前期准备工作做完,我们下面要做的就是页面展示,视图渲染
我简单介绍一下koa-nunjucks-2这个模板引擎

Nunjucks语法简单介绍
{{ username }} // 变量
{{ foo | title }} // 过滤器
// if语句
{% if hungry %}
 I am hungry
{% elif tired %}
  I am tired
{% else %}
  I am good!
{% endif %}
// for循环
<ul>
{% for item in items %}
  <li>{{ item.title }}</li>
{% else %}
  <li>This would display if the 'item' collection were empty</li>
{% endfor %}
</ul>
// 宏
{% macro field(name, value='', type='text') %}
<div class="field">
  <input type="{{ type }}" name="{{ name }}"
        value="{{ value | escape }}" />
</div>
{% endmacro %}
// 把 field 当作函数一样使用:
{{ field('user') }}
{{ field('pass', type='password') }}

更多语法内容请查阅官方文档

安装
npm i koa-nunjucks-2 -S

代码

const Koa = require('koa')
  const path = require('path')
const bodyParser = require('koa-bodyparser')
const nunjucks = require('koa-nunjucks-2')

const app = new Koa()
const router = require('./router')

app.use(nunjucks({
  ext: 'html',
  path: path.join(__dirname, 'views'),// 指定视图目录
  nunjucksConfig: {
    trimBlocks: true // 开启转义 防Xss
  }
}));

app.use(bodyParser())
router(app)
app.listen(3000, () => {
  console.log('server is running at http://localhost:3000')
})

六、静态资源

视图层展示之后我们需要处理静态资源

npm i koa-static -S

增加并指定 /public 目录为静态资源目录

  const Koa = require('koa')
  const path = require('path')
  const bodyParser = require('koa-bodyparser')
  const nunjucks = require('koa-nunjucks-2')
  // 引入 koa-static
  const staticFiles = require('koa-static')

  const app = new Koa()
  const router = require('./router')

  // 指定 public目录为静态资源目录,用来存放 js css images 等
  app.use(staticFiles(path.resolve(__dirname, "./public")))

  app.use(nunjucks({
    ext: 'html',
    path: path.join(__dirname, 'views'),
    nunjucksConfig: {
      trimBlocks: true
    }
  }));

  app.use(bodyParser())
  router(app)
  app.listen(3000, () => {
    console.log('server is running at http://localhost:3000')
  })

七、解析JSON

我们数据一般都是JSON格式的,我们只需要设置把数据挂载在响应体 body 上,同时告诉客户端『返回的是 JSON 数据』,客户端就会按照 JSON 来解析了

ctx.set("Content-Type", "application/json")
ctx.body = JSON.stringify(json)

我们将这段代码提取为中间件,这样更方便代码的维护性和扩展性
创建js

module.exports = () => {
  function render(json) {
      this.set("Content-Type", "application/json")
      this.body = JSON.stringify(json)
  }
  return async (ctx, next) => {
      ctx.send = render.bind(ctx)
      await next()
  }
}

我们把 JSON 数据的处理方法挂载在 ctx 对象中,并起名为 send。当我们需要返回 JSON 数据给客户端时候,只需要调用此方法,并把 JSON 对象作为参数传入到方法中就行了,用法如下:

ctx.send({
  status: 'success',
  data: 'hello ikcmap'
})

引入中间件,修改app.js

const Koa = require('koa')
const app = new Koa()
const router = require('./router')

const middleware = require('./middleware')

middleware(app)
router(app)
app.listen(3000, () => {
  console.log('server is running at http://localhost:3000')
})

八、记录日志

安装 log4js 模块

npm i log4js -S

日志配置

const log4js = require('log4js');
// 引入日志输出信息的封装文件
const access = require("./access.js");
const methods = ["trace", "debug", "info", "warn", "error", "fatal", "mark"]

const baseInfo = {
  appLogLevel: 'debug',
  dir: 'logs',
  env: 'dev',
  projectName: 'koa2-tutorial',
  serverIp: '0.0.0.0'
}
const { env, appLogLevel, dir, serverIp, projectName } = baseInfo
// 增加常量,用来存储公用的日志信息
const commonInfo = { projectName, serverIp }
module.exports = () => {
  const contextLogger = {}
  const appenders = {}

  appenders.cheese = {
    type: 'dateFile',
    filename: `${dir}/task`,
    pattern: '-yyyy-MM-dd.log',
    alwaysIncludePattern: true
  }
  
  if (env === "dev" || env === "local" || env === "development") {
    appenders.out = {
      type: "console"
    }
  }
  let config = {
    appenders,
    categories: {
      default: {
        appenders: Object.keys(appenders),
        level: appLogLevel
      }
    }
  }

  const logger = log4js.getLogger('cheese');

  return async (ctx, next) => {
    const start = Date.now()

    log4js.configure(config)
    methods.forEach((method, i) => {
      contextLogger[method] = (message) => {
       // 将入参换为函数返回的字符串
        logger[method](access(ctx, message, commonInfo))
      }
    })
    ctx.log = contextLogger;

    await next()
    const responseTime = Date.now() - start;
    logger.info(access(ctx, {
      responseTime: `响应时间为${responseTime/1000}s`
    }, commonInfo))
  }
}

九、错误处理

当页面访问异常的时候进行错误页面展示

const Path = require('path') 
const nunjucks = require('nunjucks')
module.exports = (opts = {}) => {
  // 增加环境变量,用来传入到视图中,方便调试
  const env = opts.env || process.env.NODE_ENV || 'development'  

  const folder = opts.errorPageFolder
  const templatePath = Path.resolve(__dirname, './error.html')
  let fileName = 'other'
  return async (ctx, next) => {
    try {
       await next()
       if (ctx.response.status === 404 && !ctx.response.body) ctx.throw(404);
    } catch (e) {
      let status = parseInt(e.status)
      const message = e.message
      if(status >= 400){
        switch(status){
          case 400:
          case 404:
          case 500:
            fileName = status;
            break;
          default:
            fileName = 'other'
        }
      }else{
        status = 500
        fileName = status
      }
      const filePath = folder ? Path.join(folder, `${fileName}.html`) : templatePath
      // 渲染对应错误类型的视图,并传入参数对象
      try{
        nunjucks.configure( folder ? folder : __dirname )
        const data = await nunjucks.render(filePath, {
          env: env, // 指定当前环境参数
          status: e.status || e.message, // 如果错误信息中没有 status,就显示为 message
          error: e.message, // 错误信息
          stack: e.stack // 错误的堆栈信息
        })
        // 赋值给响应体
        ctx.status = status
        ctx.body = data
      }catch(e){
        // 如果中间件存在错误异常,直接抛出信息,由其他中间件处理
        ctx.throw(500, `错误页渲染失败:${e.message}`)
      }
    }
  }
}

十、规范与部署

全局安装 nodemon:

npm i nodemon -g

本地项目中也需要安装:

npm i nodemon -S

更多细节用法,请查阅官方文档

部署运行
线上部署运行的话,方法也有很多,我们推荐使用 pm2。
pm2 是一个带有负载均衡功能的Node应用的进程管理器。
安装方法与 nodemon 相似,需要全局安装:

npm i pm2 -g

运行方法:

pm2 start app.js

更多细节用法,请查阅官方文档

参考文档:
https://github.com/ikcamp/koa2-tutorial

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值