Node.jsとExpressでローカルサーバーを構築する(3) ―JSONを返すスタブAPI―

**リンク一覧**

1. [Node.jsとExpressでローカルサーバーを構築する(1) ―Node.jsとnpmの導入―](https://qiita.com/koedamon/items/37ea8e9175cf0fd62371)
2. [Node.jsとExpressでローカルサーバーを構築する(2) ―Expressでルーティング―](https://qiita.com/koedamon/items/fb85c3eb32e7838f9d7c)
3. Node.jsとExpressでローカルサーバーを構築する(3) ―JSONを返すスタブAPI―（本記事）
4. [Node.jsとExpressでローカルサーバーを構築する(4) ―他サーバーから画像を取得する―](https://qiita.com/koedamon/items/122d009327e138caded2)

最近は、Jsonデータを送りつけたり受け取ったりしてなんやかやするケースが増えてきました。私の極狭い認識の範囲では、フロント側の製作期間中にAPIが完成していないことが多いです。
その為、ローカルサーバーにJSONを返すスタブAPIの機能を実装したいと思います。

[スタブとは - IT用語辞典](http://e-words.jp/w/%E3%82%B9%E3%82%BF%E3%83%96.html)

# 概要
## この記事の概要

- 目的
  - Node.jsとExpressを利用して、PC上にローカルサーバーを立ち上げる
- 本記事のゴール
  - GETでリクエストを送ると、JSONデータが返ってくる
  - POSTでJSONデータを送ると、200 OK のレスポンスコードが返ってくる
- 対象者
  - WEBフロント担当者
  - HTML,CSS,JavaScript(es2015含む)の基本的な構文を理解している人
  - HTTP通信、GET、POSTなどについて、ある程度理解している人（ざっくりで良いです）
  - 黒い画面にコマンドを打ち込むことに抵抗がない方
- 環境・バージョン
  - Windows10
  - Node.js(推奨版) 10.15.01
  - npm 6.4.1
  - Express 4.16.4
  - Advanced REST client 10.0.12

## Chrome拡張ツール：Advanced REST client
GETやPOSTの動作確認を簡単に行うために、Chrome拡張ツールの「Advanced REST client」を利用します。
[Advanced REST client](https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo?hl=ja)
導入方法や利用方法は、下記サイトが詳しいです。分かりやすいUIですので、本記事内でも都度説明します。
[「Advanced REST client」の使い方まとめ　～GoogleChromeの拡張ツール](https://viral-community.com/webservice/advanced-rest-client-8235/)


# expressでのルーティングの実装
**前回のおさらい**
Expressを使ったサーバーで、静的コンテンツのルーティングのために`use`メソッドや`express.static`メソッドを利用しました。

```js
const express = require('express');
const app = express();

app.listen(8080);

app.use(express.static(path.join(__dirname, 'public')));
```

`use`は、全てのタイプのリクエストで実行されます。

## ルーティングの基本
### GETリクエストを受け取る
GETリクエストを受け取りたい場合は、`get`メソッドを利用します。

```js
// サイトルートへのGETリクエストの場合
app.get('/', (req, res, next)=>{
  res.send('ルートだよ！');
});

// /hoge/fugaへのGETリクエストを受け取りたい場合
app.get('/hoge/fuga', (req, res, next)=>{
  res.send('ほげふが');
});
```

### POSTリクエストを受け取る
POSTリクエストを受け取りたい場合は、`post`メソッドを利用します。

```js
// サイトルートへのPOSTリクエストの場合
app.post('/', (req, res, next)=>{
  res.send('ルートだよ！全員集合！');
});

// /hoge/fugaへのPOSTリクエストを受け取りたい場合
app.post('/hoge/fuga', (req, res, next)=>{
  res.send('ほげふが');
});
```

### パスの指定

`get`と`post`いずれの場合も、第一引数にはピックアップしたいサイト上のパスを指定します。この指定は、ワイルドカードや正規表現などを組み合わせることも可能です。

```js
app.get(/^book/, ()=>{});
```

上記の例では、`/book`、`/books`にはマッチしますが、`/boo`、`/ibook`とはマッチしません。
[公式ガイド：Expressでのルーティング](https://expressjs.com/ja/guide/routing.html#route-paths)

### ミドルウェアの基本
`get`と`post`いずれの場合も、第二引数はリクエストを受け取った際のコールバック関数を渡します。このコールバック関数は、ミドルウェア（中間処理を行う関数）と呼ばれます。
ミドルウェアは、リクエスト`req`・レスポンス`res`を表すオブジェクトと、後続のミドルウェアへのバトンの役割を果たす関数`next`を受け取ります。
このミドルウェア内でクライアントにレスポンスをするか、明示的にプロセスを終了させるか`next()`を実行して他へ制御を渡すかしないと、応答なしの状態に陥ってしまいます。

```js
app.get(/^book/, (req, res, next)=>{

  if(req.query.id){
    res.send('OK.');
  }else{
    next();
  }
});
```

**res.send**
クライアントへHTTPレスポンスを返すためのメソッドです。
文字列だけでなく、配列やオブジェクトも送信できます。事前に指定しなかった場合、ヘッダーのContentTypeは送信データに応じて自動的に割り当てられます。

**res.end**
データ送信をせずにレスポンスのプロセスを終了させるためのメソッドです。
実体はNode.jsのコアモジュール`http`の`response.end`です。引数にデータを渡せば送信されますが、`res.send`などの専用メソッドを利用する方が望ましいです。

[公式ガイド：アプリケーションで使用するミドルウェアの作成](https://expressjs.com/ja/guide/writing-middleware.html)

### 送信されたデータを受け取る
#### GETで送信されたURLパラメータ
GETで送信されたURLパラメータを受け取るには、ミドルウェアに渡された`req`オブジェクトの`query`プロパティにアクセスします。

```js
/**
 * /search?color=red&size=small へのリクエスト
 */ 
app.get('/search', (req, res, next)=>{

  console.log(req.query.color); // "red"
  console.log(req.query.size);  // "small"

});
```

[リファレンス：req.query（英語）](http://expressjs.com/ja/api.html#req.query)

#### POSTで送信されたJOSNデータ
POSTでJSONデータを受け取るには、予めパース処理を行うミドルウェアを設定しておく必要があります。このミドルウェアは、Expressに予め用意されています。

```js
const express = require('express');
const app = express();

app.use(express.json()); //パース用ミドルウェアを設定

app.post((req, res)=>{
  console.log(req.body); //パースされたデータを参照
});

```

Expressの静的メソッド`json`の実行結果を`use`メソッドに渡しています。渡すのはあくまで`json`の実行結果として返されるミドルウェアで、`json`メソッドそのものではありません。パースされたデータは、`request`オブジェクトの`body`プロパティに格納されます。

[リファレンス：express.json（英語）](http://expressjs.com/ja/api.html#express.json)

### JSONデータを送る
#### オブジェクトをパースして送る
オブジェクトをJSONにパースした上で送信したい場合は、`res.json`メソッドを使います。

```js
app.get('/', (req, res, next)=>{
  res.json({ message: `This is a pen.`});
});
```

[リファレンス：res.json（英語）](http://expressjs.com/ja/api.html#res.json)

####　JSONファイルを送る
予め用意したJSONファイルを送信したい場合、`res.sendFile`メソッドが便利です。このメソッドは、Expressのバージョン4.8.0以降からサポートされています。

```js
app.get('/', (req, res, next)=>{
  res.sendFile(__dirname + '/data.json', (err) => {
    if (err) {
      res.sendStatus(400);
    } else {
      console.log('sending completed');
    }
  });
});
```

第一引数に送信したいファイルの絶対パスを指定します。
第二引数には、送信完了もしくはエラー発生時に呼び出されるコールバック関数を指定します。
エラー発生時はエラー情報を引数で受け取ります。成功時は`undefined`です。

[リファレンス：res.sendFile（英語）](http://expressjs.com/ja/api.html#res.sendFile)

### route()でミドルウェアをまとめる

`route`メソッドを利用すると、同一パスに対するタイプ別のリクエストに対し、ミドルウェアを見通し良くまとめられます。

```js
app.route('/moimoi')
  .get((req, res)=>{
    // GET
  })
  .post((req, res)=>{
    // POST
  })
  .put((req, res)=>{
    // PUT
  })
  .delete((req, res)=>{
    // DELETE
  });
```

[リファレンス：app.route（英語）](http://expressjs.com/ja/api.html#app.route)

##　Routerでルーティング処理を外部モジュール化する

ルーティング処理を他ファイルで定義して、メインの処理にミドルウェアとして組み込むことが出来ます。

### ミドルウェアを定義して公開する
Expressの静的メソッド`express.Router`を実行してルーティング用のミドルウェアの骨格を生成します。
この骨格に対して、「このパスへのリクエストはこれ返せ」といったルーティング処理を肉付けしていきます。肉付けの方法は、通常のexpress()で取得したオブジェクトと同様に、use、get、post等を利用します。
そして、完成したミドルウェアをエクスポートして公開します。

```js
/**
 *  /api/index.js
 */
const express = require('express');
const router = express.Router(); //ミドルウェアの準備

router.get('/items', (req, res) => {
  res.json({ items: [] });
});

router.post('/registration', (req, res) => {
  res.end('Completed');
});

module.exports = router; //エクスポートして公開
```

### 公開されたミドルウェアを利用する
公開されたミドルウェアをインポートしてルーティングに適用します。

```js
/**
 * /app.js
 */
const express = require('express');
const app = express();
const api = require('./api/'); //ミドルウェア読み込み

app.use('/api', api);
```

`/api`へのリクエストの対応を、`/api/index.js`で定義したミドルウェアへ渡しています。
この際、ミドルウェア内のルートパスは`/api`です。ミドルウェア内で指定した`/items`は、実際は`/app/items`へのリクエストです。ミドルウェア内で`/app/items`としてしまうと、実際には`/app/app/items`を指定したことになってしまいます。

[ガイド：express.Router（日本語）](https://expressjs.com/ja/guide/routing.html#express-router)
[リファレンス：express.Router（英語）](https://expressjs.com/ja/4x/api.html#express.router)

# API機能の実装
上記を踏まえて、API機能を実装していきます。
##　ファイルとフォルダの準備
API関連のファイルを格納するフォルダ`api`を追加します。
このフォルダの配下に、クライアントに送りつけるjsonデータと、ルーティング処理を記述するjsファイルを作成します。

```
sample/
  ├ public/
  │    ├ css/
  │    │   └ sample.css 
  │    ├ js/
  │    │   └ sample.js 
  │    ├ img/
  │    │   └ sample.png 
  │    └ index.html
  ├ api/
  │    ├ index.js
  │    └ data.json
  └ app.js
```

```js
/**
 * /app.js
 */
const express = require('express');
const app = express();
const path = require('path');
// /api/index.js で定義されたミドルウェア
const api = require('./api/');

app.listen(8080, () => {
  console.log('Running at Port 8080...');
});

// APIルーティング用ミドルウェアを/apiに設定
app.use('/api', api);

app.use(express.static(path.join(__dirname, 'public')));

app.use((req, res) => {
  res.sendStatus(404);
});


/**
 * /api/index.js
 */
const express = require('express');
const router = express.Router();

// JSONパース
router.use(express.json());

// /api/foo　へのGETリクエスト
router.get('/foo', (req, res) => {
  // ファイルを転送
  res.sendFile(__dirname + '/data.json', (err) => {
    if (err) {
      res.sendStatus(400);
    } else {
      console.log('sending completed');
    }
  });
});

// /api/bar へのGET・POSTリクエスト
router.route('/bar')
  .get((req, res) => {

    // 受け取ったパラメータをそのままJSONにして送り返している
    res.json(req.query);
  })
  .post((req, res) => {

    // 必須のデータ項目を、id,name,address として、受信データをチェックしている
    const nameAry = ['id', 'name', 'address'],
      failed = nameAry.some(v => !req.body[v]);

    if (failed) {
      res.sendStatus(400);
    } else {
      res.sendStatus(200);
    }
  });

module.exports = router;
```

`/api/data.json`に適当な内容を記述します。

```json
{
  "id": "W0001",
  "title": "I Love Cats and Dogs",
  "price": 3000000000000
}
```

準備が出来たら、サーバーを起動します。

```
$ node app.js
```

# 「Advanced REST client（ARC）」で送受信テスト

送受信のテストを、Chromeの拡張ツールの「Advanced REST client」で行いたいと思います。

こちらから取得できます。
[ホーム > アプリ > Advanced REST client](https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo?hl=ja)

ARCを起動して、まずはTOPページにアクセスしてみます。

1. Methodを「GET」
2. RequestURLに「`http://localhost:8080/`」
3. 「SEND」をクリック

![arc.png](https://qiita-image-store.s3.amazonaws.com/0/362737/d266329d-4ea4-bfce-8a48-52915fe454a7.png)

「200 OK」とHTMLデータが取得できました。

では、続けてAPIのデータを取得します。
GETで「`http://localhost:8080/api/foo`」へリクエストを送ると、`/api/data.json`が取得できます。

次は、GETで「`http://localhost:8080/api/bar?name=pee&address=poo&age=17`」へリクエストを送ります。
URLパラメータの値をJSONで取得で取得できます。

次は、POSTで「`http://localhost:8080/api/bar`」へリクエストを送ります。

- 「Parameters＞Body＞Body content type」を「application/json」
- 「Parameters＞Body＞Editor view」を「JSON visual editer」
- id, name, address の三つのプロパティを含むJSONデータを定義

![arc02.png](https://qiita-image-store.s3.amazonaws.com/0/362737/1f0cfbcb-82eb-93db-ef55-ac39dc6cde5e.png)

「200 OK」が返ってきました。
次は、項目を一つ減らして送信してみると、「400 Bad Request」が返ってきます。

これで、APIの開発が間に合っていなくても何とかなりそうですね！

初心者の拙い記事を読んでいただき、ありがとうございました。
お役に立てていたら幸いです。

# JSON Server
この記事を書いている途中で、JSONを返すAPIを簡単に実現する「JSON Server」なるものの存在を知りました。。。
[JSON Server使いこなし - モックサーバーの起動とリソース処理 | CodeGrid](https://app.codegrid.net/entry/2017-json-server-1)
[公式：GitHub - typicode/json-server](https://github.com/typicode/json-server)

# 参考情報

[Express公式サイト(英語)](https://expressjs.com/)
[Node.js + ExpressでREST API開発を体験しよう［作成編］](https://qiita.com/tamura_CD/items/e3abdab9b8c5aa35fa6b)