xuanyuanaosheng
6/2/2014 - 2:09 PM

nodejs_expressjs_restful_api_server_quickstart.md

基于 Node.js 和 Express.js 的 RESTful API 服务端开发

开发环境

  • Node.js (0.10.28+)
    • Arch Linux: 直接使用包管理器安装
    • Ubuntu / Debian / etc: 使用 visionmedia/n
    • OS X / Windows: 使用官方提供的安装包
  • 编辑器
    • WebStorm
    • Vim / Emacs (不分先后)
    • Sublime Text / Atom / 其他代码编辑器
  • Express / 模板生成器 (express 4.0+)
    • sudo npm install -g express express-generator
    • 对于连接到 NPM Registry 比较坑的同学: sudo npm install --registry=http://r.cnpmjs.org --disturl=http://dist.cnpmjs.org -g express express-generator

创建工程

首先使用 express -h 查看帮助文档。

这里直接使用 express myapiserver 来在当前目录下生成名为 myapiserver 的工程根目录。

  • ./bin/ - 可执行文件目录
  • ./public/ - 公开静态资源目录,例如样式表以及图片等文件。
  • ./routes/ - 路由(控制器)模块
  • ./views/ - 视图(页面模板)目录
  • ./app.js - 主程序文件
  • ./package.json - 程序元数据文件

目前我们只需要关心 ./app.js./routes/index.js 这两个文件。./routes/user.js 用不到,可以删掉。同时需要删掉 ./app.js 中的 var users = require('./routes/users'); 以及 app.use('/users', users);

接下来安装依赖包。因为只用到了 express.js 框架,所以我们需要的依赖都已经写在 package.json 文件中。直接执行 npm install 来安装依赖包。

如果是国际线路比较坑的同学请继续使用 npm install --registry=http://r.cnpmjs.org --disturl=http://dist.cnpmjs.org 来安装。请注意此处无需加 -g (即全局安装) 所以也不需要管理员权限(sudo)。

注册路由

文件: ./routes/index.js

模板文件已经做了一个很好的例子。对于 RESTful API 服务端,我们需要了解的:

  • 请求 URL 及格式(样例: https://example.com:3000/testuser/viewthread?id=9 | Route Scheme: /:username/viewthread)
    • 协议: HTTPS
    • 主机: example.com
    • 端口号: 3000
    • 请求路径: /testuser/viewthread
    • 请求参数: username
    • 查询: id
  • 用到的 HTTP 方法。
    • GET - 获取资源
    • POST - 创建资源
    • PUT - 替换现有资源,如果不存在则创建
    • DELETE - 删除资源
  • 常见的 HTTP 响应状态码
    • 200 OK
    • 201 Created
    • 301 Moved Permanently
    • 304 Not Modified
    • 400 Bad Request
    • 401 Unauthorized
    • 403 Forbidden
    • 404 Not Found
    • 500 Internal Server Error
    • 502 Bad Gateway
    • 503 Service Unavailable
    • 504 Gateway Timeout

现在我们来按照此规则创建一个 GET 路由(控制器)。

router.get('/:username/viewthread', function(req, res) {
    // 返回客户端请求的参数
    res.send(200, 'USER: ' + req.params.username + ', ' + 'THREAD ID: ' + req.query.id);
});

测试

启动 ./bin/www (直接执行 ./bin/www 文件或 node ./bin/www),HTTP 服务器将默认侦听 3000 端口。

打开浏览器,请求 URL: http://127.0.0.1:3000/testuser/viewthread?id=9,观察返回结果。

尝试更改 URL 中的 testuser9 为其他值,观察返回结果。

更友好的 URL 规则

由上例,URL 可以重写为 http://example.com/testuser/viewthread/9,用户更容易记忆/解析,也会被搜索引擎认为是静态资源,更利于搜索引擎优化(SEO)。

此时对应的获得 id 的方法也就是 req.params.id

POST 方法

对于一些调用 RESTful API 的情况下使用 GET 方法是不安全的,例如创建/修改数据。此时应选用 POST 方法处理。

例: 发布一篇文章。

  • 请求路径为 /post
  • 参数:
    • username: 用户名
    • password: 密码
    • category: 文章分类
    • title: 文章标题
    • content: 文章内容

处理逻辑:

  • 验证用户名 / 密码组合是否正确,正确则继续操作,错误则中断操作并返回错误代码
  • 保存数据到数据库 (或其它地方)
  • 保存成功,向客户端返回成功状态码,反之则返回错误状态码以及错误代码。

代码:

router.post('/post', function(req, res) {
    // 验证用户名 / 密码组合,此处为不涉及数据库操作,仅作测试用意
    var username = 'testuser',
        password = 'testpassword';
    if (req.body.username !== username || req.body.password !== password) {
        return res.send(401, 'Login failed.');
    } else {
        var date = new Date();
        // 将文章数据保存到数据库。此处同样为不涉及数据库操作,直接返回文章内容。
        res.write('Post saved.\n');
        res.write('Title: ' + req.body.title + '\n');
        // 插入发布日期,注意 JavaScript 的月份从 0 开始计算。
        res.write(date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDay() + ' ' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds() + 
                ' by ' + req.body.username + ' under ' + req.body.category + '\n');
        res.end(req.body.content);
    }
});

测试

引入: 一个超级好用的 RESTful API 测试工具。在 Chrome Web Store 安装

保存所做的更改后重新启动 ./bin/www 程序,在 payload 部分选择 Form 并填入全部键值并点击 Send

尝试更改用户名 / 密码,重新发送请求,观察服务端的响应。

尝试减少任意一个键值对,重新发送请求并观察服务端响应。

=> 在一些情况下,缺少数据 / 数据类型不正确并不会导致服务端报错或崩溃。对于不能确定数据有效性的请求,务必要做有效性检查。

模型

对于特定对象的处理方法,可以把它们抽象出来成为独立的模块,既易于维护也提高了代码重用率。

现在来构建一个处理 GET 方法的 API 路径,它将返回当前系统的可用内存。

新建目录 ./models,文件 ./models/ram.js

var os = require('os');

var Ram = {};

Ram.getRam = function(callback) {
    callback(Math.floor(os.freemem() / 1048576));
}

module.exports = Ram;

文件 ./routes/index.js

var Ram = require('../models/ram');

router.get('/ram', function(req, res) {
    Ram.getRam(function(ramAvailable) {
        res.send('RAM Free: ' + ramAvailable + 'MB');
    });
});

首先是建立了 Ram 这个对象模型,所有对于 Ram 的处理方法都可以抽象到 ./models/ram 模块里。然后在 index.js 中调用了这个模块来使用其提供的方法。

这里用到了一个异步调用的方法,但是例子并不是很好,因为这里并没有展现出异步的优越性以及容易踩到的坑,所以只做为异步写法的一个例子而已。

异步方法的一个参数 (通常为最后一个) 是回调函数,在逻辑处理结束后调用回调函数并传递必要的值。因此在 index.js 文件中对于 Ram.getRam() 的调用参数是一个匿名函数,它在 getRam 方法处理完成后被调用 (通过 callback) 并传递了空闲内存数量。

最终在回调函数内部向客户端发送了响应,因为所有需要处理的任务都已经完成。

练习

对于一般 API 服务端,需要返回特定格式的数据以便于客户端处理。

请参阅 Node.js 文档,尝试编写 RESTful API 服务端程序,实现如下 API:

  • 请求路径: /status
  • 请求方法: POST
  • 请求参数: accesskey, 长度: 32, 类型: Alphanumeric.
  • 响应数据类型: JSON
  • 响应值结构:
    • uptime: 字符串,系统已运行毫秒数
    • freemem: 字符串,系统可用内存
    • load_average: 数组,系统平均负载
    • node_version: 字符串,当前 Node.js 环境版本
  • 返回值样例:
{
    "uptime": "30000",
    "freemem": "2000",
    "load_average": [0.15, 0.20, 0.18],
    "node_version": "0.10.28"
}

延伸阅读