🏮

Node-RedでPOSTされたJSONをPostgreSQLへ書き込む

に公開

1. 必要なノードのインストール

PostgreSQLを操作するには、専用のコントリビュートノードが必要です。

  1. Node-REDエディタ右上のメニュー()を開き、「パレットの管理」を選択します。
  2. ノードを追加」タブに切り替えます。
  3. 検索ボックスに node-red-contrib-postgresql と入力します。
  4. 表示されたノードの「インストール」ボタンをクリックし、インストールを完了します。

2. フローの作成

以下のノードをパレットからワークスペースに配置し、接続します。

  • http in: HTTPリクエストの受付
  • function: SQLクエリの生成
  • postgres: データベースへのクエリ実行
  • http response: クライアントへの応答
  • catch: エラーハンドリング
  • debug: 動作確認(推奨)

フロー接続図

3. 各ノードの設定詳細

3.1. http in (リクエスト受付)

  • メソッド: POST
  • URL: /write-db (任意のエンドポイント)
  • 名前: POST /write-db

3.2. postgres (データベース接続設定)

このノードで、まずデータベースへの接続情報を設定します。

  1. ノードをダブルクリックし、設定画面を開きます。
  2. サーバー項目の右にある鉛筆アイコンをクリックします。
  3. 接続情報を以下のように入力します。
    • ホスト: localhost (またはPostgreSQLサーバーのIP/ホスト名)
    • ポート: 5432 (デフォルト)
    • ユーザー: (データベースのユーザー名)
    • パスワード: (上記ユーザーのパスワード)
    • データベース: (接続先のデータベース名)
    • 名前: (任意、接続設定の識別名)
  4. 必要に応じて「SSL」タブでSSL接続の設定を行います。
  5. 追加」(または「更新」)ボタンをクリックして接続情報を保存します。
  6. ノード設定画面に戻り、「完了」をクリックします。

3.3. function (クエリ生成)

受け取ったJSONから安全なSQLクエリ(INSERT文)を生成します。

  • 名前: クエリ生成
  • コード:
    // 想定する入力JSON: { "table": "sensors", "data": { "device_id": "A", "temperature": 25.4 } }
    const payload = msg.payload;
    
    // --- 入力値の基本チェック ---
    if (!payload.table || typeof payload.data !== 'object' || Object.keys(payload.data).length === 0) {
        throw new Error("Invalid JSON format. 'table' and 'data' object are required.");
    }
    
    // --- SQLインジェクション対策: テーブル名・列名の検証 ---
    // 英数字とアンダースコアのみを許可する正規表現
    const validateIdentifier = (str) => {
        if (!/^[a-zA-Z0-9_]+$/.test(str)) {
            throw new Error(`Invalid identifier used for table/column name: ${str}`);
        }
        // ダブルクォートで囲み、予約語との衝突を避ける
        return `"${str}"`;
    };
    
    const table = validateIdentifier(payload.table);
    const data = payload.data;
    const columns = Object.keys(data).map(col => validateIdentifier(col));
    const values = Object.values(data);
    
    // --- パラメータ化クエリの生成 ---
    // 値のプレースホルダー($1, $2, ...)を動的に生成
    const placeholders = values.map((_, i) => `$${i + 1}`).join(', ');
    
    // INSERT文を組み立てる (RETURNING * で挿入結果を取得)
    const query = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${placeholders}) RETURNING *;`;
    
    // postgresノードは msg.query と msg.params を参照する
    msg.query = query;
    msg.params = values; // 値はノードが安全にエスケープしてくれる
    
    return msg;
    

3.4. catch (エラーハンドラ)

フロー内で発生したエラーを捕捉します。

  • スコープ: 「現在のフローのノード」を選択

3.5. function (成功レスポンス生成)

データベースへの書き込みが成功した際のHTTPレスポンスを作成します。

  • 名前: 成功レスポンス
  • コード:
    // postgresノードは、INSERT...RETURNINGの結果を配列で返す
    const insertedData = msg.payload[0]; // 挿入された最初の行
    
    msg.statusCode = 201; // 201 Created
    msg.payload = {
        status: "success",
        message: "Data successfully inserted.",
        inserted: insertedData
    };
    return msg;
    

3.6. function (失敗レスポンス生成)

エラーが発生した際のHTTPレスポンスを作成します。catchノードから接続します。

  • 名前: 失敗レスポンス
  • コード:
    // catchノードはエラー情報を msg.error に格納する
    const errorMessage = msg.error.message || "An unknown error occurred.";
    
    // エラー内容に応じてステータスコードを分ける
    // 400: クライアント側のリクエストが不正
    // 500: サーバー側の問題
    if (errorMessage.includes("Invalid JSON") || errorMessage.includes("Invalid identifier")) {
        msg.statusCode = 400; // Bad Request
    } else {
        msg.statusCode = 500; // Internal Server Error
    }
    
    msg.payload = {
        status: "error",
        message: errorMessage
    };
    return msg;
    

3.7. http response

  • このノードは特に設定は不要です。前のノードから渡されたmsgオブジェクト(msg.payloadmsg.statusCode)を元に自動で応答します。

4. テストコード

 curl -X POST http://192.168.1.179:1880/insert \
-H "Content-Type: application/json" \
-d '{"table": "mytable", "data": {"col1": 100, "col2": 200}}'
#{"status":"success","message":"Data successfully inserted.","inserted":{"col1":100,"col2":200}}

Discussion