ExpressとMongoDBで即席RestAPI

# やること
Node.jsのMVCフレームワーク[Express](https://techacademy.jp/magazine/16119)とNoSQLの[MongoDB](https://programming-study.com/trouble/fundamental-knowledge-of-the-most-popular-nosql-database-mongodb/)を使って、ユーザ情報の登録・変更・削除ができる簡単なRestAPIを作る。
リクエストやレスポンスはjsonで（NoSQLとの相性抜群）。
MongoDBはDockerコンテナ上で起動させる。

超適当ですが全体像はこんな感じ。
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635329/a926fca7-a950-7ff9-2660-b2a78284fc5f.png)


# 準備
[Homebrew](https://brew.sh/index_ja)と[Node.js](https://nodejs.org/ja/)を入れておく。

# Docker上でMongoDB起動
MongoDBをDockerコンテナ上で起動する。
直に入れてもいいが、せっかくなのでDocker使ってみる。

```shell
# Dockerインストール
$ brew install docker --cask

# MongoDBイメージ取得
$ docker pull mongo

# コンテナを作成して起動
# ホストOS側は27018ポート、コンテナ側は27017ポート（MongoDBのデフォルト）を使用
$ docker run -p 27018:27017 --name mongo-test -d mongo
```

# Node.jsプロジェクト作成
ベースとなるプロジェクトを作って環境構築する。
Docker起動中かと思うので、別のターミナルを起動する。

```shell
# ディレクトリ作成
$ mkdir test-app

# 初期化処理（問合せ無しでpackage.json作成）
$ npm init -y

# 必要nodeモジュール（expressとmongoDB操作用モジュール）をインストール
$ npm install express mongoose
```

# Model作成
スキーマとなるUserModelを定義する。

```shell
# ディレクトリ作成
$ mkdir server
$ cd server
$ mkdir models

# Model定義JavaScript作成
$ touch models/user.js
```

user.jsをVSCodeとかで開いて編集する。
肝心のModelは今回、ユニークなID、名前、年齢の3つのフィールドを持たせる。

```javascript:user.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const userSchema = new Schema({
  id: {
    type: String,
    required: true,
    unique: true
  },
  name: { type: String },
  age: { type: Number}
});

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

# Router&Controller作成
Modelを利用して実際にHTTPリクエストが来た際にどう振舞うかを定義するRouterと、全体を制御するControllerを定義する。
今回は小規模なので、分けずにまとめてserver.jsとする。

```shell
# ディレクトリ移動
$ cd ..

# Router&Controller定義JavaScript作成
$ touch server.js
```

server.jsをVSCodeとかで開いて編集する。
httpリクエストは3001番ポートで待ち受けるとする。
コードを少なくしたかったので、登録以外はエラー処理を端折ってます。

```javascript:server.js
const express = require('express');
const mongoose = require('mongoose');
const User = require('./models/user');

// DB接続
mongoose.connect('mongodb://localhost:27018/sampledb');

const app = express();
app.use(express.json({extended: true}));

// ユーザ情報登録
app.post('/users', (req, res) => {
  const user = new User(req.body);
  user.save((err, addedUser) => {
    if (err)
      return res.status(400).json({
        errorMessage: 'failed to add the user.'
      });
    res.send(addedUser);
  });
});

// ユーザ情報取得（全件）
app.get('/users', (req, res) => {
  User.find({}, (err, users) => {
    res.send(users);
  });
});

// ユーザ情報取得（ID指定）
app.get('/users/:id', (req, res) => {
  const params = req.params.id;
  User.find({id: params}, (err, user) => {
    res.send(user);
  });
});

// ユーザ情報削除（全件）
app.delete('/users', (req, res) => {
  User.remove({}, err => {
    res.send(true);
  });
});

// ユーザ情報削除（ID指定）
app.delete('/users/:id', (req, res) => {
  const params = req.params.id;
  User.remove({id: params}, (err, deletedUser) => {
    res.send(deletedUser);
  });
});

// ポート3001番でlisten
app.listen(3001, () => {
  console.log(`Server up on 3001`);
});
```

# サーバ起動

```shell
$ node server.js
```

# 動作確認（curlコマンド）
curlコマンドでユーザ情報の登録・取得・削除をやってみる。
`_id`と`__v`はMongoDBが自動付与するフィールドで、それぞれオブジェクトID、バージョン管理用として使われる。

```shell
# Allen登録
$ curl -X POST -H 'Content-Type:application/json' -d '{"id":"001","name":"Allen","age":20}' http://localhost:3001/users
# Allen登録結果
{"_id":"607bce198776e444bd55422b","id":"001","name":"Allen","age":20,"__v":0}%

# Yuki登録
$ curl -X POST -H 'Content-Type:application/json' -d '{"id":"002","name":"Yuki","age":26}' http://localhost:3001/users
# Yuki登録結果
{"_id":"607c3bf502ac68477a41ee10","id":"002","name":"Yuki","age":26,"__v":0}%

# 全件取得
$ curl -X GET http://localhost:3001/users
# 取得結果
[{"_id":"607bce198776e444bd55422b","id":"001","name":"Allen","age":20,"__v":0},{"_id":"607c3bf502ac68477a41ee10","id":"002","name":"Yuki","age":26,"__v":0}]%

# IDを指定して取得
$ curl -X GET http://localhost:3001/users/001
# 取得結果
[{"_id":"607bce198776e444bd55422b","id":"001","name":"Allen","age":20,"__v":0}]%

# IDを指定して削除
$ curl -X DELETE http://localhost:3001/users/001
# 削除結果
{"n":1,"ok":1,"deletedCount":1}%
# 削除確認のため全件取得
$ curl -X GET http://localhost:3001/users
# 残ってるのはYukiだけ
[{"_id":"607c3bf502ac68477a41ee10","id":"002","name":"Yuki","age":26,"__v":0}]%

# 全件削除
$ curl -X DELETE http://localhost:3001/users
# 削除結果
true%
# 削除確認のため全件取得
$ curl -X GET http://localhost:3001/users
# 何も残っていない
[]%
```

# 動作確認（API Tester）
curlコマンドだと視覚的に分かりづらいので、Chrome拡張の[Talend API Tester](https://chrome.google.com/webstore/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm)を使って、視覚的に確認する。
こちらでは登録と全件取得だけ確認してみる。

まずは登録。
![スクリーンショット 2021-04-18 23.11.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635329/69b5b4e4-e448-f5df-0321-60db2c0d3d16.png)

この後、同じ要領でYukiも登録したとして、次は全件取得。

![スクリーンショット 2021-04-18 23.13.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635329/c6c2436b-af56-25e0-c0d0-feb5617bc4d5.png)

こんな感じで確認できた。
もちろん、METHODを変えることでDELETEも可能。
これで一通り、DBを操作する簡単なRestAPIを作成できた。

# 応用としてやってみたいこと
- フロントエンドをReactとかで作りたい。
- 今回DockerはMongoDBでしか使わなかったが、Node.jsそのものからDocker上で環境構築したい。VSCodeでDockerコンテナ上のワークスペースも編集できるらしい（[参考](https://qiita.com/yoskeoka/items/01c52c069123e0298660)）。
- TypeScriptで書けるようにしたい（[参考](https://qiita.com/techneconn/items/012bdf1b9ff3881546b3)）。

# 参考サイト
[初級JavascriptフルスタックエンジニアのためのReactとExpressの同時開発チュートリアル](https://casualdevelopers.com/tech-tips/how-to-develop-react-and-express-concurrently/)
[MongoDB(Mongoose)上級者への道](https://qiita.com/teradonburi/items/e8bad98112515ec7e96e)
[curlコマンドを使った操作](https://wagby.com/wdn8/restapi-curl.html)