前言

这篇文章算是对Building APIs with Node.js这本书的一个总结。用Node.js写接口对我来说是很有用的,比如在项目初始阶段,可以快速的模拟网络请求。正因为它用js写的,跟iOS直接的联系也比其他语言写的后台更加接近。

这本书写的极好,作者编码的思路极其清晰,整本书虽说是用英文写的,但很容易读懂。同时,它完整的构建了RESTful API的一整套逻辑。

我更加喜欢写一些函数响应式的程序,把函数当做数据或参数进行传递对我有着莫大的吸引力。

从程序的搭建,到设计错误捕获机制,再到程序的测试任务,这是一个完整的过程。这边文章将会很长,我会把每个核心概念的代码都黏贴上来。

环境搭建

下载并安装Node.jshttps://nodejs.org/en/

安装npm

下载演示项目

  1. git clone https://github.com/agelessman/ntask-api

进入项目文件夹后运行

  1. npm install

上边命令会下载项目所需的插件,然后启动项目

  1. npm start

访问接口文档

  1. http://localhost:3000/apidoc

程序入口

Express这个框架大家应该都知道,他提供了很丰富的功能,我在这就不做解释了,先看该项目中的代码:

  1. import express from "express"
  2. import consign from "consign"
  3. const app = express();
  4. /// 在使用include或者then的时候,是有顺序的,如果传入的参数是一个文件夹
  5. /// 那么他会按照文件夹中文件的顺序进行加载
  6. consign({verbose: false})
  7. .include("libs/config.js")
  8. .then("db.js")
  9. .then("auth.js")
  10. .then("libs/middlewares.js")
  11. .then("routers")
  12. .then("libs/boot.js")
  13. .into(app);
  14. module.exports = app;

不管是models,views还是routers都会经过Express的加工和配置。在该项目中并没有使用到views的地方。Express通过app对整个项目的功能进行配置,但我们不能把所有的参数和方法都写到这一个文件之中,否则当项目很大的时候将急难维护。

我使用Node.js的经验是很少的,但上面的代码给我的感觉就是极其简洁,思路极其清晰,通过consign 这个模块导入其他模块在这里就让代码显得很优雅。

@note:导入的顺序很重要。

在这里,app的使用很像一个全局变量,这个我们会在下边的内容中展示出来,按序导入后,我们就可以通过这样的方式访问模块的内容了:、

  1. app.db
  2. app.auth
  3. app.libs....

模型设计

在我看来,在开始做任何项目前,需求分析是最重要的,经过需求分析后,我们会有一个关于代码设计的大的概念。

编码的实质是什么?我认为就是数据的存储和传递,同时还需要考虑性能和安全的问题

因此我们第二部的任务就是设计数据模型,同时可以反应出我们需求分析的成果。在该项目中有两个模型,UserTask,每一个task对应一个user,一个user可以有多个task

用户模型:

  1. import bcrypt from "bcrypt"
  2. module.exports = (sequelize, DataType) => {
  3. "use strict";
  4. const Users = sequelize.define("Users", {
  5. id: {
  6. type: DataType.INTEGER,
  7. primaryKey: true,
  8. autoIncrement: true
  9. },
  10. name: {
  11. type: DataType.STRING,
  12. allowNull: false,
  13. validate: {
  14. notEmpty: true
  15. }
  16. },
  17. password: {
  18. type: DataType.STRING,
  19. allowNull: false,
  20. validate: {
  21. notEmpty: true
  22. }
  23. },
  24. email: {
  25. type: DataType.STRING,
  26. unique: true,
  27. allowNull: false,
  28. validate: {
  29. notEmpty: true
  30. }
  31. }
  32. }, {
  33. hooks: {
  34. beforeCreate: user => {
  35. const salt = bcrypt.genSaltSync();
  36. user.password = bcrypt.hashSync(user.password, salt);
  37. }
  38. }
  39. });
  40. Users.associate = (models) => {
  41. Users.hasMany(models.Tasks);
  42. };
  43. Users.isPassword = (encodedPassword, password) => {
  44. return bcrypt.compareSync(password, encodedPassword);
  45. };
  46. return Users;
  47. };

任务模型:

  1. module.exports = (sequelize, DataType) => {
  2. "use strict";
  3. const Tasks = sequelize.define("Tasks", {
  4. id: {
  5. type: DataType.INTEGER,
  6. primaryKey: true,
  7. autoIncrement: true
  8. },
  9. title: {
  10. type: DataType.STRING,
  11. allowNull: false,
  12. validate: {
  13. notEmpty: true
  14. }
  15. },
  16. done: {
  17. type: DataType.BOOLEAN,
  18. allowNull: false,
  19. defaultValue: false
  20. }
  21. });
  22. Tasks.associate = (models) => {
  23. Tasks.belongsTo(models.Users);
  24. };
  25. return Tasks;
  26. };

该项目中使用了系统自带的sqlite作为数据库,当然也可以使用其他的数据库,这里不限制是关系型的还是非关系型的。为了更好的管理数据,我们使用sequelize这个模块来管理数据库。

为了节省篇幅,这些模块我就都不介绍了,在google上一搜就出来了。在我看的Node.js的开发中,这种ORM的管理模块有很多,比如说对MongoDB进行管理的mongoose。很多很多,他们主要的思想就是Scheme。

在上边的代码中,我们定义了模型的输出和输入模板,同时对某些特定的字段进行了验证,因此在使用的过程中就有可能会产生来自数据库的错误,这些错误我们会在下边讲解到。

  1. Tasks.associate = (models) => {
  2. Tasks.belongsTo(models.Users);
  3. };
  4. Users.associate = (models) => {
  5. Users.hasMany(models.Tasks);
  6. };
  7. Users.isPassword = (encodedPassword, password) => {
  8. return bcrypt.compareSync(password, encodedPassword);
  9. };

hasManybelongsTo表示一种关联属性,Users.isPassword算是一个类方法。bcrypt 模块可以对密码进行加密编码。

数据库

在上边我们已经知道了,我们使用sequelize模块来管理数据库。其实,在最简单的层面而言,数据库只需要给我们数据模型就行了,我们拿到这些模型后,就能够根据不同的需求,去完成各种各样的CRUD操作。

  1. import fs from "fs"
  2. import path from "path"
  3. import Sequelize from "sequelize"
  4. let db = null;
  5. module.exports = app => {
  6. "use strict";
  7. if (!db) {
  8. const config = app.libs.config;
  9. const sequelize = new Sequelize(
  10. config.database,
  11. config.username,
  12. config.password,
  13. config.params
  14. );
  15. db = {
  16. sequelize,
  17. Sequelize,
  18. models: {}
  19. };
  20. const dir = path.join(__dirname, "models");
  21. fs.readdirSync(dir).forEach(file => {
  22. const modelDir = path.join(dir, file);
  23. const model = sequelize.import(modelDir);
  24. db.models[model.name] = model;
  25. });
  26. Object.keys(db.models).forEach(key => {
  27. db.models[key].associate(db.models);
  28. });
  29. }
  30. return db;
  31. };

上边的代码很简单,db是一个对象,他存储了所有的模型,在这里是UserTask。通过sequelize.import获取模型,然后又调用了之前写好的associate方法。

上边的函数调用之后呢,返回db,db中有我们需要的模型,到此为止,我们就建立了数据库的联系,作为对后边代码的一个支撑。

CRUD

CRUD在router中,我们先看看router/tasks.js的代码:

  1. module.exports = app => {
  2. "use strict";
  3. const Tasks = app.db.models.Tasks;
  4. app.route("/tasks")
  5. .all(app.auth.authenticate())
  6. .get((req, res) => {
  7. console.log(`req.body: ${req.body}`);
  8. Tasks.findAll({where: {user_id: req.user.id} })
  9. .then(result => res.json(result))
  10. .catch(error => {
  11. res.status(412).json({msg: error.message});
  12. });
  13. })
  14. .post((req, res) => {
  15. req.body.user_id = req.user.id;
  16. Tasks.create(req.body)
  17. .then(result => res.json(result))
  18. .catch(error => {
  19. res.status(412).json({msg: error.message});
  20. });
  21. });
  22. app.route("/tasks/:id")
  23. .all(app.auth.authenticate())
  24. .get((req, res) => {
  25. Tasks.findOne({where: {
  26. id: req.params.id,
  27. user_id: req.user.id
  28. }})
  29. .then(result => {
  30. if (result) {
  31. res.json(result);
  32. } else {
  33. res.sendStatus(412);
  34. }
  35. })
  36. .catch(error => {
  37. res.status(412).json({msg: error.message});
  38. });
  39. })
  40. .put((req, res) => {
  41. Tasks.update(req.body, {where: {
  42. id: req.params.id,
  43. user_id: req.user.id
  44. }})
  45. .then(result => res.sendStatus(204))
  46. .catch(error => {
  47. res.status(412).json({msg: error.message});
  48. });
  49. })
  50. .delete((req, res) => {
  51. Tasks.destroy({where: {
  52. id: req.params.id,
  53. user_id: req.user.id
  54. }})
  55. .then(result => res.sendStatus(204))
  56. .catch(error => {
  57. res.status(412).json({msg: error.message});
  58. });
  59. });
  60. };

再看看router/users.js的代码:

  1. module.exports = app => {
  2. "use strict";
  3. const Users = app.db.models.Users;
  4. app.route("/user")
  5. .all(app.auth.authenticate())
  6. .get((req, res) => {
  7. Users.findById(req.user.id, {
  8. attributes: ["id", "name", "email"]
  9. })
  10. .then(result => res.json(result))
  11. .catch(error => {
  12. res.status(412).json({msg: error.message});
  13. });
  14. })
  15. .delete((req, res) => {
  16. console.log(`delete..........${req.user.id}`);
  17. Users.destroy({where: {id: req.user.id}})
  18. .then(result => {
  19. console.log(`result: ${result}`);
  20. return res.sendStatus(204);
  21. })
  22. .catch(error => {
  23. console.log(`resultfsaddfsf`);
  24. res.status(412).json({msg: error.message});
  25. });
  26. });
  27. app.post("/users", (req, res) => {
  28. Users.create(req.body)
  29. .then(result => res.json(result))
  30. .catch(error => {
  31. res.status(412).json({msg: error.message});
  32. });
  33. });
  34. };

这些路由写起来比较简单,上边的代码中,基本思想就是根据模型操作CRUD,包括捕获异常。但是额外的功能是做了authenticate,也就是授权操作。

这一块好像没什么好说的,基本上都是固定套路。

授权

在网络环境中,不能老是传递用户名和密码。这时候就需要一些授权机制,该项目中采用的是JWT授权(JSON Wbb Toknes),有兴趣的同学可以去了解下这个授权,它也是按照一定的规则生成token。

因此对于授权而言,最核心的部分就是如何生成token。

  1. import jwt from "jwt-simple"
  2. module.exports = app => {
  3. "use strict";
  4. const cfg = app.libs.config;
  5. const Users = app.db.models.Users;
  6. app.post("/token", (req, res) => {
  7. const email = req.body.email;
  8. const password = req.body.password;
  9. if (email && password) {
  10. Users.findOne({where: {email: email}})
  11. .then(user => {
  12. if (Users.isPassword(user.password, password)) {
  13. const payload = {id: user.id};
  14. res.json({
  15. token: jwt.encode(payload, cfg.jwtSecret)
  16. });
  17. } else {
  18. res.sendStatus(401);
  19. }
  20. })
  21. .catch(error => res.sendStatus(401));
  22. } else {
  23. res.sendStatus(401);
  24. }
  25. });
  26. };

上边代码中,在得到邮箱和密码后,再使用jwt-simple模块生成一个token。

JWT在这也不多说了,它由三部分组成,这个在它的官网中解释的很详细。

我觉得老外写东西一个最大的优点就是文档很详细。要想弄明白所有组件如何使用,最好的方法就是去他们的官网看文档,当然这要求英文水平还可以。

授权一般分两步:

  • 生成token
  • 验证token

如果从前端传递一个token过来,我们怎么解析这个token,然后获取到token里边的用户信息呢?

  1. import passport from "passport";
  2. import {Strategy, ExtractJwt} from "passport-jwt";
  3. module.exports = app => {
  4. const Users = app.db.models.Users;
  5. const cfg = app.libs.config;
  6. const params = {
  7. secretOrKey: cfg.jwtSecret,
  8. jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken()
  9. };
  10. var opts = {};
  11. opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme("JWT");
  12. opts.secretOrKey = cfg.jwtSecret;
  13. const strategy = new Strategy(opts, (payload, done) => {
  14. Users.findById(payload.id)
  15. .then(user => {
  16. if (user) {
  17. return done(null, {
  18. id: user.id,
  19. email: user.email
  20. });
  21. }
  22. return done(null, false);
  23. })
  24. .catch(error => done(error, null));
  25. });
  26. passport.use(strategy);
  27. return {
  28. initialize: () => {
  29. return passport.initialize();
  30. },
  31. authenticate: () => {
  32. return passport.authenticate("jwt", cfg.jwtSession);
  33. }
  34. };
  35. };

这就用到了passportpassport-jwt这两个模块。passport支持很多种授权。不管是iOS还是Node中,验证都需要指定一个策略,这个策略是最灵活的一层。

授权需要在项目中提前进行配置,也就是初始化,app.use(app.auth.initialize());

如果我们想对某个接口进行授权验证,那么只需要像下边这么用就可以了:

  1. .all(app.auth.authenticate())
  2. .get((req, res) => {
  3. console.log(`req.body: ${req.body}`);
  4. Tasks.findAll({where: {user_id: req.user.id} })
  5. .then(result => res.json(result))
  6. .catch(error => {
  7. res.status(412).json({msg: error.message});
  8. });
  9. })

配置

Node.js中一个很有用的思想就是middleware,我们可以利用这个手段做很多有意思的事情:

  1. import bodyParser from "body-parser"
  2. import express from "express"
  3. import cors from "cors"
  4. import morgan from "morgan"
  5. import logger from "./logger"
  6. import compression from "compression"
  7. import helmet from "helmet"
  8. module.exports = app => {
  9. "use strict";
  10. app.set("port", 3000);
  11. app.set("json spaces", 4);
  12. console.log(`err ${JSON.stringify(app.auth)}`);
  13. app.use(bodyParser.json());
  14. app.use(app.auth.initialize());
  15. app.use(compression());
  16. app.use(helmet());
  17. app.use(morgan("common", {
  18. stream: {
  19. write: (message) => {
  20. logger.info(message);
  21. }
  22. }
  23. }));
  24. app.use(cors({
  25. origin: ["http://localhost:3001"],
  26. methods: ["GET", "POST", "PUT", "DELETE"],
  27. allowedHeaders: ["Content-Type", "Authorization"]
  28. }));
  29. app.use((req, res, next) => {
  30. // console.log(`header: ${JSON.stringify(req.headers)}`);
  31. if (req.body && req.body.id) {
  32. delete req.body.id;
  33. }
  34. next();
  35. });
  36. app.use(express.static("public"));
  37. };

上边的代码中包含了很多新的模块,app.set表示进行设置,app.use表示使用middleware。

测试

写测试代码是我平时很容易疏忽的地方,说实话,这么重要的部分不应该被忽视。

  1. import jwt from "jwt-simple"
  2. describe("Routes: Users", () => {
  3. "use strict";
  4. const Users = app.db.models.Users;
  5. const jwtSecret = app.libs.config.jwtSecret;
  6. let token;
  7. beforeEach(done => {
  8. Users
  9. .destroy({where: {}})
  10. .then(() => {
  11. return Users.create({
  12. name: "Bond",
  13. email: "Bond@mc.com",
  14. password: "123456"
  15. });
  16. })
  17. .then(user => {
  18. token = jwt.encode({id: user.id}, jwtSecret);
  19. done();
  20. });
  21. });
  22. describe("GET /user", () => {
  23. describe("status 200", () => {
  24. it("returns an authenticated user", done => {
  25. request.get("/user")
  26. .set("Authorization", `JWT ${token}`)
  27. .expect(200)
  28. .end((err, res) => {
  29. expect(res.body.name).to.eql("Bond");
  30. expect(res.body.email).to.eql("Bond@mc.com");
  31. done(err);
  32. });
  33. });
  34. });
  35. });
  36. describe("DELETE /user", () => {
  37. describe("status 204", () => {
  38. it("deletes an authenticated user", done => {
  39. request.delete("/user")
  40. .set("Authorization", `JWT ${token}`)
  41. .expect(204)
  42. .end((err, res) => {
  43. console.log(`err: ${err}`);
  44. done(err);
  45. });
  46. });
  47. });
  48. });
  49. describe("POST /users", () => {
  50. describe("status 200", () => {
  51. it("creates a new user", done => {
  52. request.post("/users")
  53. .send({
  54. name: "machao",
  55. email: "machao@mc.com",
  56. password: "123456"
  57. })
  58. .expect(200)
  59. .end((err, res) => {
  60. expect(res.body.name).to.eql("machao");
  61. expect(res.body.email).to.eql("machao@mc.com");
  62. done(err);
  63. });
  64. });
  65. });
  66. });
  67. });

测试主要依赖下边的这几个模块:

  1. import supertest from "supertest"
  2. import chai from "chai"
  3. import app from "../index"
  4. global.app = app;
  5. global.request = supertest(app);
  6. global.expect = chai.expect;

其中supertest用来发请求的,chai用来判断是否成功。

使用mocha测试框架来进行测试:

  1. "test": "NODE_ENV=test mocha test/**/*.js",

生成接口文档

接口文档也是很重要的一个环节,该项目使用的是ApiDoc.js。这个没什么好说的,直接上代码:

  1. /**
  2. * @api {get} /tasks List the user's tasks
  3. * @apiGroup Tasks
  4. * @apiHeader {String} Authorization Token of authenticated user
  5. * @apiHeaderExample {json} Header
  6. * {
  7. * "Authorization": "xyz.abc.123.hgf"
  8. * }
  9. * @apiSuccess {Object[]} tasks Task list
  10. * @apiSuccess {Number} tasks.id Task id
  11. * @apiSuccess {String} tasks.title Task title
  12. * @apiSuccess {Boolean} tasks.done Task is done?
  13. * @apiSuccess {Date} tasks.updated_at Update's date
  14. * @apiSuccess {Date} tasks.created_at Register's date
  15. * @apiSuccess {Number} tasks.user_id The id for the user's
  16. * @apiSuccessExample {json} Success
  17. * HTTP/1.1 200 OK
  18. * [{
  19. * "id": 1,
  20. * "title": "Study",
  21. * "done": false,
  22. * "updated_at": "2016-02-10T15:46:51.778Z",
  23. * "created_at": "2016-02-10T15:46:51.778Z",
  24. * "user_id": 1
  25. * }]
  26. * @apiErrorExample {json} List error
  27. * HTTP/1.1 412 Precondition Failed
  28. */
  29. /**
  30. * @api {post} /users Register a new user
  31. * @apiGroup User
  32. * @apiParam {String} name User name
  33. * @apiParam {String} email User email
  34. * @apiParam {String} password User password
  35. * @apiParamExample {json} Input
  36. * {
  37. * "name": "James",
  38. * "email": "James@mc.com",
  39. * "password": "123456"
  40. * }
  41. * @apiSuccess {Number} id User id
  42. * @apiSuccess {String} name User name
  43. * @apiSuccess {String} email User email
  44. * @apiSuccess {String} password User encrypted password
  45. * @apiSuccess {Date} update_at Update's date
  46. * @apiSuccess {Date} create_at Rigister's date
  47. * @apiSuccessExample {json} Success
  48. * {
  49. * "id": 1,
  50. * "name": "James",
  51. * "email": "James@mc.com",
  52. * "updated_at": "2016-02-10T15:20:11.700Z",
  53. * "created_at": "2016-02-10T15:29:11.700Z"
  54. * }
  55. * @apiErrorExample {json} Rergister error
  56. * HTTP/1.1 412 Precondition Failed
  57. */

大概就类似与上边的样子,既可以做注释用,又可以自动生成文档,一石二鸟,我就不上图了。

准备发布

到了这里,就只剩下发布前的一些操作了,

有的时候,处于安全方面的考虑,我们的API可能只允许某些域名的访问,因此在这里引入一个强大的模块cors,介绍它的文章,网上有很多,大家可以直接搜索,在该项目中是这么使用的:

  1. app.use(cors({
  2. origin: ["http://localhost:3001"],
  3. methods: ["GET", "POST", "PUT", "DELETE"],
  4. allowedHeaders: ["Content-Type", "Authorization"]
  5. }));

这个设置在本文的最后的演示网站中,会起作用。

打印请求日志同样是一个很重要的任务,因此引进了winston模块。下边是对他的配置:

  1. import fs from "fs"
  2. import winston from "winston"
  3. if (!fs.existsSync("logs")) {
  4. fs.mkdirSync("logs");
  5. }
  6. module.exports = new winston.Logger({
  7. transports: [
  8. new winston.transports.File({
  9. level: "info",
  10. filename: "logs/app.log",
  11. maxsize: 1048576,
  12. maxFiles: 10,
  13. colorize: false
  14. })
  15. ]
  16. });

打印的结果大概是这样的:

  1. {"level":"info","message":"::1 - - [26/Sep/2017:11:16:23 +0000] \"GET /tasks HTTP/1.1\" 200 616\n","timestamp":"2017-09-26T11:16:23.089Z"}
  2. {"level":"info","message":"::1 - - [26/Sep/2017:11:16:43 +0000] \"OPTIONS /user HTTP/1.1\" 204 0\n","timestamp":"2017-09-26T11:16:43.583Z"}
  3. {"level":"info","message":"Tue Sep 26 2017 19:16:43 GMT+0800 (CST) Executing (default): SELECT `id`, `name`, `password`, `email`, `created_at`, `updated_at` FROM `Users` AS `Users` WHERE `Users`.`id` = 342;","timestamp":"2017-09-26T11:16:43.592Z"}
  4. {"level":"info","message":"Tue Sep 26 2017 19:16:43 GMT+0800 (CST) Executing (default): SELECT `id`, `name`, `email` FROM `Users` AS `Users` WHERE `Users`.`id` = 342;","timestamp":"2017-09-26T11:16:43.596Z"}
  5. {"level":"info","message":"::1 - - [26/Sep/2017:11:16:43 +0000] \"GET /user HTTP/1.1\" 200 73\n","timestamp":"2017-09-26T11:16:43.599Z"}
  6. {"level":"info","message":"::1 - - [26/Sep/2017:11:16:49 +0000] \"OPTIONS /user HTTP/1.1\" 204 0\n","timestamp":"2017-09-26T11:16:49.658Z"}
  7. {"level":"info","message":"Tue Sep 26 2017 19:16:49 GMT+0800 (CST) Executing (default): SELECT `id`, `name`, `password`, `email`, `created_at`, `updated_at` FROM `Users` AS `Users` WHERE `Users`.`id` = 342;","timestamp":"2017-09-26T11:16:49.664Z"}
  8. {"level":"info","message":"Tue Sep 26 2017 19:16:49 GMT+0800 (CST) Executing (default): DELETE FROM `Users` WHERE `id` = 342","timestamp":"2017-09-26T11:16:49.669Z"}
  9. {"level":"info","message":"::1 - - [26/Sep/2017:11:16:49 +0000] \"DELETE /user HTTP/1.1\" 204 -\n","timestamp":"2017-09-26T11:16:49.714Z"}
  10. {"level":"info","message":"::1 - - [26/Sep/2017:11:17:04 +0000] \"OPTIONS /token HTTP/1.1\" 204 0\n","timestamp":"2017-09-26T11:17:04.905Z"}
  11. {"level":"info","message":"Tue Sep 26 2017 19:17:04 GMT+0800 (CST) Executing (default): SELECT `id`, `name`, `password`, `email`, `created_at`, `updated_at` FROM `Users` AS `Users` WHERE `Users`.`email` = 'xiaoxiao@mc.com' LIMIT 1;","timestamp":"2017-09-26T11:17:04.911Z"}
  12. {"level":"info","message":"::1 - - [26/Sep/2017:11:17:04 +0000] \"POST /token HTTP/1.1\" 401 12\n","timestamp":"2017-09-26T11:17:04.916Z"}

性能上,我们使用Node.js自带的cluster来利用机器的多核,代码如下:

  1. import cluster from "cluster"
  2. import os from "os"
  3. const CPUS = os.cpus();
  4. if (cluster.isMaster) {
  5. // Fork
  6. CPUS.forEach(() => cluster.fork());
  7. // Listening connection event
  8. cluster.on("listening", work => {
  9. "use strict";
  10. console.log(`Cluster ${work.process.pid} connected`);
  11. });
  12. // Disconnect
  13. cluster.on("disconnect", work => {
  14. "use strict";
  15. console.log(`Cluster ${work.process.pid} disconnected`);
  16. });
  17. // Exit
  18. cluster.on("exit", worker => {
  19. "use strict";
  20. console.log(`Cluster ${worker.process.pid} is dead`);
  21. cluster.fork();
  22. });
  23. } else {
  24. require("./index");
  25. }

在数据传输上,我们使用compression模块对数据进行了gzip压缩,这个使用起来比较简单:

  1. app.use(compression());

最后,让我们支持https访问,https的关键就在于证书,使用授权机构的证书是最好的,但该项目中,我们使用http://www.selfsignedcertificate.com这个网站自动生成了一组证书,然后启用https的服务:

  1. import https from "https"
  2. import fs from "fs"
  3. module.exports = app => {
  4. "use strict";
  5. if (process.env.NODE_ENV !== "test") {
  6. const credentials = {
  7. key: fs.readFileSync("44885970_www.localhost.com.key", "utf8"),
  8. cert: fs.readFileSync("44885970_www.localhost.com.cert", "utf8")
  9. };
  10. app.db.sequelize.sync().done(() => {
  11. https.createServer(credentials, app)
  12. .listen(app.get("port"), () => {
  13. console.log(`NTask API - Port ${app.get("port")}`);
  14. });
  15. });
  16. }
  17. };

当然,处于安全考虑,防止攻击,我们使用了helmet模块:

  1. app.use(helmet());

前端程序

为了更好的演示该API,我把前段的代码也上传到了这个仓库https://github.com/agelessman/ntaskWeb,直接下载后,运行就行了。

API的代码连接https://github.com/agelessman/ntask-api

总结

我觉得这本书写的非常好,我收获很多。它虽然并不复杂,但是该有的都有了,因此我可以自由的往外延伸。同时也学到了作者驾驭代码的能力。

我觉得我还达不到把所学所会的东西讲明白。

有什么错误的地方,还请给予指正。

一个完整的Node.js RESTful API的更多相关文章

  1. Node.js RESTful API

    什么是REST架构? REST表示代表性状态传输.REST是一种基于Web标准的架构,并使用HTTP协议. 它都是围绕着资源,其中每一个组件是资源和一个资源是由一个共同的接口使用HTTP的标准方法获得 ...

  2. 49.Node.js RESTful API

    转自:http://www.runoob.com/nodejs/nodejs-express-framework.html 什么是 REST? REST即表述性状态传递(英文:Representati ...

  3. 十个书写Node.js REST API的最佳实践(上)

    收录待用,修改转载已取得腾讯云授权 原文:10 Best Practices for Writing Node.js REST APIs 我们会通过本文介绍下书写Node.js REST API的最佳 ...

  4. 使用Node.js原生API写一个web服务器

    Node.js是JavaScript基础上发展起来的语言,所以前端开发者应该天生就会一点.一般我们会用它来做CLI工具或者Web服务器,做Web服务器也有很多成熟的框架,比如Express和Koa.但 ...

  5. 十个书写Node.js REST API的最佳实践(下)

    收录待用,修改转载已取得腾讯云授权 5. 对你的Node.js REST API进行黑盒测试 测试你的REST API最好的方法之一就是把它们当成黑盒对待. 黑盒测试是一种测试方法,通过这种方法无需知 ...

  6. AngularJS 授权 + Node.js REST api

    作者好屌啊,我不懂的他全都懂. Authentication with AngularJS and a Node.js REST api 几个月前,我开始觉得 AngularJS 好像好牛逼的样子,于 ...

  7. 编写 Node.js Rest API 的 10 个最佳实践

    Node.js 除了用来编写 WEB 应用之外,还可以用来编写 API 服务,我们在本文中会介绍编写 Node.js Rest API 的最佳实践,包括如何命名路由.进行认证和测试等话题,内容摘要如下 ...

  8. Node.js 常用 API

    Node.js v6.11.2  Documentation(官方文档) Buffer Prior to the introduction of TypedArray in ECMAScript 20 ...

  9. Practical Node.js (2018版) 第8章:Building Node.js REST API Servers

    Building Node.js REST API Servers with Express.js and Hapi Modern-day web developers use an architec ...

随机推荐

  1. CODE大全给你推荐几个免费的leapftp 注册码

    leapftp 2.7.6 注册码, Name: Kmos/CiA in 1999 s/n: MOD1-MO2D-M3OD-NOPQ LeapFTP2.7.5 注册名:swzn 注册码:214065- ...

  2. Spring之声明式事务

    在讲声明式事务之前,先回顾一下基本的编程式事务 编程式事务: //1.获取Connection对象 Connection conn = JDBCUtils.getConnection(); try { ...

  3. union-find算法Java实现

    package practice; /*在一个全是点的图中,循环选择两点连通,之后判断两点是否在同一通路*/ public class Testmain { public static void ma ...

  4. linux 增量备份命令Rsync 使用详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt320 Rsync的命令格式可以为以下六种: rsync [OPTION].. ...

  5. java-多个数的和

    目的:实现多个整数相加. 思路:1.首先要确定用户所需整数的个数n,此部分由用户在键盘上输入. 2.创建一个长度为n的数组. 3.用户从键盘上输入n个整数并判断是否输入正确,正确则存入数组,否则重新输 ...

  6. IOS学习[Swift中跳转与传值]

    Swift中页面跳转与传值: 1.简单方式 首先,Swift的跳转可分为利用xib文件跳转与storyboard跳转两种方法,我这里选择使用storyboard的界面跳转方法. 1.通过在storyb ...

  7. 深入理解计算机系统(2.3)------布尔代数以及C语言运算符

    本篇博客我们主要讲解计算机中的布尔代数以及C语言的几个运算符. 1.布尔代数 我们知道二进制值是计算机编码.存储和操作信息的核心,随着计算机的发展,围绕数值0和1的研究已经演化出了丰富的数学知识体系. ...

  8. IT经典书籍——Head First系列【推荐】

    Head First 系列书籍是由 O'Relly 出版社发行的一系列教育书籍,中文一般翻译为"深入浅出",它强调以特殊的方式排版,由大量的图片和有趣的内容组合构成,而达到非疲劳的 ...

  9. java 反射详解

    反射的概念和原理 类字节码文件是在硬盘上存储的,是一个个的.class文件.我们在new一个对象时,JVM会先把字节码文件的信息读出来放到内存中,第二次用时,就不用在加载了,而是直接使用之前缓存的这个 ...

  10. 201521145048《Java程序设计管理》第一周学习总结

    1. 本周学习总结 学习并了解Java的发展与历史 在网上视频中学习Java 了解并区分JVM JRE JDK 将java与已学语言做比较,发现相同处 2. 书面作业 Q1.为什么java程序可以跨平 ...