Node.js + express + MongoDBでWebAPIを作ってみる

# 使用するソフトウェア
Node.js `v8.9.1`
Express `4.16.0`
MongoDB `v4.0.5`
mongoose `5.4.7`
body-parser `1.18.3`

# 前提
Node.jsとexpressで簡単なWebAPIを提供するところまでできていること。
[Node.jsとexpressを使って簡単なWebAPIを作成する](https://qiita.com/s_harada/items/8e9413b981cfbff4db2d)

# Node.jsとexpressの準備
以下の記事を参考に、Node.jsとexpressでWebAPIを作成できる状態にしておく。
[Node.jsとexpressを使って簡単なWebAPIを作成する](https://qiita.com/s_harada/items/8e9413b981cfbff4db2d)

# MongoDBの準備
## MongoDBのインストーラをダウンロード
MongoDBはクラウド版とサーバ版があるが、今回はローカルでDBを使用したいのでサーバ版をダウンロードする。
[MongoDB community版ダウンロード](https://www.mongodb.com/download-center/community)

## MongoDBのインストール
インストーラを実行して、ウィザードに沿ってそのままインストールすればよい。
途中でMongoDBのサービスをインストールするか聞かれるので、デフォルトのままネットワークサービスユーザで実行する設定にしている。

![WS000095.JPG](https://qiita-image-store.s3.amazonaws.com/0/329353/1dee783b-8190-70ff-20eb-aad715bd55b7.jpeg)

## MongoDBのPathを通す
### システム環境変数の設定
Windowsのシステム環境変数Pathに以下のパスを追加する。

![WS000101.JPG](https://qiita-image-store.s3.amazonaws.com/0/329353/0aa4ee19-d147-feb1-f113-b6da92ab311a.jpeg)

### MongoDBのPathが通ったか確認する
Windowsを再起動し、コマンドプロンプトで以下のコマンドを実行する。

```
mongo --verion
```

MongoDBのバージョンが表示されたらPathが通っている。

### MongoDBの設定
必要に応じてMongoDBの設定を変更する。
設定は`MongoDBのインストールディレクトリ\bin\mongod.cfg`で行う。
データベースの格納場所を変更したい場合は`storage.dbPath`、ログファイルの出力先を変更したい場合は`systemLog.path`を変更する。

デフォルトでは`storage.dbPath`、`systemLog.path`ともにWindowsのProglem Filesの配下を指しているが、ここだと書き込み禁止になっているためにMongoDBの起動に失敗する。
そのためC直下などにdataフォルダとlogフォルダを作成し、mongod.cfgではそれらのフォルダを使用するように書き換える。

```mongod.cfg
# mongod.conf

# for documentation of all options, see:
#   http://docs.mongodb.org/manual/reference/configuration-options/

# Where and how to store data.
storage:
  dbPath: C:\mongo\data
  journal:
    enabled: true
#  engine:
#  mmapv1:
#  wiredTiger:

# where to write logging data.
systemLog:
  destination: file
  logAppend: true
  path:  C:\mongo\log\mongod.log

# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1


#processManagement:

#security:

#operationProfiling:

#replication:

#sharding:

## Enterprise-Only Options:

#auditLog:

#snmp:
```

### MongoDBの開始と終了
Windowsのサービスから「MongoDB」を表示し、「開始」ボタンを押すことでMongoDBがサービスとして起動する。

![WS000103.JPG](https://qiita-image-store.s3.amazonaws.com/0/329353/d4999657-e065-f94d-417d-4abd9b63af5a.jpeg)

MongoDBを終了するときは同様に、「停止」ボタンを押すことでMongoDBのサービスが停止する。

デフォルトではスタートアップの種類は「自動」になっているので、Windows起動と同時にMongoDBのサービスが開始される。

### コマンドプロンプトでMongoDBに接続
以下のコマンドを入力することで、MongoDBに接続して、対話形式でMongoDBを操作できる。

```
mongo
```

## MongoDBの基本的な操作
### MongoDBのデータ構造
MongoDBのデータ構造はざっくり以下のような形になっている。

![WS000105.JPG](https://qiita-image-store.s3.amazonaws.com/0/329353/b180507d-06de-b9e2-6ec7-3e6bda3b3fc7.jpeg)

一般的なRDBMSのテーブルとレコードに対して、MongoDBはコレクションとドキュメントという呼び方でデータを保持している。

| RDBMS | MongoDB |
|:-:|:-:|
| データベース | データベース |
| テーブル | コレクション |
| レコード | ドキュメント |

### データベースの一覧を見る
コマンドプロンプトでコマンド`mongo`を入力してMongoDBに接続し、MongoDBとの対話モードで`show dbs`というコマンドを入力する。

```
> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
```

デフォルトではadmin,config,localという三種類のデータベースがあらかじめ作成されている。

### 使用するデータベースを切り替える
使用するデータベースを切り替えるときは、`use [使用するデータベース名]`というコマンドを入力する。

```
> use local
switched to db local
```

### コレクションの一覧を見る
データベースの配下にあるコレクションの一覧を見たいときは`show collections`というコマンドを入力する。

```
> show collections
startup_log
```

### コレクション内のドキュメントを見る
コレクションの配下にあるドキュメントを見たいときは、`db.[コレクション名].find();`というコマンドを入力する。
startup_logというコレクション内にあるドキュメントを見たい場合は以下のようなコマンドとなる。

```
> db.startup_log.find();
```

### データベースを作成する
新しいデータベースを作成したい場合は、`use [作成するデータベース名]`というコマンドを入力する。
使用するデータベースを切り替えるときのコマンドと同じだが、データベース名が存在しない場合は自動でデータベースを作成してくれる。

```
> use sampledb
switched to db sampledb
```

### コレクションを追加する
データベースにコレクションを追加するには、`use [データベース名]`でデータベースを選択した状態で、`db.createCollection('[コレクション名]')`というコマンドを入力する。

```
> db.createCollection('test_collection')
{ "ok" : 1 }
```

### ドキュメントを追加する
コレクションにドキュメントを追加するには、`use [データベース名]`でドキュメントを追加するコレクションがいるデータベースを選択した状態で`db.[コレクション名]insert();`というコマンドを入力する。

```
> db.test_collection.insert({name:'hogehoge', age:20});
WriteResult({ "nInserted" : 1 })
```

findで登録されたドキュメントの内容を見てみる。

```
> db.test_collection.find();
{ "_id" : ObjectId("5c4aba1d3cfc6e2de3998745"), "name" : "hogehoge", "age" : 20 }
```

勝手に付加されている`_id`はObjectIdというもので、自動的にユニークな値が割り振られる。（RDBMSのユニークキーに相当する）

### 余談
MongoDBのドキュメントはRDBMSでいうところのテーブル定義のようなものは無く、JSON形式のドキュメントであれば中身のツリー構造に関係なくコレクションに登録できる。
そのため、以下のように同じコレクション内にsexがついているドキュメントと付いていないドキュメントを混在させることもできる。

```
> db.test_collection.insert({name:'taro', age:30, sex:'men'})
WriteResult({ "nInserted" : 1 })
> db.test_collection.find();
{ "_id" : ObjectId("5c4aba1d3cfc6e2de3998745"), "name" : "hogehoge", "age" : 20 }
{ "_id" : ObjectId("5c4abe533cfc6e2de3998746"), "name" : "taro", "age" : 30, "sex" : "men" }
```

# Node.js+express+MongooseでMongoDBを利用する
## Mongooseとは
Node.jsからMongoDBにアクセスするためのライブラリにMongooseというものがあるので、今回はこれを利用してみる。
MongooseはO/R MapperのようにMongoDBのドキュメントをオブジェクト構造で操作できるようになっている。
O/R Mapperの概要については以下のページを参照。
[O/Rマッピングの役割とメリット](http://www.atmarkit.co.jp/ait/articles/0404/13/news075.html)

## Mongooseのインストール

```
npm install mongoose --save
```

Mongooseについては以下のページを参考にさせていただいた。
[node.js から MongoDB にアクセス (Mongoose の紹介)](http://krdlab.hatenablog.com/entry/20110317/1300367785)

## MongooseでMongoDBを操作する
expressで作成したWebAPIにMongoDBへのドキュメント保存の機能を加えてみる。
expressでWebAPIを作成する手順は以下のQiitaを参照。
[Node.jsとexpressを使って簡単なWebAPIを作成する](https://qiita.com/s_harada/items/8e9413b981cfbff4db2d)

POSTリクエストのBODYに格納された情報を取り出すにはbody-parserが必要なので、以下のnpmコマンドでインストールしておく。

```
npm install body-parser --save
```

### スキーマ定義
まず、MongoDBのオブジェクト構造を表すスキーマを定義する。
今回はnameとageを保持するUserというスキーマを定義している。

```models/user.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema

const User = new Schema({
    name: String,
    age: Number
});

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

### WebAPIでMongoDBにドキュメントを保存する
#### WebAPIの作成
`/api/v1/create-user`というWebAPIを作成し、POSTリクエストで受け取ったBODYのパラメータをMongoDBに保存する。
上記で作成したUserというスキーマ定義を利用して、インスタンスを作成してsaveを行うことでMongoDBにドキュメントが作成される。

```index.js
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const User = require('./models/user');

mongoose.connect('mongodb://localhost/user');
app.use(bodyParser.urlencoded({extended:false}));
app.use(bodyParser.json());

// create-userの設定
app.post('/api/v1/create-user', (req, res) =>{
    if (!req.body){
        return res.status(500).send('reqest body empty.');
    }

    const instance = new User();
    instance.name = req.body.name;
    instance.age = req.body.age;
    // MongoDBに保存
    instance.save(function(err){
        if(!err) {
            return res.status(200).send('user create success.');
        } else {
            return res.status(500).send('user create faild.');
        }
    });
});

// イベント待機
app.listen(3000, () => console.log('Listening on port 3000'));
```

#### 動作を確認
以下のコマンドでNodeサーバを起動。

```
node index.js
```

ChromeのARCなど、HTTPリクエストを送ることのできるツールを用いてWebAPIにPOSTリクエストを実行する。
![WS000107.JPG](https://qiita-image-store.s3.amazonaws.com/0/329353/6879f7ef-b1fa-b1a7-c75f-7f8e6a614332.jpeg)

MongoDBにuserというデータベースが作成され、usersというコレクションにドキュメントが追加される。
以下のコマンドでMongoDBにドキュメントが追加されたことを確認する。

```
> use user
switched to db user
> db.users.find();
{ "_id" : ObjectId("5c4fdadbf326d40328e4e6b8"), "name" : "taro", "age" : 20, "__v" : 0 }
```

### WebAPIでMongoDBからドキュメントを取得する
#### WebAPIの作成
`/api/v1/get-all-user`を追加。
findを利用してuserデータベースのusersコレクションから全ドキュメントを取得する。
取得したドキュメントをそのままレスポンスとして返す。

```index.js
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const User = require('./models/user');

mongoose.connect('mongodb://localhost/user');
app.use(bodyParser.urlencoded({extended:false}));
app.use(bodyParser.json());

// create-userの設定
app.post('/api/v1/create-user', (req, res) =>{
    if (!req.body){
        return res.status(500).send('reqest body empty.');
    }

    const instance = new User();
    instance.name = req.body.name;
    instance.age = req.body.age;
    // MongoDBに保存
    instance.save(function(err){
        if(!err) {
            return res.status(200).send('user create success.');
        } else {
            return res.status(500).send('user create faild.');
        }
    });
});

// get-all-userの設定
app.get('/api/v1/get-all-user', (req, res) =>{
    User.find(function(err, result){
        if(!err) {
            return res.json(result);
        } else {
            return res.status(500).send('get all user faild.');
        }
    });
});

// イベント待機
app.listen(3000, () => console.log('Listening on port 3000'));
```

#### 動作を確認
ChromeのARCなど、HTTPリクエストを送ることのできるツールを用いてWebAPIにGETリクエストを実行する。

![WS000108.JPG](https://qiita-image-store.s3.amazonaws.com/0/329353/c7e40f30-0fdb-6a76-a2d3-7cac02da2f9f.jpeg)

MongoDBからドキュメントを取得し、WebAPIのリクエストに乗せて返すことができた。