はじめに
先日、以下の記事を書きました。
【CAP】CAPをBTPのPostgreSQLにデプロイ
上記の記事で紹介したcds-pg、cds-dbmを使うと、CAPで作成したCDSモデルをPostgreSQLにデプロイして使うことができます。
CAPはテーブル、ビューの登録やCRUD処理を自動的に行ってくれるので、開発者がこれらの作業を意識する必要がありません。一方で、「もしCAPを使わなかったら何をする必要があるのか?」についても知っておきたいと思いました。
以下のサンプルプロジェクト(チュートリアル)があったので、これを参考にシンプルなNode.jsアプリを作成して手順を確認してみます。
SAP-samples/cloud-cf-helloworld-nodejs
<シリーズの目次>
- 【BTP】Node.jsとPostgreSQLでシンプルなアプリケーションを作成(1)準備編
- 【BTP】Node.jsとPostgreSQLでシンプルなアプリケーションを作成(2)CRUD処理編
- 【BTP】Node.jsとPostgreSQLでシンプルなアプリケーションを作成(3)Approuter編
- 【BTP】Node.jsとPostgreSQLでシンプルなアプリケーションを作成(4)UI編
ソースコードはGitHubに格納しています。記事と合わせてご参照ください。
この記事のゴール
今回は準備編として、以下の状態を作ります。
- Node.jsアプリケーションとBTPのPostgreSQLのサービスインスタンスがバインドされている
- PostgreSQLにアプリケーションで使うテーブルが登録されている
- Node.jsアプリケーションからBTPのPostgreSQLに接続できる
ステップ
- Node.jsアプリケーションとPostgreSQLのサービスインスタンスをバインド
- PostgreSQLにテーブルを登録
- Node.jsアプリケーションからPostgreSQLに接続
1. Node.jsアプリケーションとPostgreSQLのサービスインスタンスをバインド
1.1. PostgreSQLのサービスインスタンスを登録
以下のコマンドでサービスインタンスを登録します。
cf create-service postgresql-db trial <サービスインスタンス名>
トライアル環境だとPostgreSQLのサービスインスタンスを1つしか作れないので、既存のサービスインスタンスを流用してもよいです。私は登録済のサービスインスタンスcap-posgre-sample-db
を使用しました。
1.2. Node.jsプロジェクトの作成
新規プロジェクト用のフォルダを作成し、その下にsrvフォルダを作成します。
node-postgres-sample
└ srv
srvフォルダに移動し、package.jsonを作成します。
cd node-postgres-sample/srv
npm init --yes
1.3. Dependencyをインストール
以下4つのパッケージをインストールします。
npm i express body-parser pg-promise @sap/xsenv
- pg-promiseはPostgreSQLに接続したり、クエリを実行したりするのに使用します。
- @sap/xsenvはアプリケーションの環境変数にアクセスするのに使用します。
package.jsonにstart
スクリプトを追加し、以下の状態とします。
{
"name": "node-postgres-sample",
"version": "1.0.0",
"description": "",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"@sap/xsenv": "^3.1.0",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"pg-promise": "^10.10.2"
}
}
1.4. server.jsを作成
server.jsファイルを作成します。現段階では、まだデータベースとの接続はしていません。
'use strict';
const express = require('express')
const bodyParser = require('body-parser')
var _db = undefined
const app = express()
app.use(bodyParser.json())
app.get('/', function (req, res) {
res.send("Hello!")
})
var PORT = process.env.PORT || 8088
var server = app.listen(PORT, function() {
const host = server.address().address
const port = server.address().port
console.log(`Example app listening at http://${host}:${port}`)
})
npm start
を実行し、http://localhost:8088/
を開くと以下の画面が表示されます。
1.5. mta.yamlを作成
プロジェクト直下にmta.yamlファイルを作成します。
_schema-version: '3.1'
ID: node-postgres-sample
version: 1.0.0
modules:
- name: node-postgres-sample-srv
type: nodejs
path: srv
provides:
- name: srv-api
properties:
srv-url: ${default-url}
build-parameters:
ignore: ["node_modules/"]
requires:
- name: cap-posgre-sample-db
resources:
- name: cap-posgre-sample-db
parameters:
service: postgresql-db
service-plan: trial
skip-service-updates:
parameters: true
type: org.cloudfoundry.existing-service #登録済サービスインスタンスのため、existing-serviceとする
この段階で、プロジェクトの構成は以下のようになっています。
node-postgres-sample
└ srv
|- package.json
└ server.js
└ mta.yaml
1.6. Cloud Foundryにデプロイ
プロジェクトのルートで以下のコマンドを実行してCloud Foundryにデプロイします。
mbt build
cf deploy mta_archives/node-postgres-sample_1.0.0.mtar
2. PostgreSQLにテーブルを登録
PostgreSQLにアプリケーションで使うテーブルを登録します。このために、PostgreSQLにSSHを使って接続し、コマンドラインを使って操作を行います。ツールのインストール方法、およびPostgreSQLへの接続方法については、こちらの記事をご参照ください。
※PostgreSQLのサービスインスタンスに接続するため、ホストとなるアプリケーションが必要となります。そのためにステップ1を先に実施しました。今回、ホストとなるアプリケーションはnode-postgres-sample-srv
となります。
2.1. テーブルを登録
以下のSQLを実行し、products
テーブルを登録します。
CREATE TABLE products (
id serial PRIMARY KEY,
name varchar(100),
price integer
);
2.2. テーブルにデータを登録
以下のSQLを実行し、テーブルにデータを登録します。
idはserialのため自動採番されるので、指定する必要がありません。
INSERT INTO products(name, price) VALUES ('banana', 100);
レコードが登録されたことを確認します。
SELECT * FROM products;
id | name | price
----+--------+-------
1 | banana | 100
(1 行)
3. Node.jsアプリケーションからPostgreSQLに接続
srvフォルダの配下にdb-conn.jsというファイルを作成します。ここにはデータベースへの接続を行うための処理を記述します。
プロジェクトの構成は以下のようになります。
node-postgres-sample
└ srv
|- package.json
|- db-conn.js
└ server.js
└ mta.yaml
'use strict'
const xsenv = require('@sap/xsenv')
function getConfig () {
var config = {}
if (process.env.VCAP_SERVICES) {
config = {
connectionString: xsenv.cfServiceCredentials('cap-posgre-sample-db').uri,
ssl: { rejectUnauthorized: false }
}
} else {
console.log('running locally is not supported')
}
return config;
}
function getDB (cb) {
let pgp = require('pg-promise')({
// Initialization Options
})
var db = pgp(getConfig())
let sql = `SELECT id FROM products WHERE id = 1;`
db.query(sql)
.then((result) => {
console.log('database initialized', result)
cb(null, db)
return
})
.catch((err) => {
console.log(err)
cb(err, null)
return
})
}
module.exports = {
getDB: getDB
}
server.js
も変更します。起動時にデータベースへの接続をチェックするようにします。
'use strict';
const express = require('express')
const bodyParser = require('body-parser')
const dbConn = require('./db-conn')
var _db = undefined
const app = express()
app.use(bodyParser.json())
app.get("/", function (req, res) {
res.send("Hello!")
})
function setDBCallback(error, db) {
if (error !== null) {
console.log('error when fetching the DB connection ' + JSON.stringify(error))
return
}
_db = db;
}
var PORT = process.env.PORT || 8088
var server = app.listen(PORT, function() {
const host = server.address().address
const port = server.address().port
console.log(`Example app listening at http://${server}:${port}`)
dbConn.getDB(setDBCallback);
})
以上でビルド、デプロイを行います。
※デプロイするときにcf logs node-postgres-sample-srv
でアプリケーションのログを確認します。以下のようにエラーなく起動すれば、データベースへの接続はうまくいっています。
2021-05-27T15:27:58.72+0900 [CELL/0] OUT Downloaded droplet (22.9M)
2021-05-27T15:27:58.72+0900 [CELL/0] OUT Starting health monitoring of container
2021-05-27T15:27:59.71+0900 [APP/PROC/WEB/0] OUT > node-postgres-sample@1.0.0 start /home/vcap/app
2021-05-27T15:27:59.71+0900 [APP/PROC/WEB/0] OUT > node server.js
2021-05-27T15:27:59.95+0900 [APP/PROC/WEB/0] OUT Example app listening at http://[object Object]:8080
2021-05-27T15:28:00.09+0900 [APP/PROC/WEB/0] OUT database initialized [ { id: 1 } ]
2021-05-27T15:28:01.59+0900 [CELL/0] OUT Container became healthy
以下の行で、データが取れていることが確認できます。
2021-05-27T15:28:00.09+0900 [APP/PROC/WEB/0] OUT database initialized [ { id: 1 } ]
(参考)接続に関する試行錯誤
参考にしたソースでは、データベースへ接続する処理は以下のようになっていました。
function returnUriToDB() {
var uri = '';
if (process.env.VCAP_SERVICES) {
// running in cloud
uri = xsenv.cfServiceCredentials('sapcpcfhw-db').uri;
} else {
console.log('running locally is not supported');
}
return uri;
}
function getDB(cb) {
let pgp = require('pg-promise')({
// Initialization Options
});
var db = pgp(returnUriToDB());
...
}
しかし、アプリケーションを起動する際にerror: no pg_hba.conf entry for host "xx.xx.xx.xx" user "xxxx", database "xxxx", SSL off
というエラーとなってしまいました。
調べたところ、本来SSLで接続すべきところがSSLになっていないため起こるエラーのようでした。
No pg_hba.conf entry for host in SAP CF PostgreSQL Hyperscaler
こちらのQAを参考に接続用のURLに?ssl=true
を追加したところ、エラーの内容が変わりました。
uri = xsenv.cfServiceCredentials('cap-posgre-sample-db').uri + `?ssl=true`
今度は'self signed certificate'を使っていることによるエラーのようです。
対応として、こちらを参考に以下のconfigオブジェクトを作って渡すようにしました。本番環境ではセキュリティ的によくなさそうですが、まずは接続できたのでよしとしました。
config = {
connectionString: xsenv.cfServiceCredentials('cap-posgre-sample-db').uri,
ssl: { rejectUnauthorized: false }
}
まとめ
この記事では、以下を実施しました。
- Node.jsアプリケーションとPostgreSQLのサービスインスタンスをバインド
- PostgreSQLにテーブルを登録
- Node.jsアプリケーションからPostgreSQLに接続
CAPを使わない場合、データベースに接続するために何をしなければならないかがわかった回でした。次回はテーブルに対するCRUD処理を実装したいと思います。