Giter Club home page Giter Club logo

koa2-skeleton's Introduction

Koa2-Skeleton

Using Koa2 to build Nodejs web server (without babel).

Install

  • Make sure your nodejs version > 7.6, since babel is removed.
  • Run npm install to get the necessary dependencies
  • Run npm start to start the server
  • Head over to http://localhost:3000

If your Node version is less than 7.6, but greater than 7.0, you can add this command --harmony_async_await to enable async/await feature.

Basic Feature

Contributing

Pull requests if you feel like anything is not good enough or missing out.

License

MIT

Koa2脚手架

使用 Koa2 来构建 Nodejs Web 应用(非babel编译)

安装使用

  • 无须Babel编译,确保你的Node版本不小于7.6
  • 使用npm install安装依赖
  • 使用npm start启动应用
  • 浏览器打开 http://localhost:3000

如果你的Node版本大于7.0小于7.6,可以添加 --harmony_async_await node参数开启async特性。

基本功能

开始使用

热更新

热更新策略(基于require.cache)开发中,现阶段推荐使用 node-dev,执行npm install -g node-dev安装,安装完后需要修改package.json的脚本,替换nodenode-dev

错误处理

得益于 async/await 强大的错误处理能力,使用类似于同步的方式进行错误处理,关键中间件为:middlewares/response.js

 try {
    await next();
    // 正常数据返回处理
  }
  catch(err) {
    // 异常数据返回处理
  }

使用中间件的错误捕捉,对执行顺序比较敏感,这个中间件放在controller逻辑的前面。 基于async/await这种方式只能捕获到在其上下文里的错误,其他错误由uncaughtexception处理。

返回数据协议约定

对于API这类数据返回,约定结构如下:

  • 返回码code:0表示成功,非0表示失败
  • 消息提示msg:返回码非0时消息提示
  • 数据包体data:数据payload

这个约定可以忽略,直接使用ctx.body也是没问题的。

遵循这个约定有一些方便的操作:

1. 错误抛出:

若想在业务逻辑里抛出用户级错误,可标记expose为true,结合koa2,可这样实现

ctx.throw('用户级错误abc', { expose: true, code: 9001 });

抛出错误后,接口返回{ code: 9001, msg: '用户级错误abc' }

2. 快捷构建返回包

为方便构建返回数据包,在ctx对象上新增3个对应字段ctx.resCodectx.resMsgctx.resData来替代ctx.body。 一些注意点:

  • 只赋值resData,响应内容为:{ code: 0, data: resData }
  • 只赋值resCode,且不为0,响应内容为:{ code: resCode, msg: ctx.resMsg || '系统繁忙,请稍后重试' }
  • libs/errors定义了一些通用的用户级错误返回码

3. 日志友好

赋值resCoderesMsg或者resBody可以使得log模块识别请求返回数据,并记录进日志里,方便还原用户场景,如:

2017-01-13T16:52:55+0800 <debug> visitlog.js(#47): fi4g0acifijjn10 - userResponse - 200 - 72.928ms - 36 - {"code":9001,"msg":"xxx error"}

数据验证

Joi.validateThrow是脚手架提供的工具函数,参数验证通过时,返回最终的数据对象;验证失败时抛出用户级错误。

let query = Joi.validateThrow(ctx.query, {
   pageNum: Joi.number().integer().min(1).default(1),
   pageSize: Joi.number().integer().min(1).max(100).default(10),
   fields: Joi.string().optional()
 });

测试

完善中...

日志使用

let logger = require('./libs/logger');
logger.debug('message');

共提供log、trace、debug、info、warn、error6个级别,默认输出级别设置为debug

日志格式为:

time - <level> - file(#line): reqid - xxx

reqid:用户请求ID,每次请求都会分配唯一的请求ID,存放在ctx.reqid

完整请求链路跟踪

ctx.reqid标记着每个请求的标志。

标记用户的请求进出使用middlewares/visitlog.js定制实现,参考morgan定制,格式如下:

// request
{time} - <{level}> - {file}(#{line}): {reqid} - userRequest - {method} - {path} - {remoteIp} - {referer} - {userAgent}
// response
{time} - <{level}> - {file}(#{line}): {reqid} - userReponse - {statusCode} - {responeTime} - {contentLength} - {content}

示例如下:

// request
2017-01-12T15:55:41+0800 <debug> visitlog.js(#32): hlsbupmbjmwpqc0 - userRequest - GET - /gfask/api - ::1 - ? - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36
// response
2017-01-12T15:55:41+0800 <debug> visitlog.js(#47): hlsbupmbjmwpqc0 - userResponse - 200 - 60.259ms - 56 - {"code":14,"msg":"内部接口错误,请稍候重试"}

标记子请求进出使用request-debug实现:

let reqIdsMap = {};
require('request-debug')(request, (type, data) => {
  if(type === 'request') {
    let {debugId, uri, method, body} = data;
    let reqid = data.headers.reqid || '?';
    reqIdsMap[debugId] = reqid;
    logger.debug(`${reqid} - libRequest - ${debugId} - ${uri}` + (body ? ( '-' + body) : ''));
  }
  else if(type === 'response') {
    let {debugId, statusCode, body} = data;
    let reqid = reqIdsMap[debugId] || '?';
    logger.debug(`${reqid} - libResponse - ${debugId} - ${statusCode} - ${JSON.stringify(body)}`);
    reqIdsMap[debugId] = null;
  }
});

如上代码所示,若希望每个子请求里都能加上这个标志,需要在字请求的header里添加reqid才生效,如:

const rp = require('request-promise');
let result = await rp({
  headers: {
    reqid: ctx.id
  },
  // ...
})

打印出来的日志还可以加上用户ID,请求自行处理。

路由

定义路由文件为:controllers/business/api.js

module.exports = router => {
  router.get('/list', async ctx => {
  ctx.body = 'hello world';
  })
}

那么访问路径:localhost:3000/business/api/list

更多强大路由功能参考lark-router的github(https://github.com/larkjs/lark-router)。

数据层

访问路径:controller -> service -> model

  • model: 封装不同接口,磨平不同接口的调用差异,对上层返回统一且是用户级的返回信息。
  • service: 某一功能的函数合集(例如订单等),这样的功能合集可能需要调用多个不同的model来联合完成。
  • controller: 业务逻辑层,通过调用service来获取数据能力,controller层不建议直接访问model层。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.