把koa2扩展成一个完备服务端api开发框架

将koa2做成一个易用开发的api框架:快速的数据库model层,对用户请求类型参数的完整的处理,系统日志,异常错误的处理.一定程度的分层是必须的

如题,koa本身只包含基础的接受请求返还请求.所有的其他功能都通过洋葱中间件模型实现. 我刚好实现了一个扩展koa2本身功能,满足服务端快速开发的框架.在我看来完备的服务端开发框架,需要满足:快速的数据库改建,对用户请求类型参数的完整的处理,系统日志,异常错误的处理.一定程度的分层是必须的,规划统一的开发方式也是必须的.

0.前置知识点

koa的基础知识,例如:ctx作用. Es6语法:知道回调函数,知道primise的用法,例如then.最新的同步异步处理,如 Nvm安装配置,npm的常用命令.

1.初始化koa2

前置条件:安装nvm(nodejs多版本神器),然后切换版本,到8.11以上版本. 创建新目录,然后npm包创建项目:(你也可以使用yarn)

npm init

项目初始化完成后,会生成package.json.于是你应该就是明白,这个json目录咋回事了..然后以后每次使用npm install xx -save(使用npm安装xx模块,并保存到当前目录).

Package.json文件就会有更改.当然当前目录下还会有node_module专门存储项目所安装的模块. 我们可以安装 Koa(观察 package.json中的变化)

npm install koa -save

当前项目目录下,创建app.js文件,写代码:

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 koa!</h1>'

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

运行node app.js,浏览器中打开http://localhost:3000'显示: Hello koa!

2.实现路由&controller&简单的代码分层

安装router,并划分目录结构,可参考我的提交: https://github.com/ethluz/koa2-apiserver-mvp/commit/c855b769a801e3825943fa83f9dc65f949e70171 代码目录结构如下: 具体步骤(代码就不粘贴了,github上有提交) 需要安装:koa-router模块,然后创建:router.js https://github.com/ethluz/koa2-apiserver-mvp/blob/c855b769a801e3825943fa83f9dc65f949e70171/router.js routerjs中引入了controller,于是就涉及到创建controller目录,添加home.js: https://github.com/ethluz/koa2-apiserver-mvp/tree/c855b769a801e3825943fa83f9dc65f949e70171/controller 最后还要在app.js中载入router中间件.

3.借助babel&实现es6 style的koa

npm i babel-core  babel-polyfill babel-preset-env babel-preset-es2015  babel-preset-stage-0  -S 

在app.js最上方添加如下代码.

require("babel-core/register")({  
    presets: ['env', 'stage-0']
});
require("babel-polyfill"); 

Ps:如果你直接用node.js10以上版本,将自带es6的支持,可跳过上面的配置(主要还是import from, async,await,let,const nodejs8也能支持).

完整的代码可查看,如下提交: https://github.com/ethluz/koa2-apiserver-mvp/commit/d3c1cad77761717809c29a5163abfd8d8bd41c63 这样就可以放心的使用es6语法糖了.

4.实现中间件

其实在上面的router使用中,我们就可以把koa-ruoter当作中间件,实际上所有的app.use 都是载入中间件的方式:

// 示例:载入 bodyparser中间件
  	app.use(bodyParser())

但我们需要考虑代码分层,提升整个koa服务端的拓展性,代码可读性.

中间件扩展 设计的思路: 1.我们不能把所有的app.use都写在app.js中,需要分层. 2.app.use(function()) 需要摒弃.同样的把每个拓展都单独写在各自文件里,于是: 首先创建了middleware目录,然后创建一个中间件mi-send(用于解析json,返回给客户端). mi-send目录中创建index.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()
  }
}

然后在middleware目录下创建一个,index.js用于集中载入中间件:

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

const miSend = require('./mi-send')
module.exports = (app) => {
  app.use(staticFiles(path.resolve(__dirname, "../public")))

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

  app.use(bodyParser())
  app.use(miSend())
}

修改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')
})

Ok,中间件的目录就创建完成了,然后我们修改home.js,在其中一个controller调用这个中间件:

 ctx.send({
       status: 'success',
       data: '流量'
 })

浏览器打开,就能看到效果了.这意味着,你刚刚使用中间件send拓展了ctx对象.

Ps:目录结构如下: 代码提交记录: https://github.com/ethluz/koa2-apiserver-mvp/commit/ce9f295c05d9a1dfa487a60584ccff93bd9e402e

4.实时反馈的node启动&开发环境变量&

代码修改的实时反馈 实时反馈即代码修改后,可用自动启动node,而无法手动退出当前node,重启服务. 我们这里用nodemon,首先安装它

npm i nodemon -save

然后修改package.json的scripts:

"start": "node app.js"

改为:

"start": "nodemon app.js"

环境变量-区分开发环境&线上环境本地等 如果我们的产品是开源代码的,又或者区分正式环境和测试环境(几乎肯定是要分的).那他们所连接的数据库,一些配置就必然不一致,如果直接去改代码就不合适了. 而nodejs提供了node_env 切好就是直面这个问题的.我们继续修改package.json的scripts:

"start": "export NODE_ENV=dev && nodemon app.js",
"prd": "export NODE_ENV=product && nodemon app.js",

这样运行 npm run start就是启动开发环境,而prd则是正式环境了.然而还有一个问题,命令是不同了,但是代码里如何获取node_env的值呢? 在app.js底部添加下面的代码,运行时,看shell中的输出即可:

console.log('\n app.env: \n',app.env);
console.log('\n app.env: \n', process.env);

app.env是koa提供的获取环境变量的对象属性.

model层:sequelize+postgresql的使用 sequelize能支持mysql,postgre和mongodb等.并提供几乎一致的orm.且具备足够的易用性影响力,因此推荐. sequelize的配置: 我们创建db.js保存Sequelize的初始化和基本配置

import Sequelize from 'sequelize';

const sequelize = new Sequelize('数据库名', '账户', '密码', {
    host: '连接主机',
    port: 5432, //端口
    dialect: 'postgres', //数据库,这里我们用postgre,你也可以选择mysql
    dialectOptions: {
        ssl: true   //适用于postgre非常关键,远程连接postgre需要通过ssl
    },
    pool: {
        max: 5,
        min: 0,
        idle: 10000
    }
});

sequelize.sync();

export default sequelize;

这部分也推荐看文档:https://demopark.github.io/sequelize-docs-Zh-CN/getting-started.html

有了上面,其实还没完,我们还要创建Model:

import Sequelize from 'sequelize';

export default function(sequelize) {
  const users = sequelize.define('users', {
    password: {
      allowNull: false,
      type: Sequelize.STRING,
      unique: true,
      validate: { isLowercase: true }
    },
    username: {
      type: Sequelize.STRING,
      unique: true
    }
  });
}

注:上面的代码并不需要创建id和createtime,因为sequelize会为我们自动添加上默认的. Model的创建也可以查看文档:https://demopark.github.io/sequelize-docs-Zh-CN/models-definition.html

然后我们需要在最上面的db.js中载入这个model: import 上面的model:

import users from './models/users.model';

然后把初始化的sequelize对象传入model function中:(很明显的用了闭包):

users(sequelize);

那么如何用到这个model呢,我们在需要使用数据库的controller头部添加如下代码:这里需要注意下sequelize.models.xx那一行,你在文档中很难察觉,切记

import sequelize from '../db';
//注意下面这个属性:
const users = sequelize.models.users;

sequelize.models.xx可以获取我们刚刚的users model对象,然后我们就可以随意的使用users.save() user.findAll()等等调取user对象的方法了. sequelize对model定义了哪些方法?请看:https://demopark.github.io/sequelize-docs-Zh-CN/models-usage.html

sequelize的一点资料 中文文档: https://demopark.github.io/sequelize-docs-Zh-CN/ 官方英文文档;

添加日志功能 新安装log4js模块用于管理系统日志.新增加日常中间件,并在middleware/inde.js中加载中间件. 运行将增加日志文件到当前目录. https://github.com/ethluz/koa2-apiserver-mvp/commit/fc90832a468a0162c3aa6c27a23afcc0a782463f

把日志中间件挂载到ctx&中间件的调用 https://github.com/ethluz/koa2-apiserver-mvp/commit/7a6083729041c71e26397fcef9ebda4a49b242a7

完善日志功能,按日期分割日志文件,并设置专有目录 https://github.com/ethluz/koa2-apiserver-mvp/commit/7195a00b4a0b176b8f58a155fcbcac7ebad0944d

灵活的日志配置及添加客户端信息到日志 https://github.com/ethluz/koa2-apiserver-mvp/commit/0204d238ebbead351db2c9bb92a2adb247953fb1 记录用户ip https://github.com/ethluz/koa2-apiserver-mvp/commit/36c4a66fc7eaae80b1cdda839fa51767c53799c8