Node JS和Express 4 Router搭建实现RESTful API

Express 4.0于2014年4月份发布,这促使很多基于Node的应用改变其处理路由的方式。面对新版的Express Router特性, 我们可以更加灵活的定制应用的路由。

今天,我们看看如何使用Node, Express 4(Router)以及Mongoose(与MongoDB交互)来搭建一个RESTful API。另外,我们利用Chrome插件Postman对API进行测试。

待打造的示例应用说明

首先,看一看我们打算建立的API和它能做什么!

  • 对于某个物品进行CRUD操作: 我们此处使用users为例
  • 规范化的URL: http://example.com/api/users和http://example.com/api/users/:user_id
  • 利用合适的HTTP动作去实现RESTful: 包括GET\POST\PUT\DELETE
  • 返回数据格式: JSON数据
  • 日志:记录所有的请求日志到工作台

以上都属于RESTful APIs的标准.如果你开心的话,请尽管将我们示例中的users改成您更感兴趣的或者是您应用中的物品(例如:书、博文等)。

当然,请确保您的系统中已经安装了Node


$ node --version #WIN系统个人推荐使用Nodist
v6.10.2

开始构建Express HTTP服务

我们先整理一下思路,即将搭建的API都需要哪些呢?我们需要了解Node Packages有哪些!以Express来运行服务,定义数据模型,以及定义路由。我们也需要测试我们的API。

接着,我们规划项目文件的结构。我们不需要许多文件,因为力求保持一个简洁的示例。真实的生产环境或者更大的应用,我们可以依据MVC架构再细化(例如:将路由部分从server.js独立出来)。


- app/
--- models/
------ user.js // 我们的用户数据模型
- node_modules/ // npm建立的, 项目依赖的包
- package.json // 定义我们的应用和依赖
- server.js // 配置我们的应用和路由

定义NODE包 – Package.JSON

我们先建立NODE项目,然后安装依赖包,如下:


$ npm init
$ yarn add express mongoose body-parser

最后,我们的Package.Json内容如下:


{                                                               
  "name": "node-api",                                           
  "version": "1.0.0",                                           
  "description": "",                                            
  "main": "server.js",                                          
  "scripts": {                                                  
    "test": "echo \"Error: no test specified\" && exit 1"       
  },                                                            
  "author": "",                                                 
  "license": "ISC",                                             
  "dependencies": {                                             
    "body-parser": "^1.17.1",                                   
    "express": "^4.15.2",                                       
    "mongoose": "^4.9.5"                                        
  }                                                             
}                                                               

以上依赖包用途如下:

  • express – 基于NODE的WEB框架
  • mongoose – ORM用于连接MongoDB数据库
  • body-parser – 拉取从HTTP请求中拉取POST内容

建立Express HTTP服务器

我们现在利用Express快速搭建出HTTP服务器,如下:


 // server.js                                                                    
                                                                                 
 // BASE SETUP                                                                   
 // =============================================================================
                                                                                 
 // import the packages we need                                                  
 const express = require('express');                                             
                                                                                 
 const app = express();                                                          
 const bodyParser = require('body-parser');                                      
                                                                                 
 // configure app to use bodyParser()                                            
 // this will let us get the data from a POST                                    
 app.use(bodyParser.urlencoded({ extended: true }));                             
 app.use(bodyParser.json());                                                     
                                                                                 
 const port = process.env.PORT || 8080; // set HTTP server port                  
                                                                                 
 // REQUEST FOR OUR API                                                          
 // =============================================================================
 const router = express.Router(); // get an instance of the express Router       
                                                                                 
 // test route to make sure everything is working (accessed at GET               
 // http://localhost:8080/api)                                                   
 router.get('/', (req, res) => {                                                 
   res.json({ message: 'hi, welcome to our api!' });                             
 });                                                                             
                                                                                 
 // more roues for our API will happen here                                      
                                                                                 
 // REGISTER OUR ROUTES ---------------------------------------------------------
 // all of our routes will be prefixed with /api                                 
 app.use('/api', router);                                                        
                                                                                 
 // START THE SERVER                                                             
 // =============================================================================
 app.listen(port);                                                               
 console.log(`Magic happens on port ${port}`);                                                                  


每个部分都有备注说明:

Base Setup
这部分,我们主要是导入了依赖的包。从而配置了express、bodyParser以及服务端口。
Routes
这里面放置了我们所有的路由设置。
Start the server
启动内置于express的http服务。

测试HTTP服务

虽然只是简单的一段代码(server.js),我们已经可以开始进行初步的测试啦。


node server.js

利用POSTMAN进行API测试

Postman可以帮助我们测试API。 它会发送HTTP请求到我们指定的URL。我们甚至可以传递参数,也支持需要验证的API接入。

我们在Chrome安装好插件后,插件管理中心对应的插件有“启动应用”的按钮,点击就可以打开POSTMAN控制台啦。

使用很简单的!在控制台,我们需要输入一个请求的URL,选择一个HTTP verb, 最后发送请求。

在我们当前的测试用例中,我们只需要输入http://localhost:8080/api,选择GET,然后单击发送按钮。

chrome plugin postman
postman is good for API testing

Mongoose – MongoDB数据库和User模型

通过Mongoose,我们可以创建需要的数据模型以及连接操作我们的MongoDB数据库。

建立数据库并建立连接

我们这里使用了mlab的免费服务作为数据库,你也可以使用本地MongoDB服务。

我们修改server.js,如下:


...
// BASE SETUP
...

 const mongoose = require('mongoose');                                           
                                                                                 
 mongoose.connect('mongodb://node:node@ds111771.mlab.com:11771/restfuldemo');    
...

添加的代码很简单,我们引入了mongoose包,并用它的实例来连接mlab提供的数据库。

USER数据模型 – APP/MODELS/USER.JS

我们通过Mongoose建立USER数据模型如下:


// app/models/user.js
const mongoose = require('mongoose');                     
                                                          
const Schema = mongoose.Schema;                           
                                                          
const UserSchema = new Schema({                           
  name: String,                                           
});                                                       
                                                          
module.exports = mongoose.model('User', UserSchema);      

我们将USER数据模型引入到server.js中。


// BASE SETUP
...
const User = require('./app/models/user');
...

到此,我们的应用已经基本建立完成。我们可以开始添加路由部分。路由是API中重要的组成部分。

Express Router and Routes

我们将利用Express的Router模块来实现我们所有的路由处理。 下面的表格是我们所需的路由规划。

路由 HTTP请求类型 描述
/api/users GET 获取所有用户
/api/users POST 建立一位用户
/api/users/:user_id GET 获取一位用户
/api/users/:user_id PUT 更新一位用户的资料
/api/users/:user_id DELETE 删除一位用户

这些基本覆盖了一个API的基本路由设定需求。

路由中间件

我们之前已经简单定义了应用中的第一个路由。Express Router是支持中间件模式的(这是EXPRESS 4.0的重大变革)。

假设我们希望每次有请求到我们的API,都会触发某个事件(这里以console.log为例)。


// server.js
...
// REQUEST FOR OUR API                                                          
// =============================================================================
const router = express.Router(); // get an instance of the express Router       
                                                                                
// middleware to use for all requests                                           
router.use((req, res, next) => {                                                
  // do logging                                                                 
  console.log('Something is happening.');                                       
  next(); // goto next routes instead of stop here←OB                           
});                                                                             

我们没有添加复杂的代码,只是简单的使用了router.use去注册了一个匿名函数(包括request\response\next三个参数)。 中间件是非常强大的一个特性,这让我我们可以轻松实现非常都的功能,例如部分路由是基于用户验证的。

简单测试一下我们的中间件时候正常工作吧!

建立Express API路由

我们开始创建用于获取所有用户的创建一位用户的路由。他们都由/api/users路由处理。我们先创建建立用户的部分,这样我们后续就有用户来获取啦。

创建一位用户 – POST /API/USERS

我们现在添加路由来处理POST请求,然后在POSTMAN中进行测试。


......
// more roues for our API will happen here                                  
router.route('/users')                                                      
  .post((req, res) => {                                                     
    const user = new User();  // create a new instance of the User model    
    user.name = req.body.name; // set the user name                         
                                                                            
    // save the user and check for errors                                   
    user.save((err) => {                                                    
      if (err) res.send(err);                                               
      res.json({ message: 'User Created!' });                               
    });                                                                     
  });   
......                                                                    

我们启动HTTP服务器,然后利用POSTMAN测试一下,结果如下:

Postman Test API Post
注意选择Body中的x-www-from-urlencoded

获取所有用户 – GET /API/USERS

我们将路由添加到之前的router.route()之后即可。如下:


.......
 // more roues for our API will happen here                               
 router.route('/users')                                                   
   .post((req, res) => {                                                  
     const user = new User();  // create a new instance of the User model 
     user.name = req.body.name; // set the user name                      
                                                                          
     // save the user and check for errors                                
     user.save((err) => {                                                 
       if (err) res.send(err);                                            
       res.json({ message: 'User Created!' });                            
     });                                                                  
   })                                                                     
   // get all users                                                       
   .get((req, res) => {                                                   
     User.find((err, users) => {                                          
       if (err) res.send(err);                                            
       res.json(users);                                                   
     });                                                                  
   }); 
......                                                                   

同样的,我们也在POSTMAN里面测试一下,不过对于GET的很简单,我们直接输入URL为http://localhost:8080/api/users,选择请求类型为GET即可。

为单个用户创建路由

我们已经将以/users结尾的路由处理完毕啦。现在,我们来处理有传递用户id的情况。 我们有三种需求,如下:

  • 获取某个用户的信息
  • 更新某个用户的资料
  • 删除某个用户

来自请求路由中的:user_id将可以通过body-parser获取到。

获取单个用户信息 GET /API/USERS/:USER_ID

我们添加另外一个路由router.route()来处理所有带有:user_id的请求。


......
// on routes that end in /users/:user_id                   
router.route('/users/:user_id')                            
  .get((req, res) => {                                     
    User.findById(req.params.user_id, (err, user) => {     
      if (err) res.send(err);                              
      res.json(user);                                      
    });                                                    
  });                                                      
......

因为我们这里并没有手动设定id值,所以我们通过POSTMAN之前的历史记录去获取到某个用户的Id,然后用于测试。

更新指定id用户的信息 – PUT /API/USERS/:USER_ID

我们用函数链的形式去添加新的请求到之前的Route即可, 代码如下:


......
 // on routes that end in /users/:user_id                      
 router.route('/users/:user_id')                               
   .get((req, res) => {                                        
     ...                                               
   })                                                          
   .put((req, res) => {                                        
     User.findById(req.params.user_id, (err, user) => {        
       if (err) res.send(err);                                 
       const newUser = user;                                   
       newUser.name = req.body.name;                           
       newUser.save((saveErr) => {                             
         if (saveErr) res.send(saveErr);                       
         res.json({ message: 'User Updated!' });               
       });                                                     
     });                                                       
   });  
......                                                       

POSTMAN中进行测试,注意选择请求类型为PUT和URL地址带有id,参数设置就一个name(同POST)。

删除指定id的用户 – DELETE /API/USERS/:USER_ID

有了以上经验,我想关于DELETE的请求也不用多言吧。代码如下:


......
 // on routes that end in /users/:user_id                   
 router.route('/users/:user_id')                            
   .get((req, res) => {                                     
     ...                                     
   })                                                       
   .put((req, res) => {                                     
    ...                                         
   })                                                       
   .delete((req, res) => {                                  
     User.remove({ _id: req.params.user_id }, (err) => {    
       if (err) res.send(err);                              
       res.json({ message: 'Successfully Deleted!' });      
     });                                                    
   });                                                      
......

现在我们可以在POSTMAN中指定请求类型为DELETE以测试删除。

总结

现在我们可以通过API来执行对于用户的CRUD操作啦。这只是一个开端,并未涉及到验证等RESTful的其他设计要点。不过,以上实现的简单示例是任何更大更健全APIs的基础入门。

我们还可以去完善错误消息,为应用中其他部分添加CRUD操作,例如文章等。