CLOVER🍀

That was when it all began.

Node.jsのフレームワーク、Expressを使ってRESTサーバーを書いてみる

Node.jsを使って、ちょっとREST APIが作れるフレームワークを試してみようと思いまして。

Expressというフレームワークがスタンダードらしいので、これを試してみることにします。

Express

ドキュメントをざっと見た感じ、シンプルで使いやすそうな雰囲気が?

以前にNode.jsのhttpモジュールを使って試したようなこと(JSONを受け取って四則演算を行うAPIを作ってみる)を、Expressを使って書いてみましょう。

Node.jsで、JSONをPOSTするHttpClient/受けるHttpServerを書いてみる - CLOVER

環境

対象の環境は、このようになっています。

$ node -v
v9.5.0

$ npm -v
5.6.0

準備

とりあえず、チュートリアルを見ながらインストール。

Installing

$ npm install --save express

package.jsonでの依存関係は、このように。

  "dependencies": {
    "express": "^4.16.2"
  },

Hello World!!」

Hello World」のサンプルを見ながら、簡単なHTTPサーバーを書いてみます。
Hello world example

こんな感じで。
src/simple.js

const express = require("express");
const app = express();

app.get("/", (req, res) => res.send("Hello World"));

app.listen(3000);

リッスンポートは3000で。

package.jsonにはこういう記述にして

  "scripts": {
    "simple": "node src/simple.js"

実行。

$ npm run simple

> hello-rest-api@1.0.0 simple ...
> node src/simple.js

確認。

$ curl http://localhost:3000
Hello World

OKそうですね。というか、簡単…。

ちなみに、Application#listenの戻り値はhttp.Serverらしいので、停止する時はこれを受け取ってstopを呼べばよいようです。
app.listen

こんな感じで。

const httpServer = app.listen(3000);

// ...
httpServer.stop();

JSONを受け取るサーバーを書いてみる

続いて、JSONを受け取るサーバーを書いてみましょう。POSTで受け取ることにします。

こちらを見ると、JSONをリクエストとしてパースするには、body-parserというmiddlewareが必要なようです。
req.body

body-parser - npm

とはいえ、Expressをインストールしていると、そのまま使えるようですが。

ちょっと書いてみましょう。
src/rest-server.js

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

app.use(bodyParser.json());

app.post("/calc", (req, res) => {
    const calc = req.body;

    console.log(`[${new Date()}] request = [${JSON.stringify(calc)}]`);

    const operator = calc.operator;
    const a = calc.a;
    const b = calc.b;

    const calculator = () => {
        if (operator === "+") {
            return a + b;
        } else if (operator === "-") {
            return a - b;
        } else if (operator === "*") {
            return a * b;
        } else if (operator === "/") {
            return a / b;
        } else {
            return `Unknown operator[${operator}]`;
        }
    };
    
    res.json({"result": calculator()});
});

app.listen(3000);

Application#useで、bodyParser#jsonを使うようにすればよい、と。

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

app.use(bodyParser.json());

この状態で、「application/json」なリクエストを受け付けると、JSONパースしてくれるようです。

app.post("/calc", (req, res) => {
    const calc = req.body;

    console.log(`[${new Date()}] request = [${JSON.stringify(calc)}]`);

    const operator = calc.operator;
    const a = calc.a;
    const b = calc.b;

レスポンスは、Response#jsonで返すとJSONにしてくれるようです。

    res.json({"result": calculator()});

このスクリプトを、「npm run」で起動するようにしてみましょう。

  "scripts": {
    "simple": "node src/simple.js",
    "rest": "node src/rest-server.js",

確認。

$ curl -H 'Content-Type: application/json' http://localhost:3000/calc -d '{ "operator": "+", "a": 3, "b": 4 }'
{"result":7}

OKそうです。

テストを書いてみる

蛇足ですが、このサーバーを起動した状態で、テストを書いて確認してみましょう。テスト中にサーバーを起動して停止して…とかいうのは、考えないことにします。

テストのランナーとしてはAVAを
GitHub - avajs/ava: 🚀 Futuristic JavaScript test runner

アサーションにはChaiを使用しました。
Chai

HTTPリクエストの送信には、「request-promise」を使用することにします。
GitHub - request/request-promise: The simplified HTTP request client 'request' with Promise support. Powered by Bluebird.

インストール。

$ npm install --save-dev ava@next chai request request-promise

package.jsonでの依存関係は、このように。

  "devDependencies": {
    "ava": "^1.0.0-beta.1",
    "chai": "^4.1.2",
    "request": "^2.83.0",
    "request-promise": "^4.2.2"
  }

テストコードは、こんな感じで書きました。
test/rest-server-test.js

import test from "ava";
import chai from "chai";
import rq  from "request-promise";

chai.should();

const optionsBasic = {
    method: "POST",
    uri: "http://localhost:3000/calc",
    json: true
};

test("calc plus", t => {
    const options = Object.assign({}, optionsBasic);
    options.body = { operator: "+", a: 8 , b: 3 };
    
    return rq(options)
        .then(response => {
            response.result.should.equal(11);
            t.pass();
        });
});

test("calc minus", t => {
    const options = Object.assign({}, optionsBasic);
    options.body = { operator: "-", a: 8 , b: 3 };
    
    return rq(options)
        .then(response => {
            response.result.should.equal(5);
            t.pass();
        });
});

test("calc multiply", t => {
    const options = Object.assign({}, optionsBasic);
    options.body = { operator: "*", a: 8 , b: 3 };
    
    return rq(options)
        .then(response => {
            response.result.should.equal(24);
            t.pass();
        });
});

test("calc division", t => {
    const options = Object.assign({}, optionsBasic);
    options.body = { operator: "/", a: 8 , b: 2 };
    
    return rq(options)
        .then(response => {
            response.result.should.equal(4);
            t.pass();
        });
});

test("calc invalid operator", t => {
    const options = Object.assign({}, optionsBasic);
    options.body = { operator: "?", a: 8 , b: 3 };
    
    return rq(options)
        .then(response => {
            response.result.should.equal("Unknown operator[?]");
            t.pass();
        });
});

package.jsonでは、AVAを実行するようにして

  "scripts": {
    "simple": "node src/simple.js",
    "rest": "node src/rest-server.js",
    "test": "ava -v"
  },

別コンソールでサーバーを起動した状態で、確認。

$ npm test

> hello-rest-api@1.0.0 test ...
> ava -v


  ✔ calc plus
  ✔ calc minus
  ✔ calc multiply
  ✔ calc division
  ✔ calc invalid operator

  5 tests passed

OKですね。