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
でビルドできます。(以下でやっていましたね。)
あとは、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のオプションとか、色々とネタはありますが、とりあえずそんな感じで!