ECMAScript「新手向」koa2打起步到填坑

3 日志

狼叔koa-generator现已补充加了koa-logger,在app.js文本你得找到这么的代码:

const logger = require('koa-logger');
...
...
app.use(convert(logger()));

koa-loggertj大神写的koa开发时替换console.log输出的一个插件。

假诺你得以时间要依照文件大小,本地输出log文件的讲话,提议要拔取log4js-node

–recursive

mocha默认执行test目下之测试脚本,然则不会晤运行test生之子目录中的脚本。
回忆只要执行子目录中的测试脚本,可以在运行时加加--recursive参数。

$ mocha --recursive

使用supertest

我们针对现有的有数个API接口getUserregisterUser开展测试。在test目下开创user_api.test.js文件,内容如下:

const request = require('supertest');
const expect = require('chai').expect;
const app = require('../app.js');

describe('user_api', () => {

    it('getUser', (done) => {

        request(app.listen())
            .get('/api/users/getUser?id=1')     //get方法
            .expect(200)                        //断言状态码为200
            .end((err, res) => {

                console.log(res.body);
                //断言data属性是一个对象
                expect(res.body.data).to.be.an('object');

                done();
            });
    })

    it('registerUser', (done) => {

        // 请求参数,模拟用户对象
        var user = {
            username: '阿,希爸',
            age: 31
        }

        request(app.listen())
            .post('/api/users/registerUser')            //post方法
            .send(user)                                 //添加请求参数
            .set('Content-Type', 'application/json')    //设置header的Content-Type为json
            .expect(200)                                //断言状态码为200
            .end((err, res) => {

                console.log(res.body);
                //断言返回的code是0
                expect(res.body.code).to.be.equal(0);
                done();
            })
    })
})

一经现在直接运行npm test开展测试会报错,原因是mocha默认是勿援助async await语法,解决的计是Babel

Babel的严重性意图是针对不同版本的js举办转码。

假设你针对Babel无打听,请仔细看Babel
入门教程
Babel官网

由于koa-generator已经协助我们抬高相关的Babel仰,大家才待丰富相关的规则就是得了。在路的清目录下加加一个.babelrc文件,内容如下:

{
  "env": {
    "test": {
        "presets": ["es2015-node5"],
        "plugins": [
            "transform-async-to-generator",
            "syntax-async-functions"
        ]
    }
  }
}

随即段文件的意思是本着当env=test时,应用es2015-node5transform-async-to-generatorsyntax-async-functions规则举行转码。

Babel咱俩装好了,想假若mocha行使那规则还要以履时加加一个下令。
打开package.json,将scripts.test修改为:

"test": "NODE_ENV=test mocha --compilers js:babel-core/register",

每当极限履行npm test,输出如下内容表明测试通过。

  user_api
  <-- GET /api/users/getUser?id=1
  --> GET /api/users/getUser?id=1 200 14ms 74b
{ code: 0,
  message: 'success',
  data: { username: '阿,希爸', age: 30 } }
    ✓ getUser (57ms)
  <-- POST /api/users/registerUser
registerUser { username: '阿,希爸', age: 31 }
  --> POST /api/users/registerUser 200 2ms 30b
{ code: 0, message: 'success' }
    ✓ registerUser

有关supertest的再次多为此法请参考
github_supertest

假如用查看项目代码 –> 代码地址:

https://github.com/tough1985/hello-koa2
选择Tag -> step5

1.4.1 中间件的举办各样

koa的中级件是由generator组成的,这决定了中间件的施行顺序。
Express的高中级件是逐一执行,从第一只中等件执行及最后一个中档件,发出响应。

koa是于第一独中等件初阶实践,曰镪<code>next</code>进入下一个中等件,一贯举行到最终一个中档件,在逆序,执行上一个中级件<code>next</code>之后的代码,从来顶第一单中等件执行完毕才爆发响应。

2.1 当我们输入npm start的时节还干了数什么

package.json文件中

"scripts": {
    "start": "./node_modules/.bin/nodemon bin/run",
    "koa": "./node_modules/.bin/runkoa bin/www",
    "pm2": "pm2 start bin/run ",
    "test": "echo \"Error: no test specified\" && exit 1"
  }

足见到就片情节,当我们以终点输入:

$ npm start

于纵相会运作package.jsonscripts目的对应的start字段前面的始末,分外给您在极端输入:

$ ./node_modules/.bin/nodemon bin/run

nodemon插件的成效是当您启动了服务后,修改文件可以自行还开服务。
关于nodemon的又多内容 –>
nodemon

而不考虑自动还开功用,其实这词代码异常给实践了<code>node
bin/run</code>
咱得以望项目标bin目下,有一个run文件,代码如下:

#!/usr/bin/env node

var current_path = process.cwd();

require('runkoa')(current_path + '/bin/www' )

这边引入了一个runkoa,这些组件是狼叔描绘的koa2对babel环境因的一个装进插件。

关于runkoa相关内容表明 –>
runkoa。那里我们最终晤面执行bin目录下的www文件来启动服务。

4.4 对URL举行过滤

怎一定如果当router此前安装?
实则在router之后设置也得,可是要于controller里面执行<code>await
next()</code>才会调用。也就是说谁要格式化输出结果好手动调用。

在router前面设置也发一个题目,就是享有的路由响应输出都会师进展格式化输出,这彰着也无入预期,那么我们若对准URL进行过滤,通过过滤的才对他展开格式化处理。

双重改造一下response_formatter中等件,让他领一个参数,然后回来一个async
function做吧中等件。改造后的代码如下:

/**
 * 在app.use(router)之前调用
 */
var response_formatter = (ctx) => {
    //如果有返回数据,将返回数据添加到data中
    if (ctx.body) {
        ctx.body = {
            code: 0,
            message: 'success',
            data: ctx.body
        }
    } else {
        ctx.body = {
            code: 0,
            message: 'success'
        }
    }
}

var url_filter = function(pattern){

    return async function(ctx, next){
        var reg = new RegExp(pattern);
        //先去执行路由
        await next();
        //通过正则的url进行格式化处理
        if(reg.test(ctx.originalUrl)){
            response_formatter(ctx);
        }
    }
}
module.exports = url_filter;

app.js受到对应的代码改吗:

//仅对/api开头的url进行格式化处理
app.use(response_formatter('^/api'));

本作客127.0.0.1:3001/api/users/getUser如此这般因api先河的地址都会面举行格式化处理,而另外地方则未会晤。

–grep

万一你写了无数测试用例,当您上加了一个初的测试,执行后假设当结果中找半上。这种景色便可设想--grep参数。
--grep得就举行单个测试用例,也就是是推行某一个it。比如用方底测试修改如下:

describe('Array', function() {
    describe('#indexOf()', function() {
        it('should return -1 when the value is not present', function(){
            assert.equal(-1, [1,2,3].indexOf(4));
        })

        it('length', function(){
            assert.equal(3, [1, 2, 3].length);
        })
    })
});

补偿加了一个length测试用例,想只要独立实施此测试用例就假使在顶峰输入:

$ mocha --grep 'length'

足见见length用例被单独实施了。

此处来好几欲注意,因为我们安排了npm test,假若直接运行

$ npm test --grep 'length'

这样是匪可知达功用果的。

要给npm
scripts
本子传参需要先输入--然后以输入参数,所以想即便实施方的效果应输入:

$ npm test -- --grep 'length'

关于mocha即使简单的牵线这么多,想假如询问再多系的情节,推荐仔细读一全阮一峰先生写的测试框架
Mocha
实例教程

常用的参数

mocha在履时可带过多参数,这里介绍几单常因而的。

5.3 supertest

当前大家可利用测试框架做有简易的测试,想假诺测试接口的对应数额,就要以supertest了。

supertest重中之重意义就是对HTTP举办测试。尤其是对REST
API,我们针对get请求很命理术数,可是post方法就是死麻烦(当然你也可采用postman这样的插件)。

supertest可以学HTTP的各样求,设置header,添加请求数据,并针对性响应举行预言。

5.2 chai

chai举凡一个断言库。从前的事例中,我们以的是node提供的断言库,他的效用相比较少,基本上惟有equalokfail如此这般简单的力量,很麻烦满足平日的急需。

mocha法定代表您爱用啊断言用什么断言,反正老子都援助。

选择chai凡是因他针对断言的几栽语法都扶助,而且成效吗较完善 –>
chai官网

chai支持shouldexpectassert其三栽断言格局。

assert语法往日我们早就显现了了,chai单单是长了效果,语法并不曾转。
expectshould的语法更仿佛自然语言的习惯,不过should用的时刻会出现有奇怪的情景。所以于常用之或者expect

官方的DEMO

var expect = chai.expect;

expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.length(3);
expect(tea).to.have.property('flavors')
  .with.length(3);

显而易见语法的可读性更好,更近乎人类的语言。

简单的解释其中的tobe这么的语法。

chai接纳了链式语法,为了要语法更加类似自然语言,添加了诸多达语义可是尚未另外效能的词汇。

  • to
  • be
  • been
  • is
  • that
  • which
  • and
  • has
  • have
  • with
  • at
  • of
  • same

地方列有的这些歌词没有此外功能,只是为增进语义。

也就是说
expect(1+1).to.be.equal(2)

expect(1+1).equal(2)
举凡完全相同的。

3.3 先导化logs文件目录

优先来修改一下log_config.js文本,让后边的创办过程更舒心。

改后底代码:

var path = require('path');

//日志根目录
var baseLogPath = path.resolve(__dirname, '../logs')

//错误日志目录
var errorPath = "/error";
//错误日志文件名
var errorFileName = "error";
//错误日志输出完整路径
var errorLogPath = baseLogPath + errorPath + "/" + errorFileName;
// var errorLogPath = path.resolve(__dirname, "../logs/error/error");


//响应日志目录
var responsePath = "/response";
//响应日志文件名
var responseFileName = "response";
//响应日志输出完整路径
var responseLogPath = baseLogPath + responsePath + "/" + responseFileName;
// var responseLogPath = path.resolve(__dirname, "../logs/response/response");

module.exports = {
    "appenders":
    [
        //错误日志
        {
            "category":"errorLogger",             //logger名称
            "type": "dateFile",                   //日志类型
            "filename": errorLogPath,             //日志输出位置
            "alwaysIncludePattern":true,          //是否总是有后缀名
            "pattern": "-yyyy-MM-dd-hh.log",      //后缀,每小时创建一个新的日志文件
            "path": errorPath                     //自定义属性,错误日志的根目录
        },
        //响应日志
        {
            "category":"resLogger",
            "type": "dateFile",
            "filename": responseLogPath,
            "alwaysIncludePattern":true,
            "pattern": "-yyyy-MM-dd-hh.log",
            "path": responsePath  
        }
    ],
    "levels":                                   //设置logger名称对应的的日志等级
    {
        "errorLogger":"ERROR",
        "resLogger":"ALL"
    },
    "baseLogPath": baseLogPath                  //logs根目录
}

接下来打开bin/www文本,添加如下代码:

var fs = require('fs');
var logConfig = require('../config/log_config');

/**
 * 确定目录是否存在,如果不存在则创建目录
 */
var confirmPath = function(pathStr) {

  if(!fs.existsSync(pathStr)){
      fs.mkdirSync(pathStr);
      console.log('createPath: ' + pathStr);
    }
}

/**
 * 初始化log相关目录
 */
var initLogPath = function(){
  //创建log的根目录'logs'
  if(logConfig.baseLogPath){
    confirmPath(logConfig.baseLogPath)
    //根据不同的logType创建不同的文件目录
    for(var i = 0, len = logConfig.appenders.length; i < len; i++){
      if(logConfig.appenders[i].path){
        confirmPath(logConfig.baseLogPath + logConfig.appenders[i].path);
      }
    }
  }
}

initLogPath();

这么每一遍启动服务的时候,都相会失掉肯定一下有关的文件目录是否有,假若无有即创办连锁的文件目录。

兹于来启动服务。在浏览器访问,能够看到项目蒙大多矣logs目录以及相关子目录,并发出了生活文件。

情如下:

[2016-10-31 12:58:48.832] [INFO] resLogger - 
*************** response log start ***************
request method: GET
request originalUrl:  /
request client ip:  ::ffff:127.0.0.1
request query:  {}
response time: 418
response status: 200
response body: 
"<!DOCTYPE html><html><head><title>koa2 title</title><link rel=\"stylesheet\" href=\"/stylesheets/style.css\"></head><body><h1>koa2 title</h1><p>Welcome to koa2 title</p></body></html>"
*************** response log end ***************

得遵照自己之需求,定制相关的日志格式。

其余关于配置文件之抉择项可以参照log4js-node
Appenders说明

设若需要查看项目代码 –> 代码地址:

https://github.com/tough1985/hello-koa2
选择Tag -> step3

安装supertest

当终点输入:

$ npm install --save-dev supertest

1.1 安装koa-generator

以终端输入:

$ npm install -g koa-generator

4 格式化输出

假设我们本支出之凡一个API服务接口,会爆发一个联合之应格式,同时也指望发API错误时统一错误格式。

3.1 log4js

log4js提供了差不两个日志等级分类,同时为可以替换console.log输出,此外他尚可遵照文件大小或者日期来特别本地日志文件,仍可以够利用邮件等花样发送日志。

俺们于即时演示用infoerror星星种日志等级分别记录响应日志与错日志。

1.2 使用koa-generator生成koa2项目

以您的劳作目录下,输入:

$ koa2 HelloKoa2

得逞开创项目后,进入项目目录,并举办<code>npm
install</code>命令

$ cd HelloKoa2 
$ npm install

4.1 建立一个API接口

呢最近底劳务上加少独接口,一个getUser一个registerUser。

先在手上种下创立一个app/controllers目,在该目录下上加一个user_controller.js文件。

代码如下:

//获取用户
exports.getUser = async (ctx, next) => {
    ctx.body = {
        username: '阿,希爸',
        age: 30
    }
}

//用户注册
exports.registerUser = async (ctx, next) => {
    console.log('registerUser', ctx.request.body);
}

简言之的效仿一下。getUser再次回到一个user对象,registerUser只是打印输出一下伸手参数。

搭下去吗即片单道配置路由。

4.2 为API接口配置路由

咱俩目的在于服务之地方的咬合是及时如之

域名 + 端口号 /api/功能类型/具体端口

例如

127.0.0.1:3001/api/users/getUser

事先来补充加一个api的路由和另外路由于暌违管理。在routes目下创办一个api目录,添加user_router.js文件,代码如下:

var router = require('koa-router')();
var user_controller = require('../../app/controllers/user_controller');

router.get('/getUser', user_controller.getUser);
router.post('/registerUser', user_controller.registerUser);

module.exports = router;

诸如此类虽到位了getUserregisterUser展开了路由于安排,其中getUserGET术呼吁,registerUser是用POST道要。

对接下去对users斯功效模块举行路由配置,在routes/api目下加加一个index.js文件,代码如下:

var router = require('koa-router')();
var user_router = require('./user_router');

router.use('/users', user_router.routes(), user_router.allowedMethods());

module.exports = router;

最后对api开展路由配置,在app.js文本中上加如下代码:

const api = require('./routes/api');
......
router.use('/api', api.routes(), api.allowedMethods());

开行服务,在浏览器中做客127.0.0.1:3001/api/users/getUser好收获如下输出,表明配置成功。

{
  "username": "阿,希爸",
  "age": 30
}

1.4.2 async await语法协助

koa2增了<code>async</code>
<code>await</code>语法的援助.

本来koa的高中级件写法

app.use(function *(next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  this.set('X-Response-Time', ms + 'ms');
});

koa2中的写法

app.use(async (next) => {
  var start = new Date;
  await next();
  var ms = new Date - start;
  this.set('X-Response-Time', ms + 'ms');
});

koa阐明说假如在v3版本被撤消针对generator中间件的襄助,所以为了长久考虑仍然用async语法的好。
假如想要继承以<code>function*</code>语法,可以使用
<code>koa-convert</code>
这些当中件举办转换。这也是若见到项目蒙会起下代码的案由

const convert = require('koa-convert');

app.use(convert(bodyparser));
app.use(convert(json()));
app.use(convert(logger()));

1.3 启动项目

于极端输入:

$ npm start

类启动后,默认端口号是3000,在浏览器中运作能够收获下图的效应表明运行成功。

于此还感谢狼叔-桑世龙

眼前色之文件目录如下图

FileTree

5.1 mocha

使用chai

test目录下新建一个chai.test.js文本,内容如下:

const expect = require('chai').expect;

describe('chai expect demo', function() {
    it('expect equal', function() {
        expect(1+1).to.equal(2);
        expect(1+1).not.equal(3);
    });
});

当巅峰输入:

$ npm test -- --grep 'expect equal'

得输出:

  chai expect demo
    ✓ expect equal


  1 passing (6ms)

征配置成功。有关chai的又多职能要查看官方API –>
chai_api

1.4 关于koa2

2 项目安排

这边的部署指的凡运行环境的布局,比如咱们以开发阶段使用当地的数据库,测试要动用测试库,公布上线时候使用线上之库,也会发例外的捧口号。

2.3 配置环境

至于配置环境常用之发生development、test、production、debug。
可以拔取node提供的<code>process.env.NODE_ENV</code>来设置。

当起步服务之下可以本着NODE_ENV拓展赋值,例如:

$ NODE_ENV=test npm start 

接下来咱们可以于bin/www文件被输出一下,看看是否安排成功,添加如下代码:

console.log("process.env.NODE_ENV=" + process.env.NODE_ENV);

然后以巅峰输入

$ NODE_ENV=test npm start 

得看到终端打印:

process.env.NODE_ENV=test

大家得以scripts对象元帅环境布置好,例如我们拿starttest分别设置developmenttest条件,代码如下:

"scripts": {
    "start": "NODE_ENV=development ./node_modules/.bin/nodemon bin/run",
    "koa": "./node_modules/.bin/runkoa bin/www",
    "pm2": "pm2 start bin/run ",
    "test": "NODE_ENV=test echo \"Error: no test specified\" && exit 1",
    "start_koa": "bin/run"
},

足当极限分别输入<code>npm start</code>和<code>npm
test</code>来测试环境配置是否见效。

由并不曾测试内容,现在之test脚本会退出,后边我们在前述koa的测试。

安装mocha

当极端输入

$ npm install --save-dev mocha

–dev代表只是于development环境下加加依。

5 测试

node使用主流的测试框架基本就是是mochaAVA了,这里根本归因于mocha呢根基举行构建相关的测试。

4.5 API异常处理

尽管集中处理API万分,首先要成立一个API相当类,在app目录下新建一个error目录,添加ApiError.js文本,代码如下:

/**
 * 自定义Api异常
 */
class ApiError extends Error{

    //构造方法
    constructor(error_name, error_code,  error_message){
        super();
        this.name = error_name;
        this.code = error_code;
        this.message = error_message;
    }
}

module.exports = ApiError;

为了给从定义Api非常可以重新好的行使,大家创制一个ApiErrorNames.js文本来封装API分外音信,并可因此API错误名称获取很信息。代码如下:

/**
 * API错误名称
 */
var ApiErrorNames = {};

ApiErrorNames.UNKNOW_ERROR = "unknowError";
ApiErrorNames.USER_NOT_EXIST = "userNotExist";

/**
 * API错误名称对应的错误信息
 */
const error_map = new Map();

error_map.set(ApiErrorNames.UNKNOW_ERROR, { code: -1, message: '未知错误' });
error_map.set(ApiErrorNames.USER_NOT_EXIST, { code: 101, message: '用户不存在' });

//根据错误名称获取错误信息
ApiErrorNames.getErrorInfo = (error_name) => {

    var error_info;

    if (error_name) {
        error_info = error_map.get(error_name);
    }

    //如果没有对应的错误信息,默认'未知错误'
    if (!error_info) {
        error_name = UNKNOW_ERROR;
        error_info = error_map.get(error_name);
    }

    return error_info;
}

module.exports = ApiErrorNames;

修改ApiError.js文件,引入ApiErrorNames

ApiError.js

const ApiErrorNames = require('./ApiErrorNames');

/**
 * 自定义Api异常
 */
class ApiError extends Error{
    //构造方法
    constructor(error_name){
        super();

        var error_info = ApiErrorNames.getErrorInfo(error_name);

        this.name = error_name;
        this.code = error_info.code;
        this.message = error_info.message;
    }
}

module.exports = ApiError;

response_formatter.js文件被处理API卓殊。

先引入ApiError:
<code>var ApiError =
require(‘../app/error/ApiError’);</code>

接下来修改url_filter

var url_filter = (pattern) => {
    return async (ctx, next) => {
        var reg = new RegExp(pattern);
        try {
            //先去执行路由
            await next();
        } catch (error) {
            //如果异常类型是API异常并且通过正则验证的url,将错误信息添加到响应体中返回。
            if(error instanceof ApiError && reg.test(ctx.originalUrl)){
                ctx.status = 200;
                ctx.body = {
                    code: error.code,
                    message: error.message
                }
            }
            //继续抛,让外层中间件处理日志
            throw error;
        }

        //通过正则的url进行格式化处理
        if(reg.test(ctx.originalUrl)){
            response_formatter(ctx);
        }
    }
}

解释一下这段代码

  1. 运用<code>try catch</code>包裹<code>await
    next();</code>,这样后的高中级件抛来之良且可以即时几集中处理;

  2. <code>throw
    error;</code>是为着吃外层的logger中间件可以处理日志。

以仿效运行效果,我们修改user_controller.js文本,内容如下:

const ApiError = require('../error/ApiError');
const ApiErrorNames = require('../error/ApiErrorNames');
//获取用户
exports.getUser = async (ctx, next) => {
   //如果id != 1抛出API 异常
    if(ctx.query.id != 1){
        throw new ApiError(ApiErrorNames.USER_NOT_EXIST);
    }
    ctx.body = {
        username: '阿,希爸',
        age: 30
    }
}

启航服务,在浏览器中访问127.0.0.1:3001/api/users/getUser得赢得结果如下:

{
  "code": 101,
  "message": "用户不存在"
}

当浏览器被访问127.0.0.1:3001/api/users/getUser?id=1足得结果如下:

{
  "code": 0,
  "message": "success",
  "data": {
    "username": "阿,希爸",
    "age": 30
  }
}

要需要查看项目代码 –> 代码地址:

https://github.com/tough1985/hello-koa2
选择Tag -> step4

测试环境

前面说过环境布置的始末,我们得进行测试的时光,加载相关的测试配置该怎么开?

以极端输入

$ NODE_ENV=test mocha

为了避免每一次都失去输入NODE_ENV=test,可以改package.json文件被的scripts.test改为:

"test": "NODE_ENV=test mocha",

今后运行测试直接输入npm test尽管可了。

前传

由兴趣近来始于钻探koa2,由于事首暴发了有express经验,以为koa如故可怜好上手的,不过之所以起来发现尚是稍稍地点容易懵逼,由此整理此文,希望可以辅助到部分新人。

如您不懂javascript,指出您先去撸一百分之百红宝书javascript高级程序设计
苟你不熟知ES6,指出乃先失撸一全勤阮一峰先生的ECMAScript
6入门

盖自己吗是新娘,我只是整理了自家之读书经验,怎么样填平踩到的坑。

如果来读者发现自家来描绘错的地点希望你会顿时留言为自家,别叫我误导了此外新手。

正文的系列环境Mac OS
编译器 VScode

4.3 格式化输出

当一个API接口,我们或想统一重返格式,例如getUser的出口为客户端的重回值是这样的:

{
    "code": 0,
    "message": "成功",
    "data": {
      "username": "阿,希爸",
      "age": 30
    }
}

据koa的中间件执行各类,大家要拍卖数据应在发送响应在此之前和路途由于拿到数码之后上加一个中档件。在档次之绝望目录下加加一个middlewares目,在该目录下加加response_formatter.js文本,内容如下:

/**
 * 在app.use(router)之前调用
 */
var response_formatter = async (ctx, next) => {
    //先去执行路由
    await next();

    //如果有返回数据,将返回数据添加到data中
    if (ctx.body) {
        ctx.body = {
            code: 0,
            message: 'success',
            data: ctx.body
        }
    } else {
        ctx.body = {
            code: 0,
            message: 'success'
        }
    }
}

module.exports = response_formatter;

然后在app.js中载入。

const response_formatter = require('./middlewares/response_formatter');
...
//添加格式化处理响应结果的中间件,在添加路由之前调用
app.use(response_formatter);

router.use('/', index.routes(), index.allowedMethods());
router.use('/users', users.routes(), users.allowedMethods());
router.use('/api', api.routes(), api.allowedMethods());

app.use(router.routes(), router.allowedMethods());

启动服务,在浏览器中做客127.0.0.1:3001/api/users/getUser可以博得如下输出,表明配置成功。

{
  "code": 0,
  "message": "success",
  "data": {
    "username": "阿,希爸",
    "age": 30
  }
}

1 构建项目

思念选拔koa,大家必将首先想到去官网看看,没照生只guide之类的能轻松入门,不过koa官网暨koa本身一样简单。

万一只要自一点点搭建环境的言辞,感觉好烦,所以先去摸索了搜寻有无发出型生成器,然后就是意识了狼叔-桑世龙写的koa-generator

2.4 配置文件

为可以依据不同之运行条件加载不同之部署内容,大家用添加一些布置文件。
首先在路根本目录下加加config目录,在config目录下上加index.js、test.js、development.js六个文本,内容如下。

development.js

/**
 * 开发环境的配置内容
 */

module.exports = {
    env: 'development', //环境名称
    port: 3001,         //服务端口号
    mongodb_url: '',    //数据库地址
    redis_url:'',       //redis地址
    redis_port: ''      //redis端口号
}

test.js

/**
 * 测试环境的配置内容
 */

module.exports = {
    env: 'test',        //环境名称
    port: 3002,         //服务端口号
    mongodb_url: '',    //数据库地址
    redis_url:'',       //redis地址
    redis_port: ''      //redis端口号
}

index.js

var development_env = require('./development');
var test_env = require('./test');

//根据不同的NODE_ENV,输出不同的配置对象,默认输出development的配置对象
module.exports = {
    development: development_env,
    test: test_env
}[process.env.NODE_ENV || 'development']

代码应该都没什么可说的,然后大家更来编排bin/www文件。

bin/www增长如下代码

//引入配置文件
var config = require('../config');

// 将端口号设置为配置文件的端口号,默认值为3000
var port = normalizePort(config.port || '3000');
// 打印输出端口号
console.log('port = ' + config.port);

测试效果,在巅峰输入<code>npm start</code>,可以看来

process.env.NODE_ENV=development
port = 3001

顶浏览器被走访http://127.0.0.1:3001,可以看本的输入内容,表达配置文件都生效。

若用查看项目代码 –> 代码地址:

https://github.com/tough1985/hello-koa2
选择Tag -> step2

2.2 npm scripts

我们在scripts目的中上加同段子代码"start_koa": "bin/run",修改后scripts对象的情节如下:

"scripts": {
    "start": "./node_modules/.bin/nodemon bin/run",
    "koa": "./node_modules/.bin/runkoa bin/www",
    "pm2": "pm2 start bin/run ",
    "test": "echo \"Error: no test specified\" && exit 1",
    "start_koa": "bin/run"
  }

这就是说既然输入<code>npm
start</code>执行start后边的台本,聪明之公势必会想念:是匪是自家输入<code>npm
start_koa</code>就可以实施start_koa前边相关的代码了也?
随便你是怎么想的,反正自己立即虽想的如此天真。
其实我们输入<code>npm
start_koa</code>之后,终端会指示npm尚未有关的授命。
那么在scripts中的start_koa命要怎么用也,其实要加以一个run一声令下才可以举行,在终端输入:

$ npm run start_koa

得视服务正常运作了。

npm备受,有四单常因而之缩写

npm start是npm run start
npm stop是npm run stop的简写
npm test是npm run test的简写
npm restart是npm run stop && npm run restart && npm run start的简写

另的还设用<code>npm run</code>来执行了。

推介读一全副阮一峰先生写的npm scripts
使用指南
,很有援救。

安装chai

当终点输入:

$ npm install --save-dev chai

3.2 log4js 配置

config目录下创立一个log_config.js文本,内容如下:

var path = require('path');

//错误日志输出完整路径
var errorLogPath = path.resolve(__dirname, "../logs/error/error");

//响应日志输出完整路径
var responseLogPath = path.resolve(__dirname, "../logs/response/response");

module.exports = {
    "appenders":
    [
        //错误日志
        {
            "category":"errorLogger",             //logger名称
            "type": "dateFile",                   //日志类型
            "filename": errorLogPath,             //日志输出位置
            "alwaysIncludePattern":true,          //是否总是有后缀名
            "pattern": "-yyyy-MM-dd-hh.log"       //后缀,每小时创建一个新的日志文件
        },
        //响应日志
        {
            "category":"resLogger",
            "type": "dateFile",
            "filename": responseLogPath,
            "alwaysIncludePattern":true,
            "pattern": "-yyyy-MM-dd-hh.log"
        }
    ],
    "levels":                                     //设置logger名称对应的的日志等级
    {
        "errorLogger":"ERROR",
        "resLogger":"ALL"
    }
}

接下来创制一个utils目录,添加log_util.js文本,内容如下:

var log4js = require('log4js');

var log_config = require('../config/log_config');

//加载配置文件
log4js.configure(log_config);

var logUtil = {};

var errorLogger = log4js.getLogger('errorLogger');
var resLogger = log4js.getLogger('resLogger');

//封装错误日志
logUtil.logError = function (ctx, error, resTime) {
    if (ctx && error) {
        errorLogger.error(formatError(ctx, error, resTime));
    }
};

//封装响应日志
logUtil.logResponse = function (ctx, resTime) {
    if (ctx) {
        resLogger.info(formatRes(ctx, resTime));
    }
};

//格式化响应日志
var formatRes = function (ctx, resTime) {
    var logText = new String();

    //响应日志开始
    logText += "\n" + "*************** response log start ***************" + "\n";

    //添加请求日志
    logText += formatReqLog(ctx.request, resTime);

    //响应状态码
    logText += "response status: " + ctx.status + "\n";

    //响应内容
    logText += "response body: " + "\n" + JSON.stringify(ctx.body) + "\n";

    //响应日志结束
    logText += "*************** response log end ***************" + "\n";

    return logText;

}

//格式化错误日志
var formatError = function (ctx, err, resTime) {
    var logText = new String();

    //错误信息开始
    logText += "\n" + "*************** error log start ***************" + "\n";

    //添加请求日志
    logText += formatReqLog(ctx.request, resTime);

    //错误名称
    logText += "err name: " + err.name + "\n";
    //错误信息
    logText += "err message: " + err.message + "\n";
    //错误详情
    logText += "err stack: " + err.stack + "\n";

    //错误信息结束
    logText += "*************** error log end ***************" + "\n";

    return logText;
};

//格式化请求日志
var formatReqLog = function (req, resTime) {

    var logText = new String();

    var method = req.method;
    //访问方法
    logText += "request method: " + method + "\n";

    //请求原始地址
    logText += "request originalUrl:  " + req.originalUrl + "\n";

    //客户端ip
    logText += "request client ip:  " + req.ip + "\n";

    //开始时间
    var startTime;
    //请求参数
    if (method === 'GET') {
        logText += "request query:  " + JSON.stringify(req.query) + "\n";
        // startTime = req.query.requestStartTime;
    } else {
        logText += "request body: " + "\n" + JSON.stringify(req.body) + "\n";
        // startTime = req.body.requestStartTime;
    }
    //服务器响应时间
    logText += "response time: " + resTime + "\n";

    return logText;
}

module.exports = logUtil;

对接下修改app.js 文件被的logger部分。

//log工具
const logUtil = require('./utils/log_util');


// logger
app.use(async (ctx, next) => {
  //响应开始时间
  const start = new Date();
  //响应间隔时间
  var ms;
  try {
    //开始进入到下一个中间件
    await next();

    ms = new Date() - start;
    //记录响应日志
    logUtil.logResponse(ctx, ms);

  } catch (error) {

    ms = new Date() - start;
    //记录异常日志
    logUtil.logError(ctx, error, ms);
  }
});

当即时将<code>await next();</code>放到了一个<code>try
catch</code>里面,这样后的中级件有很都足以当及时集中处理。

按部就班您会拿部分API相当作为正常值再次来到给客户端,就足以以立刻集中开展拍卖。然后后边的中级件如<code>throw</code>自定义之API十分就可了。

在启动服务在此以前毫无忘记先安装log4js插件:

$ npm install log4js --save

起初服务

$ npm start

此时会启动失利,控制台会输出没有公文或者文件目录。原因是我们当布局中即使配置了文件目录,不过并从未开创连锁目录,解决的形式是手动创立连锁目录,或者在服务启动的时,确认一下索引是否有,假使未有则创制连锁目录。

1.4.3 Context

Context封装了node中的request和response。

koa@1.x使用this引用Context对象:

app.use(function *(){
  this.body = 'Hello World';
});

koa@2.x中使用ctx来访问Context对象:

app.use(async (ctx, next) => {
  await next();
  ctx.body = 'Hello World';
});

下边代码中之<code>ctx.body = ‘Hello
World'</code>那行代码表示设置response.body的价为’Hello World’。

而你看文档就有或懵逼,那么我发送post告的参数应该怎么抱呢?
诚如ctx不可知一贯拿走request的body,想假如落post恳请被的参数要接纳<code>ctx.request.body</code>。

假设得查看项目代码 –> 代码地址:

https://github.com/tough1985/hello-koa2
选择Tag -> step1

使用mocha

于类型的干净目录下上加test目录,添加一个test.js文本,内容如下:

var assert = require('assert');
/**
 * describe 测试套件 test suite 表示一组相关的测试
 * it 测试用例 test case 表示一个单独的测试
 * assert 断言 表示对结果的预期
 */
describe('Array', function() {
    describe('#indexOf()', function() {
        it('should return -1 when the value is not present', function(){
            assert.equal(-1, [1,2,3].indexOf(4));
        })
    })
});

在终端输入:

$ mocha

得取得输出如下:

  Array
    #indexOf()
      ✓ should return -1 when the value is not present


  1 passing (9ms)

mocha默认运行test目下之测试文件,测试文件一般和如测试的步履文件同名以<code>.test.js</code>作为后缀名。例如add.js的测试脚论名字便add.test.js

describe表示测试套件,每个测试脚论至少该包含一个<code>describe</code>。

it表示测试用例。

每个describe可以分包六只describe或多个it

assert凡node提供的断言库。

assert.equal(-1, [1,2,3].indexOf(4));

立时词代码的意是大家期望[1,2,3].indexOf(4)的价应是-1,如果[1,2,3].indexOf(4)的运转结果是-1,则经过测试,否则不经。

可以把-1改成-2再试一下。

方的例子是mocha提供的,mocha官网