Node.js/Expressのボディパーサーの仕様確認（エコーサーバー(echo-server)）

# 背景/目的

クライアント側からのリクエストを各種パーサーがどのようなオブジェクトや配列にしてくれるのを確認することを目的にリクエストの情報をまとめてレスポンスにそのまま返却するようなエコーサーバーを作ってみました。
その他、HTTPクライアントのリクエストが想定通りになっているかを確認するのにも使えると思うので記録として残しておきます。

# 仕様

リクエストの以下の情報をレスポンスJSONにまとめて返す。

- ヘッダー情報
- パス
- HTTPメソッド(Verb)
- クエリパラメータ
- リクエストボディ（ある場合のみ）

# 準備

npmとかnode.jsはインストールされている前提。

```console
mkdir echo-server
cd echo-server
npm init -f
npm install -y express
touch index.js
```

# ソースコード

↑で作ったindex.js

```javascript:index.js
'use strict';

const express = require('express');
const app = express();
const bodyParser = require('body-parser');

// Set body parser
app.use(bodyParser.urlencoded({ extended: true, type: 'application/x-www-form-urlencoded' }));
app.use(bodyParser.json({ type: 'application/json' }));
app.use(bodyParser.raw({ type: '*/*' }));

// All request process.
app.use((req, res, next) => {
  res.json({
    path: req.originalUrl.indexOf('?') === -1 ? req.originalUrl : req.originalUrl.substring(0, req.originalUrl.indexOf('?')),
    method: req.method,
    query: req.query,
    headers: req.headers,
    body: req.body ? (Object.keys(req.body).length ? req.body : undefined) : undefined
  });
});

// Create HTTP server.
const http = require('http');
const server = http.createServer(app);
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
server.listen(port);
server.on('error', (err) => { console.error(err); });
server.on('listening', () => { console.log('listening on ' + port); });
```

# 実行

`node index.js`

# 実行結果確認

## GETリクエスト

以下のようなクエリパラメータ名にすると配列にしてくれるみたいです。

```console:request
$ curl -s -X GET -H "Authorization: Bearer dummmy" "http://localhost:3000/foo/bar?page=1&size=10&qarr[]=q1&qarr[]=q2&qarr[]=q3"  | jq .
```
```json:response
{
  "path": "/foo/bar",
  "method": "GET",
  "query": {
    "page": "1",
    "size": "10",
    "qarr": [
      "q1",
      "q2",
      "q3"
    ]
  },
  "headers": {
    "host": "localhost:3000",
    "user-agent": "curl/7.68.0",
    "accept": "*/*",
    "authorization": "Bearer dummmy"
  }
}
```

## POST(Json)

これはJSONなのでそのまま出る感じなのでさらっと。

```Console:request
$ curl -s -X POST -H "Authorization: Bearer dummmy" -H "Content-Type: application/json" -d '{"foo":"foo-value", "bar": "bar-value", "arr": [{"sub": "sub1"},{"sub": "
sub2"}]}' "http://localhost:3000/hogehoge"  | jq .
```
```json:response
{
  "path": "/hogehoge",
  "method": "POST",
  "query": {},
  "headers": {
    "host": "localhost:3000",
    "user-agent": "curl/7.68.0",
    "accept": "*/*",
    "authorization": "Bearer dummmy",
    "content-type": "application/json",
    "content-length": "81"
  },
  "body": {
    "foo": "foo-value",
    "bar": "bar-value",
    "arr": [
      {
        "sub": "sub1"
      },
      {
        "sub": "sub2"
      }
    ]
  }
}
```

## POST(フォーム)

Web画面(HTML)のフォーム送信（`Content-Type: application/x-www-form-urlencoded`）のケースです。
これは、 `obj[sub]=obj-sub-value` という名前の付け方をすると、以下のようなjsonにしてくれます。

```json
"obj": {
    "sub": "sub-value"
}
```
ネストしたオブジェクトの場合は `nested[sub1][sub2]=sub1-sub2` という名前の付け方をすると

```json
"nested": {
  "sub1": {
    "sub2": "sub1-sub2"
  }
}
```

つまり、各フォーム要素のname属性を意識してつければ、例えばBackendのデータベースがMongoDBのようなオブジェクト形式だった場合にサーバー側でFormをオブジェクトの形に整形（組み立てる）処理を書かなくても済むわけです。もちろんJSonスキーマだったり、バリデーションのチェックは必要ですが。。

```Console:request
$ curl -s -X POST -H "Authorization: Bearer dummmy" -H "Content-Type: application/x-www-form-urlencoded" -d 'foo=foo-valoue&bar=bar-value&arr=arr1&arr=arr2&arr=arr3&
obj[sub]=sub-value&nested[sub1][sub2]=sub1-sub2' "http://localhost:3000/foo/bar" | jq .
```
```json:response
{
  "path": "/foo/bar",
  "method": "POST",
  "query": {},
  "headers": {
    "host": "localhost:3000",
    "user-agent": "curl/7.68.0",
    "accept": "*/*",
    "authorization": "Bearer dummmy",
    "content-type": "application/x-www-form-urlencoded",
    "content-length": "103"
  },
  "body": {
    "foo": "foo-valoue",
    "bar": "bar-value",
    "arr": [
      "arr1",
      "arr2",
      "arr3"
    ],
    "obj": {
      "sub": "sub-value"
    },
    "nested": {
      "sub1": {
        "sub2": "sub1-sub2"
      }
    }
  }
}
```