Node.js + Express 4.x + MongoDB(Mongoose)でRESTfulなjsonAPIサーバの作成を丁寧に解説する．+ テストクライアントを用いたAPIテストまで

Node.js Advent Calendar その2 
http://qiita.com/advent-calendar/2015/nodejs2

Node.js Advent Calendarはこちら!
http://qiita.com/advent-calendar/2015/nodejs

こちらは1日目の記事です!

RESTfulなAPIサーバって良く聞きますよね．
実際の開発でも，クライアントサイド，サーバサイドと作業を分担して行う事が多いと思います．

同様な記事は多々あります．
しかしながら，具体的なコードへの言及や実行方法の確認などが甘いものも多く，初心者が作成する際には「結局どうすりゃ作れるんだよ」と途方に暮れる事も多いかと思います．
この記事では2つの目標を建てました．

1. 「**APIサーバ作成初心者がNode.js + Express + MongoDBでのRESTfulAPIを一から作成出来るようになる**」
2. 「**作成したAPIサーバにテストクライアントを使ってテストを行える**」

この2点が達成出来る事を目標にします．

# はじめに
## RESTfulAPIとは
まずはじめに，RESTとは，平たく言ってしまえばWebの設計思想の一つです．
RESTfulとは，RESTの設計思想に則ったモノというニュアンスで良いでしょう．
つまり，RESTfulAPIとは，**RESTの設計思想に基づいたAPI**という意味合いになります．

## RESTの原則とは
この辺りの設計思想は人によって微妙に差異がありますので，あくまで軽く触れる程度にしておきます．

1. ステートレスなクライアント/サーバプロトコル．
2. 明示的にHTTPメソッドを用いる．
3. 処理結果はHTTPステータスコードで返す．
4. 直感的に理解できる,ディレクトリ構造に似たURLで公開する．

より詳細に知りたい方は以下を参照すると良いでしょう．
[RESTful Web サービスの基本](https://www.ibm.com/developerworks/jp/webservices/library/ws-restful/)


# 環境
* Mac OSX Maverick
* Node.js v0.12.4
* Express v4.x
* Mongoose v4.2.7
* body-parser v1.0.2

一般的な**MEANスタック**と呼ばれるものと殆ど差異が無いと思います．

>
* M：MongoDB（ドキュメント指向データベース）
* E：Express（Node.js上で動作するMVCフレームワーク）
* A：AngularJS（フロントエンドのJavaScript用MVWフレームワーク）
* N：Node.js（サーバーサイドJavaScriptの実行環境）

MEANスタックの詳細は以下が参考になります．
[LAMPに代わる構成として注目のMEANスタックの基礎知識とインストール、ひな型作成](http://www.atmarkit.co.jp/ait/articles/1412/01/news041.html)

# 開発の準備
## 各種インストール
インストール方法などは他にも多くの方が記事化しているため，今回は割愛させていただきます．

* Node.js v0.12.4
* Express v4.x
* Mongoose v4.2.7
* body-parser v1.0.2

こちらの4つが使える状態となっていれば大丈夫です．


## 今回作成するAPIのリファレンス
今回は，簡単なユーザ情報を扱うAPIを作成します．

|    Route    |   HTTP Verb  |  Description |
|:-----------:|:------------:|:------------:|
| /api                |      GET     |     テストメッセージをPOSTする．              |
| /api/users          |      POST    |     ユーザを作成する                         |
| /api/users          |      GET     |     全てのユーザ情報を取得する．              |
| /api/users/:user_id |      GET     |     UserIDに一致するユーザ情報を取得する．    |
| /api/users/:user_id |      PUT     |     UserIDに一致するユーザ情報を更新する．    |
| /api/users/:user_id |      DELETE  |     UserIDに一致するユーザ情報を削除する．    |

## Web API テストクライアント
便利なAPIChromeのアプリとして[Postman](http://www.getpostman.com)というアプリが公開されています，
![Postman](http://i.imgur.com/pZM6mCx.jpg)

今回はこのPostmanを用いてGUI環境でAPIのテストを行います．

Postmanの登録方法，簡単な使い方等は以下が参考になります．
[ostman-google-chromeを使ったweb-apiテストクライアント](http://www.moongift.jp/2014/05/postman-google-chromeを使ったweb-apiテストクライアント/)

余談ですが，CUI上でも`curl`というコマンドを駆使する事で同様の内容を実現できます．
`curl`を用いたAPIテストに関しては後述するおまけの項でまとめて紹介しようと思います．


# いざ，RESTfulAPI開発

おまたせしました．
ここからは手を動かして，実際に開発していきます．

## 今回のディレクトリ構成
今回は**3file**で完結するディレクトリ構成で行います．

```:terminal
$ tree .
jsonAPI
├── server.js
├── package.json
├── app
│   └── models
│       └──user.js
└── node_module

```

まずは簡単にディレクトリだけ作成しておきます．

```:terminal
$ mkdir jsonAPI
$ cd jsonAPI
$ mkdir app
$ cd app
$ mkdir models
```

## package.json の記述

まずは，package.jsonを記述します．
package.jsonは，RubyでいうGemfileのようなものです．
パッケージのバージョン管理を行うために書くという認識でも良いかもしれません．

package.jsonの作成には，
`$ npm init`
コマンドを用いてCUIの指示に従って記述するか，

```json:package.json
{
    "name": "jsonAPI",
    "main": "server.js",
    "dependencies": {
        "express": "~4.0.0",
        "mongoose": "~3.6.13",
        "body-parser": "~1.0.1"
    }
}
```

上記の内容をコピペして保存して下さい．

本来は，公開するライブラリとして書くのが一般的なようです．
今回はただのテストとして作成するため，内容はある程度適当でも良いでしょう．
詳細に記述する方法は下記が参考になります．
[npm package.json 取扱説明書](http://liberty-technology.biz/PublicItems/npm/package.json.html)


## "超"簡単なapiサーバの作成
まずは， GETで http://localhost:3000/api を叩くとmessageを返すAPIを作ります．

|    Route    |   HTTP Verb  |  Description |
|:-----------:|:------------:|:------------:|
| /api                |      GET     |     テストメッセージをPOSTする．              |

逐一コードを説明するのは大変なので，各行に意味を記述しておきました．

```js:server.js
// server.js

// 必要なパッケージの読み込み
var express    = require('express');
var app        = express();
var bodyParser = require('body-parser');

// POSTでdataを受け取るための記述
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// 3000番を指定
var port = process.env.PORT || 3000;

// expressでAPIサーバを使うための準備
var router = express.Router();

router.use(function(req, res, next) {
    console.log('Something is happening.');
    next();
});

// 正しく実行出来るか左記にアクセスしてテストする (GET http://localhost:3000/api)
router.get('/', function(req, res) {
    res.json({ message: 'Successfully Posted a test message.' });
});


// ルーティング登録
app.use('/api', router);

//サーバ起動
app.listen(port);
console.log('listen on port ' + port);

```

たったこれだけでjsonAPIが完成しました．
後は，サーバを起動して

```:console
$ node server.js
```
早速，Postmanを用いて http://localhost:3000/api にアクセスしてみましょう．

以下の画面が出れば成功です．
HTTPメソッドはGETを，URLは上記のものを入力し，Sendボタンを押すだけで結果が確認出来ます．
<img width="1392" alt="スクリーンショット 2015-11-25 19.31.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/55673/ba2d9190-a488-5473-2e45-22a915597ebf.png">


## 簡単なデータの作成と一覧取得をやってみる．
次は，以下の2つのAPIを実装してみます．

|    Route    |   HTTP Verb  |  Description |
|:-----------:|:------------:|:------------:|
| /api/users          |      POST    |     ユーザを作成する                         |
| /api/users          |      GET     |     全てのユーザ情報を取得する．              |

### MongoDBを使う
まずはMongoDBを使う準備を行います．
```
$ sudo mongod --dbpath /var/db/mongo/
```
でサーバ起動後，

```
$ mongo
> use jsonAPI
> db.createCollection('user')
{ "ok" : 1 }
> show collections
system.indexes
user
> quit()
```
この手順を踏んでmongoDB内で準備をしておきます．

### Userモデルの作成
以下のようなUserのモデルを作成してみます．

|    Column   |   Type  |  Description |
|:-----------:|:------------:|:------------:|
| twitter_id  |      String     |     twitterのID     |
| name        |      String     |     名前            |
| age         |      Number     |     年齢            |

このようなデータは，以下のように記述する事が出来ます．

```js:/app/models/user.js

// app/models/user.js

var mongoose     = require('mongoose');
var Schema       = mongoose.Schema;

var UserSchema   = new Schema({
    twitter_id : { type: String, required: true, unique: true },
    name: String,
    age: Number
});

module.exports = mongoose.model('User', UserSchema);

```

### APIの実装
まずは，パッケージの読み込みの下部にDBへの接続，モデルの宣言を追記します


```js:server.js
// server.js

// 必要なパッケージの読み込み
var express    = require('express');
var app        = express();
var bodyParser = require('body-parser');

// -----------追記ここから-----------

// DBへの接続
var mongoose   = require('mongoose');
mongoose.connect('mongodb://localhost/jsonAPI');

// モデルの宣言
var User       = require('./app/models/user');

// -----------追記ここまで-----------

// POSTでdataを受け取るための記述
...
```

次に，実際にルーティングの設定と，各メソッドの実装を行います．

```js:server.js

    res.json({ message: 'Successfully Posted a test message.' });
});

//-----------追記ここから-----------

// /users というルートを作成する．
// ----------------------------------------------------
router.route('/users')

// ユーザの作成 (POST http://localhost:3000/api/users)
    .post(function(req, res) {

        // 新しいユーザのモデルを作成する．
        var user = new User();

        // ユーザの各カラムの情報を取得する．
        user.twitter_id = req.body.twitter_id;
        user.name = req.body.name;
        user.age = req.body.age;

        // ユーザ情報をセーブする．
        user.save(function(err) {
            if (err)
                res.send(err);
            res.json({ message: 'User created!' });
        });
    })

// 全てのユーザ一覧を取得 (GET http://localhost:8080/api/users)
    .get(function(req, res) {
        User.find(function(err, users) {
            if (err)
                res.send(err);
            res.json(users);
        });
    });
//-----------追記ここまで-----------

// ルーティング登録
```

さて，実際に http://localhost:8080/api/users にPOSTメソッドでアクセスしてみます．
POSTメソッドでアクセスするにはメソッドをPOSTにしてアクセスする必要があります．

また，下図のBodyという欄で， `x-www-form-urlencoded`を指定して，登録に必要なデータを入力する必要があります．
これを行うと，無事にUserが作成できた事がわかります．
 
<img width="1390" alt="スクリーンショット 2015-11-25 20.21.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/55673/9ff6059d-6f0e-6b5a-ddcd-0e3510b598ad.png">

次に，作成したユーザ情報を取得するGETメソッドを入力してみましょう．
GETメソッドとして， http://localhost:8080/api/users にアクセスすると，作成したデータ一覧のjsonが吐き出されました．

<img width="1390" alt="スクリーンショット 2015-11-25 20.21.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/55673/0e7fe4ab-b454-7811-b1c1-e4509c141eba.png">

これで簡単なデータの作成と一覧取得を行うAPIが完成しました．


## 任意のユーザデータの取得，更新，削除を行うAPIの作成(GET,PUT,DELETE)

ここまで来たら残り僅かです．
あるユーザIDを与えると取得，更新，削除が行えるAPIを作成してみます．

|    Route    |   HTTP Verb  |  Description |
|:-----------:|:------------:|:------------:|
| /api/users/:user_id |      GET     |     UserIDに一致するユーザ情報を取得する．    |
| /api/users/:user_id |      PUT     |     UserIDに一致するユーザ情報を更新する．    |
| /api/users/:user_id |      DELETE  |     UserIDに一致するユーザ情報を削除する．    |

```js:server.js

// 全てのユーザ一覧を取得 (GET http://localhost:8080/api/users)
    .get(function(req, res) {
        User.find(function(err, users) {
            if (err)
                res.send(err);
            res.json(users);
        });
    });

//-----------追記ここから-----------

// /users/:user_id というルートを作成する．
// ----------------------------------------------------
router.route('/users/:user_id')

// 1人のユーザの情報を取得 (GET http://localhost:3000/api/users/:user_id)
    .get(function(req, res) {
        //user_idが一致するデータを探す．
        User.findById(req.params.user_id, function(err, user) {
            if (err)
                res.send(err);
            res.json(user);
        });
    })
// 1人のユーザの情報を更新 (PUT http://localhost:3000/api/users/:user_id)
    .put(function(req, res) {
        User.findById(req.params.user_id, function(err, user) {
            if (err)
                res.send(err);
            // ユーザの各カラムの情報を更新する．
            user.twitter_id = req.body.twitter_id;
            user.name = req.body.name;
            user.age = req.body.age;

            user.save(function(err) {
                if (err)
                    res.send(err);
                res.json({ message: 'User updated!' });
            });
        });
    })

// 1人のユーザの情報を削除 (DELETE http://localhost:3000/api/users/:user_id)
    .delete(function(req, res) {
        User.remove({
            _id: req.params.user_id
        }, function(err, user) {
            if (err)
                res.send(err);
            res.json({ message: 'Successfully deleted' });
        });
    });

//-----------追記ここまで-----------

// ルーティング登録
```

では，実際にuserIDを指定してGETをしてみます．

<img width="1396" alt="スクリーンショット 2015-11-25 20.44.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/55673/573c58cc-b641-5c67-c27b-34b9bd3a29bf.png">

1人のユーザのみの情報がうまく取得出来ました!

次にPUTメソッドでユーザの情報を更新してみます．
PUTメソッドは，POSTメソッド同様にBodyに更新するデータを記述する必要があります．
<img width="1392" alt="スクリーンショット 2015-11-25 20.44.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/55673/9ea0007a-f698-3c66-c077-a295f8a6f159.png">


正しく更新できたか確認してみましょう．

<img width="1390" alt="スクリーンショット 2015-11-25 20.45.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/55673/17043903-ac73-21e5-7e8e-55ed306b0d83.png">
無事に更新が出来てます!

最後に，DELETEメソッドを用いて削除を行ってみます．
<img width="1389" alt="スクリーンショット 2015-11-25 20.45.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/55673/fa907b07-8fe8-fab4-fead-d2a6a56735e9.png">

再びGETで確認してみると…

<img width="1390" alt="スクリーンショット 2015-11-25 20.45.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/55673/b14e1e5d-6695-8c0a-ccc3-26fead0e9f36.png">

無事に削除出来たようです! これでバッチリですね!!

# 終わりに
今回は，Node.js + Express 4.x + MongoDB(Mongoose)でRESTfulなjsonAPIサーバの作成を行ってみました．
Node.jsの他にも様々な言語でRESTfulAPIは記述することが出来ます．
是非他の言語でも挑戦してみてはいかがでしょうか．

一時期はjavascriptは死に言語とまで言われていました．
しかし，IoTが盛んな今，javascriptで記述出来るマイコンボードが大きく増えたことで再びjavascriptの需要が増加したといえるでしょう．
今後も何かしらの用途でjavascriptを使う機会が増えると思うので，躓いた時の参考になれば幸いです．

## ソースコード一覧
今回のソースコードは下記から閲覧することが出来ます．
うまく動かない場合はこちらを参考にして下さい．

https://github.com/shopetan/Sample-RESTful-jsonAPI

## 参考記事
* [Node.js(Express) と MongoDB で RESTful な API サーバを作る](http://qiita.com/hkusu/items/9bb59541c64696b15730)

AngularJS Full-Stackを利用して作成しています．こちらはHerokuへのデプロイが中心ですね．

* [Express API 4.x](http://expressjs.com/4x/api.html)

こちらはExpressのAPIリファレンスです．あーだこーだドキュメントとにらめっこして作成しました．

 
# おまけ(curlコマンドを用いたCUI上でのAPIテスト方法)

## GET /api

```:console
$ curl -H "Accept: application/json" -H "Content-type: application/json" -X GET http://localhost:3000/api

# result
{"message":"Successfully Posted a test message."}
```

## POST /api/users

```:console
$ curl -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"twitter_id":"testid3","name":"parson3","age":21}'  http://localhost:3000/api/users

{"message":"User created!"}

```

## GET /api/users

```:console
$ curl -H "Accept: application/json" -H "Content-type: application/json" -X GET http://localhost:3000/api/users

[
{"_id":"5655995184c2595b45f2bc1d","age":18,"name":"parson1","twitter_id":"testid","__v":0},
{"_id":"5655999e84c2595b45f2bc1f","age":18,"name":"parson2","twitter_id":"testid2","__v":0},
{"_id":"56559bf7e7430d9b45d13d34","age":21,"name":"parson3","twitter_id":"testid3","__v":0}
]
```

# GET /api/users/:user_id

```:console
$ curl -H "Accept: application/json" -H "Content-type: application/json" -X GET http://localhost:3000/api/users/5655999e84c2595b45f2bc1f

{"_id":"5655999e84c2595b45f2bc1f","age":18,"name":"parson2","twitter_id":"testid2","__v":0}
```

# PUT /api/users/:user_id

```:console
$ curl -H "Accept: application/json" -H "Content-type: application/json" -X PUT -d '{"twitter_id":"testid2","name":"parson20","age":50}'  http://localhost:3000/api/users/5655999e84c2595b45f2bc1f

{"message":"User updated!"}
```

# DELETE /api/users/:user_id

```:console
$ curl -H "Accept: application/json" -H "Content-type: application/json" -X DELETE http://localhost:3000/api/users/5655999e84c2595b45f2bc1f

{"message":"Successfully deleted"}
```