サルでも分かるExpressでのjsonAPIサーバーの作り方

サル並みの語彙力の僕が書いた記事です。
![istock_000018606811xsmall.jpg](https://qiita-image-store.s3.amazonaws.com/0/92480/8f060db5-643d-e10b-5296-9ad05bd72f23.jpeg)

#事前知識
##APIサーバーとは
Webサービスのバックエンドによく使われる。
「PHP入門」などでWebサービスを作るシーンが多く見られますがその違いは簡単に言うと「Webサービスの外部から利用できるかできないか」です。

多くのPHP入門の記事ではPHPでMySQLなどのデータベースからデータを取ってきてそれをそのまま```echo```などで表示する例がよく見られます。
しかし最近のWebサービス開発ではフロントエンドの役割をする部分がバックエンドのAPIサーバーにリクエストを投げてAPIサーバーがデータベースからデータを取ってきてそれをフロントエンドに返しページに表示するのが多い気がします。

APIサーバーを立てるメリットとして僕が一番思うのはそのアプリのスマホ版を作る時に楽です。

主なAPIサーバーの機能をまとめると「 **受け取ったリクエストに対してそれに応じたデータベース操作をして結果を返してあげる** 」です。

##jsonAPIサーバーとは
結果をjson形式で返すAPIサーバーです。
他にはXMLで返すapiサーバーもあります。

#環境
バージョンは一桁目以外は気にしないで大丈夫です。
Expressは後に説明するので他の4つを揃えといてください。
##Node.js
v5.7.1
サーバーサイドでJavaScript動かすやつ

##Express 
4.13.1
Node.jsの上で動くMVCフレームワーク
jsonAPIサーバーとしての役割以外に```ejs```というテンプレートエンジンを用いたWeb開発もできます

##MongoDB
3.2.3
ドキュメント指向のデータベース

##npm
3.10.7
Node.jsのライブラリやパッケージを管理する**パッケージマネージャ**

##postman
httpリクエストを送ってAPIのテストを行うことができる

#データベースの構成
今回はWebサービスによくある「投稿記事(article)」と「ユーザー(user)」のデータを扱うのでデータベースは以下のようです

Node.jsでMongoDBを利用して読み書きを行う際、この一つのデータの塊をモデルとして扱います

##投稿記事(article)
| カラム名     |      型     |     説明         |
|-----------:|------------:|----------------:|
| title      | String      |  記事タイトル      |
| text       | String      |  記事の本文      |
| date       | String      |  投稿日時        |
|_id         | ObjectId ※  | 投稿記事固有のID  |

##ユーザー(user)
| カラム名      |      型     |         説明        |
|------------:|------------:|-------------------:|
| name        | String      |ユーザーの名前         |
| screen_name | String      |Twitterの @~~ みたいな|
| bio         | String      | 軽い自己紹介         |
| _id         | ObjectId ※  | ユーザー固有のID      |

※　ObjectIdとは = mongodbが他と被らないように振ってくれるIDです

#練習用に作ってみるAPI

| method |      url           |            説明         | 
|:------:|:------------------:|:-----------------------:|
|  GET   | /api/v1/article    | 記事一覧を取得            |
|  GET   | /api/v1/article/:id| :idで指定したの記事を取得   |
|  POST  | /api/v1/article     | 記事投稿                 |
| DELETE | /api/v1/article/:id | :idで指定した記事を削除     |
|  GET   | /api/v1/user:/     | ユーザー一覧を取得          |
|  GET   | /api/v1/user:/id   | :idで指定したユーザー情報取得|
|  POST  | /api/v1/user/      | ユーザーの新規追加         |
|   PUT  | /api/v1/user/:id   | :idで指定したユーザー情報更新|

ちょっと多いですが頑張りましょう

#準備
## npm init
```sh:
$ mkdir　express-api
$ cd express-api
$ npm init
```

今回は```express-api```をプロジェクトのディレクトリとします。
作ったディレクトリの中で```npm init```を実行してください。いくつか質問が出てきますが全て空欄でエンターを押して大丈夫です。

## ディレクトリ構成
とりあえずこういう感じで作ってください

>express-api
　|---- /app
　|　　　└ app.js
　|　　　└ /models 
　|ㅤ　　　└ /route
　|　　　　　└ v1
　|---- package.json

**app.js** これを実行してサーバーを起動します
**/route** このディレクトリは各apiのコードを書いたjsファイル入れていきます
**/models** データベースのモデル情報を書いたjsファイルを入れていきます
**package.js** ```npm init```によって生産されたものです

##  必要なライブラリなどのインストール
```sh:
$ npm install express --save
$ npm install mongodb --save
$ npm install mongoose --save
$ npm install body-parser --save
$ npm install moment --save
```

npmを使ってインストールしていきます。
末尾に```--save```をつけるとpackage.jsonに書き込んでくれます。
package.jsonに書き込むと他人のPCにこのプロジェクトを送った時に```npm install```と打つだけでpackage.jsonに記載されたものが全てインストールされます。便利ですね！！

# コードを書く
## Hello,World
まずは先ほど書いたapiではなくテスト用に以下のようなapiを作ります

| method |      url           |            説明         | 
|:------:|:------------------:|:-----------------------:|
|  GET   | /api/v1/           | 「Hello,world」とjsonが返ってきます           |


```js:app.js
// ライブラリ読み込み
var express    = require('express');
var app        = express();
var bodyParser = require('body-parser');

//body-parserの設定
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

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


// GET http://localhost:3000/api/v1/
app.get('/api/v1/',function(req,res){
	res.json({
		message:"Hello,world"
	});
});

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

実行してみましょう。

```sh:
$ node app/app.js
listen on port 3000
```

サーバーはこれでポート番号3000に起動できました。
```res.json()```で結果を返しています。
postmanを使って```http://localhost:3000/api/v1/```にGETリクエストを送りましょう(postmanの使い方は省略します)
以下のように返ってくれば成功です！！

```json:
{
  "message": "Hello,world"
}
```

## apiをディレクトリで分けて綺麗にする
まず、```app.js```を以下のように書き換えます

```js:app.js
var express    = require('express');
var app        = express();
var bodyParser = require('body-parser');

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

var port = process.env.PORT || 3000;

var router = require('./routes/v1/');
app.use('/api/v1/', router);

//サーバ起動
app.listen(port);
console.log('listen on port ' + port);
```
新しく追加した二行で「**/routes/v1/index.jsをrouterとして読み込むよー**」という処理と「**それを/api/v1/のapiとして使うよー**」という処理をしてます。
app.jsの既存の```app.get()```部分は消してください。

ちなみに、```/routes/v1/```は```/routes/v1/index.js```として扱われます。ディレクトリを参照した場合、その下のindex.jsが読み込まれます。


次に、apiの処理を**app/routes/v1**に```index.js```を作りましょう

```js:index.js
var express = require('express');
// ルーティングするで
var router = express.Router();

// routerにルーティングの動作を書いてく
router.get('/',function(req,res){
	res.json({
		message:"Hello,world"
	});
});

//routerをモジュールとして扱う準備
module.exports = router;
```

このコードを```app.js```から呼び出すので```router.get```の第一引数は ```/``` で大丈夫です。```app.js```で「**それを/api/v1/のapiとして使うよー**」と宣言してるのでこのapiは```http://localhost:3000/api/v1/```として使えます。

サーバーを再起動してpostmanでapiを叩いても結果は先ほどと変わらずしっかり動くはずです

##さらに分ける
ここからついに本題です(｀・ω・´)
article関係のapiとuser関係のapiをどっちも```index.js```に書くものアリです。
しかしそれだとapiが増えた時読みづらいので```routes/v1/```の中に```article.js```と```user.js```を作りapiを分けましょう。
試しに各種test用のapiを作ります

| method |       url           |            説明         | 
|:------:|:-------------------:|:------------------------:|
|  GET   | /api/v1/article/test| 「This is article api」と返す|
|  GET   | /api/v1/user/test   | 「This is user api」と返す   |

そうなるとディレクトリ構成は以下のような感じです

>express-api
　|---- /app
　|　　　└ app.js
　|　　　└ /models 
　|ㅤ　　　└ /route
　|　　　　　└ v1
　|　　　　　　└ index.js
　|　　　　　　└ article.js
　|　　　　　　└ user.js
　|---- package.json

それでは```index.js```を以下のように書き換えましょう

```js:index.js
var express = require('express');
var router = express.Router();

router.use('/article', require('./article.js'));
router.use('/user', require('./user.js'));

module.exports = router;
```

新たに追加された2行は「**article.jsの中に書いてあるapiを/articleとして使うよー**」と「**user.jsの中に書いてあるapiを/userとして使うよー**」という意味です。
つまり```index.js```から見れば「**それを/api/v1/のapiとして使うよー**」と言ってるので```/article```として扱うということはapp.jsから見た時```/api/v1/article/```として扱われることを意味します。

それでは中身を書いていきます

```js:article.js
var express = require('express');
var router = express.Router();

// GET  http://localhost:3000/api/v1/article/test
router.get('/test', function (req, res) {
	res.json({
		message:"This is article api"
	});
});

//routerをモジュールとして扱う準備
module.exports = router;
```

```js:user.js
var express = require('express');
var router = express.Router();

// GET  http://localhost:3000/api/v1/user/test
router.get('/test', function (req, res) {
	res.json({
		message:"This is user api"
	});
});

//routerをモジュールとして扱う準備
module.exports = router;
```

サーバーを再起動してpostmanでapiを叩いてみましょう
```http://localhost:3000/api/v1/article/test```にリクエストを送った時

```json
{
  "message": "This is article api"
}
```
と結果が返ってくると思います。
```http://localhost:3000/api/v1/user/test```も上手く結果が返ってくればバッチリです！！この2つのapiはテスト用なので消してしまっても構いません。

##DB操作を含むapiを作っていく
###DB起動
ターミナルをもう1つ開き以下のように実行してください。

```sh:
$ sudo mongod --dbpath /var/db/mongo/
```

これでMongoDBが起動し、読み書きができる状態になりました
###Modelファイル作成
データベースの準備から始めます

データベースに入れるデータを書いたファイルを**/models**に作ります
それぞれ```articleModel.js```と```userModel.js```としましょう

```js:articleModel.js
var mongoose     = require('mongoose'); //mongoDBに接続するためのライブラリ
var Schema       = mongoose.Schema; //mongoDBのスキーマを作る
var moment       = require('moment');

var ArticleSchema   = new Schema({
    title :String,
    text: String,
    date: String
});

ArticleSchema.methods.setDate = function () { 
    //作った時間をセット
    this.date = moment().format("YYYY-MM-DD HH:mm:ss"); 
};

// スキーマをモデルとしてコンパイルし、それをモジュールとして扱えるようにする
module.exports = mongoose.model('ArticleModel', ArticleSchema);
```

```js:userModel.js
var mongoose     = require('mongoose'); //mongoDBに接続するためのライブラリ
var Schema       = mongoose.Schema; //mongoDBのスキーマを作る

var UserSchema   = new Schema({
    name :String,
    screen_name: String,
    bio: String
});

// スキーマをモデルとしてコンパイルし、それをモジュールとして扱えるようにする
module.exports = mongoose.model('UserModel', UserSchema);
```

簡単に流れを説明すると、```mongoose```を読み込み、スキーマを作るのに必要なカラム名とデータ型を記述していき、最後の行でスキーマをモデルに変換してモジュールとして扱うようにしています。

ArticleModel.jsに見られる```ArticleSchema.methods.setDate```に代入した関数はNode.jsからそれぞれの値を入れるときに呼び出すための関数です。
この場合、ArticleSchemaのdateに現在時刻をセットしてます。
**また、_idは自動生産されます。**

### app.js編集
まずapp.jsに以下を追記しましょう(4行目で大丈夫です)

```js
var mongoose   = require('mongoose');

mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost:27017/ExpressAPI');
mongoose.connection.on('error', function(err) {
	console.error('MongoDB connection error: ' + err);
	process.exit(-1);
});
```
　

それではユーザーの新規追加APIを作りましょう
(本来ユーザー登録関係のapiにはtoken発行などが必要ですが今回は省略し、ユーザーの基本情報を新しく登録する処理のみとさせていただきます)

###POST /api/v1/user/

user.jsを以下のようにします

```js:user.js
var express   = require('express');
var router    = express.Router();
var UserModel = require('../../models/userModel.js');

router.post('/',function(req,res){

    // モデル作成．
    var User = new UserModel();

    // データを詰め込む
    User.name = req.body.name;
    User.screen_name = req.body.screen_name;
    User.bio =  req.body.bio;
    
    // 保存処理
    User.save(function(err) {
        if (err){
            // エラーがあった場合エラーメッセージを返す
            res.send(err);
        } else {
            // エラーがなければ「Success!!」
            res.json({ message: 'Success!!' });
        }
    });
});

//routerをモジュールとして扱う準備
module.exports = router;
```

POSTを受け取った時に実行したいので```router.post()```で書きます。
コメント文の通り、userModel.jsを使い、モデルを作成し、そこにデータを詰め込み```User.save()```でDBに保存しています。
```req.body.~~```で一緒に送られてきたデータが取得してます。
　


サーバーを再起動してpostmanでPOSTリクエストを投げてみましょう。
今回は**name**と**screen_name**と**bio**の値も一緒にリクエストしないといけないのでpostmanのBody欄でKeyとValueを指定してあげましょう
![スクリーンショット 2016-11-17 1.56.58.png](https://qiita-image-store.s3.amazonaws.com/0/92480/38fe0b19-4bea-389c-1f27-1e4c646e401c.png)

エラーが出なければ保存できてます👍


###GET /api/v1/user/
それでは保存したデータを取得するAPIを作ります
以下を追記してください。

```js:user.js
router.get('/', function (req, res) {
    UserModel
        .find()
        .then(function (users) {
            res.json(users);
        });
});
```

全て取得するのは超簡単！！
```UserModel.find()```でUserModelが全て取得できます。
```then()```で受け取った時の処理を書きます。ここはres.jsonでそのまま結果を返してあげれば完了です！！

```json
[
  {
    "_id": "582c8fc9e3841640e1140ce4",
    "bio": "Hello",
    "name": "太郎",
    "screen_name": "tarou04",
    "__v": 0
  },
  {
    "_id": "582c9144e3841640e1140ce5",
    "bio": "Hello",
    "name": "二郎",
    "screen_name": "jirou08",
    "__v": 0
  }
]
```

postmanでGETリクエストを送ってこんな感じで返ってくれば成功です

###GET /api/v1/user/:id

次は末尾の```:_id```によって返す値を変えるapiです
先ほどDBに登録した二郎君にmongoDBが```582c9144e3841640e1140ce5```という素敵なidを振ってくれたのでこのidをリクエストすると二郎君の情報が取れる、といったapiを作りたいと思います！！

```js:user.js
router.get('/:id', function (req, res) {

    var Userid = req.params.id;
    UserModel
        .findById(Userid,function (err,user) {
            res.json(user);
        });
});
```

第一引数でurlを指定する際に```:id```と書くとそれは```req.params.id```として受け取れます。
もちろん、```:screen_name```と書けば```req.params.screen_name```です！

関数```findById()```は引数と_idが一致したデータを返します

それではpostmanでリクエストします
今回僕は```http://localhost:3000/api/v1/user/582c91a1db5998416ca5fc93```にGETリクエストを投げたので、以下のように返ってきました

```json
  {
    "_id": "582c91a1db5998416ca5fc93",
    "bio": "Hello",
    "screen_name": "jirou08",
    "name": "二郎",
    "__v": 0
  }
```

この場合```findById()```の引数が```582c91a1db5998416ca5fc93```になったのでそれに一致したユーザーである二郎くんの情報が返ってきました

###PUT /api/v1/user/:id

これは```PUT```なので指定ユーザーの情報を更新する、他のものに変えるapiです。

```js:user.js
router.put('/:id',function (req, res) {

    var Userid = req.params.id;

    UserModel
        .findById(Userid, function(err, user) {
            if (err) {
                res.send(err);
            } else {

            	user.name = req.body.name;
            	user.screen_name = req.body.screen_name;
            	user.bio = req.body.bio;

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

```

簡単に言うと```GET /api/v1/user/:id```と```POST /api/v1/user/```が混ざったような感じです。
```findById()```で取得したデータをコールバック関数の中で```user```として扱いそこの中のデータを入れ替えてそのまま```save()```しています。
今回は```findById()```で取得したデータを使ってるので```new UserModel()```は不要です

今回も**name**、**screen_name**、**bio**を含めてリクエストを送ってみましょう
![スクリーンショット 2016-11-17 2.56.16.png](https://qiita-image-store.s3.amazonaws.com/0/92480/bf1a3d52-ce36-5793-e7f5-9d9b788a67b1.png)
これでリクエストしたので二郎くんのbioを「Hello」から「buongiorno!!」に変わりました！！

###POST /api/v1/article

```js:article.js
var express   = require('express');
var router    = express.Router();
var ArticleModel = require('../../models/articleModel.js');

router.post('/',function(req,res){

    // モデル作成．
    var Article = new ArticleModel();

    // データを詰め込む
    Article.title = req.body.title;
    Article.text = req.body.text;
    Article.setDate();

    // 保存処理
    Article.save(function(err) {
        if (err){
            // エラーがあった場合エラーメッセージを返す
            res.send(err);
        } else {
            // エラーがなければ「Success!!」
            res.json({ message: 'Success!!' });
        }
    });
});

//routerをモジュールとして扱う準備
module.exports = router;
```

基本的な動きは```POST /api/v1/user```と同じです。
唯一違う点としてデータを詰め込む際に```setDate（）```という関数を呼び出してる点です。
これはスキーマを作成するときにarticleModel.jsに書いた関数をここで呼び出しています。

###GET /api/v1/article
```js:user.js 
router.get('/', function (req, res) {
    ArticleModel
        .find()
        .then(function (articles) {
            res.json(articles);
        });
});
```

```GET /api/v1/user``` とほぼ同じなので説明は省略します

###GET /api/v1/article/:id
```js:article.js
router.get('/:id', function (req, res) {

    var Articleid = req.params.id;
    ArticleModel
        .findById(Articleid,function (err,article) {
            res.json(article);
        });
});

```

###DELETE /api/v1/article/:id
```js:article.js
router.delete('/:id',function(req,res){
	var Articleid = req.params.id;
	ArticleModel.remove({_id:Articleid})
	    .then(function(){
	    res.json({message:'Success!!'});
	});
});
```
こちらもとても簡単！
```ArticleModel.remove({_id:Articleid})```でArticleModelの中から```_id```が一致したものを削除しています

## 最終的なディレクトリ構成とコード
### ディレクトリ
>express-api
　|---- /app
　|　　　└ app.js
　|　　　└ /models 
　|　　　　　└ articleModel.js
　|　　　　　└ userModel.js
　|ㅤ　　　└ /route
　|　　　　　└ v1
　|　　　　　　└ index.js
　|　　　　　　└ article.js
　|　　　　　　└ user.js
　|---- package.json
　|---- /node_modules

###コード
複数回の編集があったもののみです

```js:app.js
var express    = require('express');
var app        = express();
var bodyParser = require('body-parser');
var mongoose   = require('mongoose');

mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost:27017/ExpressAPI');
mongoose.connection.on('error', function(err) {
	console.error('MongoDB connection error: ' + err);
	process.exit(-1);
});

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());


var port = process.env.PORT || 3000;


var router = require('./routes/v1/');
app.use('/api/v1/', router);

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

```article.js
var express   = require('express');
var router    = express.Router();
var ArticleModel = require('../../models/articleModel.js');

router.post('/',function(req,res){

    // モデル作成．
    var Article = new ArticleModel();

    // データを詰め込む
    Article.title = req.body.title;
    Article.text = req.body.text;
    Article.setDate();

    // 保存処理
    Article.save(function(err) {
        if (err){
            // エラーがあった場合エラーメッセージを返す
            res.send(err);
        } else {
            // エラーがなければ「Success!!」
            res.json({message: 'Success!!'});
        }
    });
});

router.get('/', function (req, res) {
    ArticleModel
        .find()
        .then(function (articles) {
            res.json(articles);
        });
});

router.get('/:id', function (req, res) {

    var Articleid = req.params.id;
    ArticleModel
        .findById(Articleid,function (err,article) {
            res.json(article);
        });
});

router.delete('/:id',function(req,res){
	var Articleid = req.params.id;
	ArticleModel.remove({_id:Articleid})
		.then(function(){
			res.json({message:'Success!!'});
		});
});

//routerをモジュールとして扱う準備
module.exports = router;
```

```js:user.js
var express   = require('express');
var router    = express.Router();
var UserModel = require('../../models/userModel.js');

router.get('/test', function (req, res) {
	res.json({
		message:"This is user api"
	});
});

router.post('/',function(req,res){
	
	// 新しいユーザのモデルを作成する．
	var User = new UserModel();

	// ユーザの各カラムの情報を取得する．
    User.name = req.body.name;
    User.screen_name = req.body.screen_name;
    User.bio =  req.body.bio;
    
       // ユーザ情報をセーブする．
	User.save(function(err) {
	    if (err)
	        res.send(err);
	    res.json({ message: 'Success!' });
	});
});

router.get('/', function (req, res) {

    UserModel
        .find()
        .then(function (users) {
            res.json(users);
        });
});

router.get('/:id', function (req, res) {

    var Userid = req.params.id;
    UserModel
        .findById(Userid,function (err,user) {
            res.json(user);
        });
});

router.put('/:id',function (req, res) {

    var Userid = req.params.id;

    UserModel
        .findById(Userid, function(err, user) {
            if (err) {
                res.send(err);
            } else {

            	user.name = req.body.name;
            	user.screen_name = req.body.screen_name;
            	user.bio = req.body.bio;

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

});

//routerをモジュールとして扱う準備
module.exports = router;
```

#あとがき
## v1って何
version1です。アプリに新機能を追加するときは```v2```などのディレクトリ名を作ってapiを書いていくと良いと思います👍

##最後に
誤字など誤ってる部分があったら教えてください。
また、わからない部分があれば[@leafia78](http://twitter.com/leafia78)にメンション飛ばして頂ければ出来る範囲で答えます