EatSmartシステム部ブログ

ウェブサイトの開発や運営に関する情報です。

docker+Node.js(express+pg-promise)で簡単なAPIサーバーを作成

インフラの構成を変更して自由度が上がったので、サービスに必要な簡単なAPIをdocker+Node.jsで構築してみました。 eatsmart.hatenablog.com

今までNode.jsは、sassを使うために呪文のようにnpmを使った程度なので、一から調べながら始めたのですが、結構簡単にできたなあという印象です。

まあ、簡単な処理しかしないAPIですが。

環境の構築

まずは、環境を構築します。

Node.jsのプロジェクトを作るために、プロジェクトのルートフォルダで

$ npm init

と実行します。プロジェクトの諸元を聞かれるので、適当に答えます。 これにより、プロジェクトフォルダにpackage.jsonファイルが生成されます。

次に、Node.jsでRESTサービスを作るには、 Express - Node.js Web アプリケーション・フレームワーク というライブラリを使用するのが簡単らしいので、express(とbody-parser)をプロジェクトにインストールします。

$ npm install express --save
$ npm install body-parser --save

ここで、--saveオプションを付けることで、プロジェクトのpackage.jsonに依存関係を追記してくれるようです。

package.json

  "dependencies": {
    "body-parser": "^1.18.3",
    "express": "^4.16.3",
  }

さらに、今回はDBとしてPostgreSQLに接続するために、pg_promiseというライブラリを使用します。

$ npm install pg-promise --save

これで、package.jsonは、以下のようになりました。

  "dependencies": {
    "body-parser": "^1.18.3",
    "express": "^4.16.3",
    "pg-promise": "^8.4.6"
  }

実装

次は実装です。この辺はNode.js+expressの作法を調査して則ることにしました。

expressではリクエストのエンドポイントをディレクトリ単位でルーティングするのが良さそうですが、今回は現時点では機能が少ないので、アプリケーション本体のapp.jsと、データ(今回はタイムライン)の一覧を返すアクションのtimeine.jsで構成することにしました。

プロジェクトのルートから

/
├─app/
│ ├─app.js
│ └─routes/
│   └─timeline.js
└─package.json

という構成にしました。 (express-generatorというのを使って構成するのも良さそう。)

あとは、ざっくりapp.jsは

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

app.use(bodyParser.urlencoded({ extended: true }));
/* 開発環境でのCORS対応のため
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});
*/
app.use('/timeline',require('./routes/timeline'));

//サーバ起動
const port = process.env.PORT || 3000;
app.listen(port);
console.log('server start listen on port ' + port);

としました。

/timeline以下をtimeline.jsにルーティングする設定をして、サーバーを起動する感じです。

途中のコメントアウトは、ローカル環境でwebサーバーとAPIサーバーを立てるとドメイン(localhostのポート番号)を違えるため、CORSに引っかかってしまうので許可するための設定です。

timeline.jsは、

const express = require('express');
const router = express.Router();
const pgp = require('pg-promise')();
const db = pgp(process.env.DB_CONNECT_STRING);

router.get('/list', function (req, res) {
  let name = req.query.name;

  db.any("SELECT TIMELINE_TEXT FROM TIMELINE_TABLE WHERE TIMELINE_NAME=$1",[name])
    .then(function (data) {
      let list = [];
      for(let result of data){
        list.push(result['timeline_text']);
      }
      res.json(list);
    });
});

module.exports = router;

としています。/timeline配下の/listを処理するようにしています。

DB接続は、環境変数で与えるようにしました。 クエリパラメータで与えられたnameという値をSQLプレースホルダーに渡して、結果の一覧をJSONで返却しています。

動作確認

DB接続先文字列を環境変数で与えるようにしたので、APIサーバーの起動は、プロジェクトルートフォルダで

$ export DB_CONNECT_STRING=postgres://[user]:[password]@[server]:[port]/[DB名]
$ node app/app.js

とします。

curlコマンドで動作の確認ができました。

$ curl http://server:3000/timeline/list?name=test
["timeline text 1","timeline text 2","timeline text 3","timeline text 4","timeline text 5"]

dockerコンテナ化

次に、本番サーバー用に、dockerコンテナ化します。 公式のnodeのdockerイメージがあるので、プロジェクトフォルダを詰め込んでnpm installするだけで良さそうです。

Dockerfileは以下のようにしました。

FROM node:10
MAINTAINER H.Miyake

ADD app ./app
ADD package.json ./

RUN npm install

CMD ["node","app/app.js"]

このファイルをプロジェクトのルートフォルダに置いて、gitにコミットします。 その後、(最近git-flow的にやっているので)releaseブランチを切って、

$ docker build -t [tag_name] http://XXXXX/XXXX/XXXX.git#release

でビルドできます。(以下でやっていましたね。)

eatsmart.hatenablog.com

あとは、imageをpushしておいて、本番サーバーで

$ docker pull [tag_name]
$ docker run -d \
--name [name] \
--restart=always \
--network=[docker network名] \
-e DB_CONNECT_STRING=postgres://[user]:[password]@[server]:[port]/[DB名] \
-it [tag_name]

で起動できます。

docker runのオプションとか、色々とネタはありますが、とりあえずそんな感じで!