Pyon Pyon Today

打造一个基于 Node.js 的 Web API 工程

2015-10-07 Web

最近被强上服务端,记录个创建过程(什么时候有个 IDE 能用(..•˘_˘•..))。流水帐警报。

此次目的是建立一个只提供 API 的服务器,所以前端什么的就不用管了,直接上 Node.js 搞。关于各项工具的进一步使用,建议顺着下面的链接查阅对应文档,总览如下:

  • 服务器:Express@4.12.3
  • 数据库:MongoDB@3.0.7
  • 测试框架:Jasmine@2.3.2
  • API 测试工具:hippie@0.4.0
  • 测试覆盖率报告:Istanbul@0.3.22
  • 工作流:Gulp@3.9.0
  • 版本管理:git@2.6.1(Github)
  • Lint:eslint@1.6.0
  • 编译(ES6 -> ES5):Babel@5.8.25
  • 源代码:

    • 程序:JavaScript(ES6)
    • 测试及其它:JavaScript(ES5)
  • 在线平台:

最终的项目目录结构看起来是这样子的:

.
├── build                  // ES6 代码编译输出目录
├── .codeclimate.yml       // Code Climate 配置
├── config.js              // 程序自己的配置
├── coverage               // Istanbul 生成的测试报告目录
├── .eslintrc              // ESLint 配置
├── .git                   // git 目录
├── gulpfile.js            // Gulp 配置
├── mongodb.conf           // MongoDB 配置
├── node_modules           // NPM 安装的依赖模块目录
├── package.json           // 程序自己的 NPM 包信息
├── data                   // MongoDB 数据库目录
│   └── mongodb
├── src                    // 源代码目录
│   └── app.js
├── test                   // 测试程序目录
│   └── spec
│       └── yahaloSpec.js
└── .travis.yml            // Travis CI 配置

创建 NPM package

先在 Github 创建新的仓库。

写一个 Node.js 程序的例行,给项目创建 package.json 以管理依赖。按照命令提示填写即可。

npm init

用 gulp 管理程序所有入口,在 pakcage.json 中 scripts 字段如下填写。

"scripts"{
    "test": "gulp",
    "dev": "gulp dev",
    "run": "gulp run"
}

编写 gulpfile.js

共用变量

将一些常用目录写在变量中,会比较容易管理。

var appSrc = 'src/**/*.js' // 程序源代码
var appDest = 'build/**/*.js' // 编译输出的文件
var appDestPath = 'build' // 编译输出目录
var testSrc = ['test/spec/*Spec.js'] // 测试程序源代码
var server = null // 用来保存 http 服务器实例,在启动服务器测试的时候

Lint

本人是直接用了 AirBnB 的 JS 规范去掉了 JSX 部分。选择自己喜欢的 .eslintrc 放在根目录就可以啦。

接着是在根目录编写 gulpfile.js,先是完成 lint 工作

var gulp = require('gulp')
var eslint = require('gulp-eslint')
 
gulp.task('lint', function () {
    return gulp
        .src(appSrc)
        .pipe(eslint({ rulePaths: ['./'] }))
        .pipe(eslint.format())
})

编译 ES6 代码

使用 Babel 将 ES6 的源代码编译到 CommonJS 规范的 ES5 代码,输出到 build 目录。

var babel = require('gulp-babel')
var newer = require('gulp-newer')
 
gulp.task('compile', function () {
    return gulp
        .src(appSrc)
        .pipe(newer(appDestPath))
        .pipe(babel({ modules: 'common' }))
        .pipe(gulp.dest(appDestPath))
})

启动/关闭服务器

通过 Gulp 来控制服务器的开关。这里利用前面创建的 server 这个变量储存服务器实例,保证只有一个实例运行。

gulp.task('serve', function (callback) {
    server = require('./build/app')
    callback()
})
 
gulp.task('end-serve', function (callback) {
    if (server) {
        server.close()
        server = null
    }
    callback()
})

测试

由于测试的是服务端程序,需要测试前先启动服务器,根据 gulp-stanbul 的说明,将任务分成以下两部分。在 pre-tsettest 任务之间启动服务器即可。

var jasmine = require('gulp-jasmine')
var SpecReporter = require('jasmine-spec-reporter')
var istanbul = require('gulp-istanbul')
 
gulp.task('pre-test', function () {
    return gulp.src(appDest).pipe(istanbul()).pipe(istanbul.hookRequire())
})
 
gulp.task('test', function () {
    return gulp
        .src(testSrc)
        .pipe(jasmine({ reporter: new SpecReporter() }))
        .on('end', function () {
            // 测试跑完关闭服务器
            server.close()
            server = null
        })
        .pipe(istanbul.writeReports())
})

监视

在开发的时候,让源代码改变的时候自动重新编译运行。而在测试程序改变的时候,重跑一遍测试。这里利用 run-sequence 来让一次而不是并行地执行 gulp 任务,在 Gulp 4.0 (参见 Migrating to gulp 4 by example - We Are Wizards Blog)中已经自带了 gulp.series 与 gulp.parallel 来控制执行次序。

var runSequence = require('run-sequence')
gulp.task('watcher-appSrc', function (callback) {
    runSequence('end-serve', 'compile', 'pre-test', 'serve', 'test', callback)
})
 
gulp.task('watcher-testSrc', function (callback) {
    runSequence('pre-test', 'test', callback)
})
 
gulp.task('watch', function (callback) {
    gulp.watch(appSrc, ['watcher-appSrc'])
    gulp.watch(testSrc, ['watcher-testSrc'])
    callback()
})

串接任务

将前面的任务串起来,分别创建用于 CI 的一次性测试、开发中持续监视与作为后端运行的三个最终使用的任务。

// once
gulp.task('default', function (callback) {
    runSequence(['compile', 'lint'], 'pre-test', 'serve', 'test', 'end-serve', callback)
})
 
// develop
gulp.task('dev', function (callback) {
    runSequence('compile', 'pre-test', 'serve', 'test', 'watch', callback)
})
 
// run server
gulp.task('run', function (callback) {
    runSequence('compile', 'serve', callback)
})

安装依赖

根据前面用到的包,以及服务端的需求,安装并保存依赖到 package.json 中去。

npm install --save express gulp mongodb mongoskin run-sequence
npm install --save-dev babel-eslint gulp-babel gulp-eslint gulp-istanbul gulp-jasmine jasmine-spec-reporter hippie gulp-newer

配置文件

用一个配置文件来保存程序配置,比如服务器端口号,创建在根目录 config.js

var config = {
    serverPort: 2333, // 服务器端口
    databaseURI: 'mongodb://localhost:27017', // MongoDB 数据库 URI
    dev: true, // 开发模式标志
}
 
module.exports = config

编写测试

先编写一个最简单的 GET 请求测试,文件为 test/spec/yahaloSpec.js,服务器端口就从配置中读取。

var hippie = require('hippie')
var port = require('../../config').serverPort
 
describe('yahalo Spec !', function () {
    it('should get 200 yooo', function (done) {
        hippie()
            .base('http://localhost:' + port)
            .get('/')
            .expectStatus(200)
            .expectBody('yahalo! GET!')
            .end(function (err, res, body) {
                if (err) done.fail(err)
                else done()
            })
    })
})

创建 MongoDB 配置

先为 MongoDB 创建数据库目录 data/mongodb。 在根目录添加 MongoDB 配置 mongodb.conf

# See http://www.mongodb.org/display/DOCS/File+Based+Configuration for format details
# Run mongod --help to see a list of options
 
port = 27017
bind_ip = 127.0.0.1
httpinterface = true
rest = true
quiet = false
dbpath = data/mongodb
logpath = data/mongod.log
logappend = true

编写服务端程序

写一个最简单的只会相应 GET 请求的程序(src/app.js),同时在启动的时候连接 MongoDB。

import express from 'express'
import mongoose from 'mongoose'
import config from '../config'
 
const app = express()
 
mongoose.connect(config.databaseURI, () => {
    if (config.dev) {
        // 在开发模式运行的时候,在一开始清空数据库
        mongoose.connection.db.dropDatabase()
    }
})
 
app.get('/', (req, res) => {
    res.send('yahalo! GET!')
})
 
const server = app.listen(config.serverPort, () => {
    console.log(`my app listening at http://localhost:${server.address().port}`)
})
 
server.on('close', () => {
    // 在关闭服务器的时候断开数据库连接
    mongoose.connection.close()
})
 
export default server

接下来在根目录执行 mongod -f mongodb.conf 启动数据库,然后直接运行 gulp,就能够看到命令行下输出的测试报告了,以及 istanbul 在 coverage 目录下生成的各种格式的报告(包括 html)。

整合 Code Climate 和 Travis CI

首先在两个平台都将项目的 Github 仓库添加上。在 Code Climate 那边选择 Engine analysis,根据提示步骤在根目录编写 Code Climate 配置文件 .codeclimate.yml

# 启用 eslint 
eslint:
    enabled: true
 
# 设定要进行评级的代码 
ratings:
    paths:
        - src/**/*.js

之后在 Code Climate 当前项目旁边的 Set Up Coverage 按钮上戳一下,在页面最底部获得用于 Travis CI 连接 Code Climate 上该项目用的 repo_token,将其写入根目录的 Travis CI 的配置文件 .travis.yml

addons:
    code_climate:
        repo_token: balabalabalashaaa

最后是接着编辑 Travis CI 的配置文件 .travis.yml,让 Travis CI 自动跑测试,同时报告测试覆盖率到 Code Climate:

# 指定程序语言 
language: node_js
 
# 指定 node 版本,“node” 为最新的稳定版本 
node_js:
    - 'node'
 
# 启用 MongoDB 
services:
    - mongodb
 
# 跑之前先安装依赖 
install:
    - npm install codeclimate-test-reporter
    - npm install
 
# 执行 gulp 直接开跑 
script: gulp
 
# 跑完报告测试覆盖率 
after_script:
    - codeclimate-test-reporter < coverage/lcov.info

至此,项目在 Github 上每遭到 push 一次,Travis CI 和 Code Climate 就会自动对你的代码进行测试并报告结果咯~关于 CI 还有很多的用途可以探索哟。