🌐 Node-RED HTTP ノードガイド

📚 目次

📖 1. 概要

Node-RED には HTTP通信に関連する 3つのノード が用意されています。これらを組み合わせることで、WebサーバーやAPIクライアントの機能を簡単に実装できます。

📥 HTTP In

HTTP In

役割: HTTPリクエストを受信(サーバー機能)

URLエンドポイントを定義し、外部からのリクエストを受け付けます

📤 HTTP Response

HTTP Response

役割: HTTPレスポンスを送信(サーバー機能)

リクエストに対してステータスコードとデータを返します

🔀 HTTP Request

HTTP Request

役割: 外部APIにリクエストを送信(クライアント機能)

外部のWebサービスやAPIを呼び出します

⚠️ 重要: HTTP In と HTTP Response は必ずペアで使用します。HTTP Response がないと、リクエスト元はタイムアウトまで待ち続けます。

基本フローパターン

サーバー機能(API提供):

HTTP In 処理 HTTP Response

クライアント機能(API呼び出し):

Inject HTTP Request Debug

⚙️ 2. HTTP In ノード

主要プロパティ

プロパティ説明
メソッド受け付けるHTTPメソッドGET, POST, PUT, DELETE
URLエンドポイントのパス/api/data, /users/:id
名前ノードの表示名GET /api/sensors

URLパラメータ

URLに :パラメータ名 を含めると、動的なパスを定義できます:

// URL設定: /users/:userId/posts/:postId // リクエスト: GET /users/123/posts/456 // msg.req.params の内容: { "userId": "123", "postId": "456" }

msg に設定される情報

プロパティ説明
msg.payloadリクエストボディPOSTで送られたJSONなど
msg.req.paramsURLパラメータ{id: "123"}
msg.req.queryクエリパラメータ{page: "1", limit: "10"}
msg.req.headersリクエストヘッダー{"content-type": "..."}
msg.req.bodyリクエストボディ(生)POSTデータ

エンドポイントのアクセス方法

http://[Node-REDのホスト]:1880/[設定したURL]

例: URLを /api/hello に設定した場合

http://localhost:1880/api/hello

⚙️ 3. HTTP Response ノード

主要プロパティ

プロパティ説明デフォルト
ステータスコードHTTPステータスコード(msg.statusCodeを使用)
ヘッダ追加ヘッダー(msg.headersを使用)

msgで設定する情報

プロパティ説明
msg.payloadレスポンスボディJSON、HTML、テキスト
msg.statusCodeステータスコード200, 201, 400, 404, 500
msg.headersレスポンスヘッダー{"Content-Type": "..."}

主要なHTTPステータスコード

コード意味用途
200OK成功(デフォルト)
201Createdリソース作成成功
400Bad Requestリクエスト形式エラー
401Unauthorized認証必要
404Not Foundリソースが存在しない
500Internal Server Errorサーバー内部エラー

⚙️ 4. HTTP Request ノード

主要プロパティ

プロパティ説明デフォルト
メソッドHTTPメソッド(GET/POST/PUT/DELETE等)GET
URLリクエスト先のURL(空)
ペイロード(送信)リクエストボディの扱いIgnore
出力形式レスポンスの形式UTF-8文字列
ヘッダ追加HTTPヘッダー(空)

Method(HTTPメソッド)オプション

オプション用途ボディ
GETデータ取得、検索なし
POSTデータ送信、新規作成あり
PUTデータ更新(全体置換)あり
DELETEデータ削除任意
HEADヘッダー情報のみ取得なし
- set by msg.method -動的に指定可変

Return(レスポンス形式)オプション

オプション説明用途
UTF-8文字列文字列として受信テキスト、HTML、JSON
バイナリバッファBufferオブジェクト画像、ファイル
JSONオブジェクト自動でJSONパースREST API

動的なURL設定

URLを空にすると、msg.url でURLを動的に指定できます:

// Function ノードで動的にURL設定 msg.url = "https://api.example.com/users/" + msg.payload.userId; return msg;

動的なヘッダー設定

// Function ノードでヘッダーを設定 msg.headers = { "Content-Type": "application/json", "Authorization": "Bearer " + flow.get("apiToken") }; return msg;

APIテスト用リソース

以下の認証不要で安定した公開APIを使ってテストできます:

🌍 World Time API

世界各地の現在時刻を取得

https://worldtimeapi.org/api/timezone/Asia/Tokyo

🔧 httpbin.org

HTTPリクエストのテスト・デバッグ用

https://httpbin.org/get, /post, /status/200

📝 JSONPlaceholder

REST APIのテスト用(TODOリスト、投稿など)

https://jsonplaceholder.typicode.com/todos/1

🔧 5. 実用的な使用パターン

📥 サンプルフローのインポート方法:

  1. 下のサンプルフローJSONをコピー
  2. Node-REDエディタで メニュー → 読み込み を選択
  3. JSONをペーストして「読み込み」をクリック

このサンプルフローには、以下で説明するパターンの実例が含まれています。

💡 テスト方法:

パターン1: シンプルなGET API

GET /api/hello レスポンス設定 Response

用途: 固定データや状態を返すシンプルなエンドポイント

パターン2: POST でデータ受信

POST /api/data データ処理 Response

用途: センサーデータの受信、フォーム送信の処理

パターン3: 外部API呼び出し

トリガー HTTP Request データ整形 結果表示

用途: 外部APIからのデータ取得、Webhook連携

パターン4: エラーハンドリング

リクエスト API呼出 ステータス判定 成功/失敗

用途: msg.statusCode でHTTPステータスを確認し、エラー時の処理を分岐

パターン5: HTMLページの提供

GET /page HTML生成 Response

用途: 動的なWebページ、ダッシュボード

📥 サンプルフローJSON(クリックで展開)
[ { "id": "http_sample_tab", "type": "tab", "label": "HTTP ノード サンプル", "disabled": false, "info": "" }, { "id": "http_comment1", "type": "comment", "z": "http_sample_tab", "name": "━━━ 基本: Hello World API ━━━", "info": "GET http://localhost:1880/api/hello", "x": 190, "y": 40, "wires": [] }, { "id": "http_in_hello", "type": "http in", "z": "http_sample_tab", "name": "GET /api/hello", "url": "/api/hello", "method": "get", "upload": false, "skipBodyParsing": false, "swaggerDoc": "", "x": 160, "y": 100, "wires": [["http_change_hello"]] }, { "id": "http_change_hello", "type": "change", "z": "http_sample_tab", "name": "レスポンス設定", "rules": [ {"t": "set", "p": "payload", "pt": "msg", "to": "{\"message\":\"Hello from Node-RED!\",\"timestamp\":\"\"}", "tot": "json"}, {"t": "set", "p": "payload.timestamp", "pt": "msg", "to": "", "tot": "date"}, {"t": "set", "p": "headers", "pt": "msg", "to": "{\"Content-Type\":\"application/json\"}", "tot": "json"} ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 380, "y": 100, "wires": [["http_response_hello", "http_debug_hello"]] }, { "id": "http_response_hello", "type": "http response", "z": "http_sample_tab", "name": "Response", "statusCode": "200", "headers": {}, "x": 580, "y": 100, "wires": [] }, { "id": "http_debug_hello", "type": "debug", "z": "http_sample_tab", "name": "リクエスト確認", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "\"リクエスト受信\"", "statusType": "str", "x": 600, "y": 140, "wires": [] }, { "id": "http_comment2", "type": "comment", "z": "http_sample_tab", "name": "━━━ POST: データ受信API ━━━", "info": "POST http://localhost:1880/api/data", "x": 190, "y": 200, "wires": [] }, { "id": "http_in_post", "type": "http in", "z": "http_sample_tab", "name": "POST /api/data", "url": "/api/data", "method": "post", "upload": false, "skipBodyParsing": false, "swaggerDoc": "", "x": 160, "y": 260, "wires": [["http_func_post"]] }, { "id": "http_func_post", "type": "function", "z": "http_sample_tab", "name": "データ処理", "func": "// 受信データを処理\nvar receivedData = msg.payload;\n\n// データを保存(flow変数)\nvar dataStore = flow.get('dataStore') || [];\ndataStore.push({\n id: Date.now(),\n data: receivedData,\n receivedAt: new Date().toISOString()\n});\nflow.set('dataStore', dataStore);\n\n// レスポンス作成\nmsg.payload = {\n success: true,\n message: \"データを受信しました\",\n received: receivedData,\n totalCount: dataStore.length\n};\nmsg.statusCode = 201;\nmsg.headers = {\"Content-Type\": \"application/json\"};\n\nreturn msg;", "outputs": 1, "timeout": 0, "initialize": "", "finalize": "", "libs": [], "x": 350, "y": 260, "wires": [["http_response_post", "http_debug_post"]] }, { "id": "http_response_post", "type": "http response", "z": "http_sample_tab", "name": "Response", "statusCode": "", "headers": {}, "x": 540, "y": 260, "wires": [] }, { "id": "http_debug_post", "type": "debug", "z": "http_sample_tab", "name": "受信データ", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "\"データ受信\"", "statusType": "str", "x": 550, "y": 300, "wires": [] }, { "id": "http_comment3", "type": "comment", "z": "http_sample_tab", "name": "━━━ URLパラメータ: /users/:id ━━━", "info": "GET http://localhost:1880/api/users/1", "x": 200, "y": 360, "wires": [] }, { "id": "http_in_users", "type": "http in", "z": "http_sample_tab", "name": "GET /api/users/:id", "url": "/api/users/:id", "method": "get", "upload": false, "skipBodyParsing": false, "swaggerDoc": "", "x": 170, "y": 420, "wires": [["http_func_users"]] }, { "id": "http_func_users", "type": "function", "z": "http_sample_tab", "name": "ユーザー検索", "func": "// URLパラメータからIDを取得\nvar userId = msg.req.params.id;\n\n// サンプルユーザーデータ\nvar users = {\n \"1\": {name: \"田中太郎\", email: \"tanaka@example.com\"},\n \"2\": {name: \"鈴木花子\", email: \"suzuki@example.com\"},\n \"3\": {name: \"佐藤次郎\", email: \"sato@example.com\"}\n};\n\n// ユーザー検索\nif (users[userId]) {\n msg.payload = {\n success: true,\n user: { id: userId, name: users[userId].name, email: users[userId].email }\n };\n msg.statusCode = 200;\n} else {\n msg.payload = {\n success: false,\n error: \"ユーザーが見つかりません\",\n requestedId: userId\n };\n msg.statusCode = 404;\n}\n\nmsg.headers = {\"Content-Type\": \"application/json\"};\nreturn msg;", "outputs": 1, "timeout": 0, "initialize": "", "finalize": "", "libs": [], "x": 380, "y": 420, "wires": [["http_response_users", "http_debug_users"]] }, { "id": "http_response_users", "type": "http response", "z": "http_sample_tab", "name": "Response", "statusCode": "", "headers": {}, "x": 580, "y": 420, "wires": [] }, { "id": "http_debug_users", "type": "debug", "z": "http_sample_tab", "name": "検索結果", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "req.params.id", "statusType": "msg", "x": 570, "y": 460, "wires": [] }, { "id": "http_comment4", "type": "comment", "z": "http_sample_tab", "name": "━━━ 外部API呼び出し ━━━", "info": "World Time APIで時刻取得", "x": 180, "y": 520, "wires": [] }, { "id": "http_inject_api", "type": "inject", "z": "http_sample_tab", "name": "東京の時刻取得", "props": [], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 160, "y": 580, "wires": [["http_request_time"]] }, { "id": "http_request_time", "type": "http request", "z": "http_sample_tab", "name": "World Time API", "method": "GET", "ret": "obj", "paytoqs": "ignore", "url": "https://worldtimeapi.org/api/timezone/Asia/Tokyo", "tls": "", "persist": false, "proxy": "", "insecureHTTPParser": false, "authType": "", "senderr": false, "headers": [], "x": 360, "y": 580, "wires": [["http_func_time"]] }, { "id": "http_func_time", "type": "function", "z": "http_sample_tab", "name": "時刻を整形", "func": "// APIレスポンスから時刻情報を抽出\nvar datetime = msg.payload.datetime;\nvar timezone = msg.payload.timezone;\n\nmsg.payload = {\n timezone: timezone,\n datetime: datetime,\n formatted: new Date(datetime).toLocaleString('ja-JP')\n};\n\nreturn msg;", "outputs": 1, "timeout": 0, "initialize": "", "finalize": "", "libs": [], "x": 550, "y": 580, "wires": [["http_debug_time"]] }, { "id": "http_debug_time", "type": "debug", "z": "http_sample_tab", "name": "時刻表示", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "payload.formatted", "statusType": "msg", "x": 720, "y": 580, "wires": [] }, { "id": "http_comment5", "type": "comment", "z": "http_sample_tab", "name": "━━━ エラーハンドリング ━━━", "info": "ステータスコードで分岐", "x": 180, "y": 640, "wires": [] }, { "id": "http_inject_error", "type": "inject", "z": "http_sample_tab", "name": "404テスト", "props": [], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 150, "y": 700, "wires": [["http_request_error"]] }, { "id": "http_request_error", "type": "http request", "z": "http_sample_tab", "name": "404エラーテスト", "method": "GET", "ret": "obj", "paytoqs": "ignore", "url": "https://httpbin.org/status/404", "tls": "", "persist": false, "proxy": "", "insecureHTTPParser": false, "authType": "", "senderr": false, "headers": [], "x": 360, "y": 700, "wires": [["http_switch_status"]] }, { "id": "http_switch_status", "type": "switch", "z": "http_sample_tab", "name": "ステータス判定", "property": "statusCode", "propertyType": "msg", "rules": [ {"t": "btwn", "v": "200", "vt": "num", "v2": "299", "v2t": "num"}, {"t": "btwn", "v": "400", "vt": "num", "v2": "499", "v2t": "num"}, {"t": "gte", "v": "500", "vt": "num"} ], "checkall": "false", "repair": false, "outputs": 3, "x": 560, "y": 700, "wires": [["http_debug_success"], ["http_debug_client_err"], ["http_debug_server_err"]] }, { "id": "http_debug_success", "type": "debug", "z": "http_sample_tab", "name": "成功 (2xx)", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "statusCode", "targetType": "msg", "statusVal": "\"成功\"", "statusType": "str", "x": 750, "y": 660, "wires": [] }, { "id": "http_debug_client_err", "type": "debug", "z": "http_sample_tab", "name": "クライアントエラー (4xx)", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "statusCode", "targetType": "msg", "statusVal": "\"クライアントエラー\"", "statusType": "str", "x": 790, "y": 700, "wires": [] }, { "id": "http_debug_server_err", "type": "debug", "z": "http_sample_tab", "name": "サーバーエラー (5xx)", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "statusCode", "targetType": "msg", "statusVal": "\"サーバーエラー\"", "statusType": "str", "x": 780, "y": 740, "wires": [] }, { "id": "http_comment6", "type": "comment", "z": "http_sample_tab", "name": "━━━ HTMLページ提供 ━━━", "info": "GET http://localhost:1880/page", "x": 180, "y": 800, "wires": [] }, { "id": "http_in_page", "type": "http in", "z": "http_sample_tab", "name": "GET /page", "url": "/page", "method": "get", "upload": false, "skipBodyParsing": false, "swaggerDoc": "", "x": 140, "y": 860, "wires": [["http_in_template"]] }, { "id": "http_in_template", "type": "template", "z": "http_sample_tab", "name": "HTML生成", "field": "payload", "fieldType": "msg", "format": "html", "syntax": "mustache", "template": "\n\n\n \n Node-RED Page\n \n\n\n
\n

Node-RED Web Page

\n

This page is dynamically generated by Node-RED.

\n
\n

Current Time: {{timestamp}}

\n

Request Host: {{req.headers.host}}

\n
\n
\n\n", "output": "str", "x": 310, "y": 860, "wires": [["http_in_change_html"]] }, { "id": "http_in_change_html", "type": "change", "z": "http_sample_tab", "name": "タイムスタンプ設定", "rules": [ {"t": "set", "p": "timestamp", "pt": "msg", "to": "", "tot": "date"}, {"t": "set", "p": "headers", "pt": "msg", "to": "{\"Content-Type\":\"text/html; charset=utf-8\"}", "tot": "json"} ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 510, "y": 860, "wires": [["http_response_page"]] }, { "id": "http_response_page", "type": "http response", "z": "http_sample_tab", "name": "Response", "statusCode": "200", "headers": {}, "x": 700, "y": 860, "wires": [] }, { "id": "http_comment7", "type": "comment", "z": "http_sample_tab", "name": "━━━ セルフテスト ━━━", "info": "HTTP Requestで自分のAPIを呼び出し", "x": 170, "y": 920, "wires": [] }, { "id": "http_inject_selftest", "type": "inject", "z": "http_sample_tab", "name": "APIテスト", "props": [], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 140, "y": 980, "wires": [["http_request_selftest"]] }, { "id": "http_request_selftest", "type": "http request", "z": "http_sample_tab", "name": "自分のAPIを呼出", "method": "GET", "ret": "obj", "paytoqs": "ignore", "url": "http://localhost:1880/api/hello", "tls": "", "persist": false, "proxy": "", "insecureHTTPParser": false, "authType": "", "senderr": false, "headers": [], "x": 350, "y": 980, "wires": [["http_debug_selftest"]] }, { "id": "http_debug_selftest", "type": "debug", "z": "http_sample_tab", "name": "テスト結果", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "payload.message", "statusType": "msg", "x": 550, "y": 980, "wires": [] } ]

📝 6. 演習問題

演習1: シンプルなJSON API 初級

📋 課題: GET /api/status でシステムの状態を返すAPIを作成してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント

HTTP In ノード:

  • Method: GET
  • URL: /api/status

Change ノード:

  • msg.payload に JSONオブジェクトを設定
  • msg.headers に Content-Type を設定

HTTP Response ノード: デフォルト設定でOK

✅ 解答例フロー
[ {"id": "ex1_tab", "type": "tab", "label": "演習1"}, {"id": "ex1_http_in", "type": "http in", "z": "ex1_tab", "name": "GET /api/status", "url": "/api/status", "method": "get", "upload": false, "skipBodyParsing": false, "swaggerDoc": "", "x": 170, "y": 100, "wires": [["ex1_change"]]}, {"id": "ex1_change", "type": "change", "z": "ex1_tab", "name": "レスポンス設定", "rules": [{"t": "set", "p": "payload", "pt": "msg", "to": "{\"status\":\"running\",\"version\":\"1.0.0\"}", "tot": "json"}, {"t": "set", "p": "headers", "pt": "msg", "to": "{\"Content-Type\":\"application/json\"}", "tot": "json"}], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 390, "y": 100, "wires": [["ex1_response"]]}, {"id": "ex1_response", "type": "http response", "z": "ex1_tab", "name": "Response", "statusCode": "200", "headers": {}, "x": 580, "y": 100, "wires": []} ]

演習2: 外部APIからデータ取得 初級

📋 課題: World Time API を使って、ニューヨークの現在時刻を取得し、Debugノードに表示してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント

Inject ノード: デフォルト設定でOK(タイムスタンプを送信)

HTTP Request ノード:

  • Method: GET
  • URL: 上記URLを入力
  • Return: JSONオブジェクトを解析

Debug ノード: msg.payload全体を表示

✅ 解答例フロー
[ {"id": "ex2_tab", "type": "tab", "label": "演習2"}, {"id": "ex2_inject", "type": "inject", "z": "ex2_tab", "name": "NY時刻取得", "props": [], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 150, "y": 100, "wires": [["ex2_http"]]}, {"id": "ex2_http", "type": "http request", "z": "ex2_tab", "name": "World Time API", "method": "GET", "ret": "obj", "paytoqs": "ignore", "url": "https://worldtimeapi.org/api/timezone/America/New_York", "tls": "", "persist": false, "proxy": "", "insecureHTTPParser": false, "authType": "", "senderr": false, "headers": [], "x": 360, "y": 100, "wires": [["ex2_debug"]]}, {"id": "ex2_debug", "type": "debug", "z": "ex2_tab", "name": "NY時刻", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "payload.datetime", "statusType": "msg", "x": 550, "y": 100, "wires": []} ]

演習3: URLパラメータを使ったAPI 中級

📋 課題: GET /api/items/:id で商品情報を返すAPIを作成してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント

URLパラメータの取得: msg.req.params.id

商品データの定義: Functionノード内でオブジェクトとして定義

存在チェック: if文で商品の存在を確認し、msg.statusCodeを分岐

✅ 解答例フロー
[ {"id": "ex3_tab", "type": "tab", "label": "演習3"}, {"id": "ex3_http_in", "type": "http in", "z": "ex3_tab", "name": "GET /api/items/:id", "url": "/api/items/:id", "method": "get", "upload": false, "skipBodyParsing": false, "swaggerDoc": "", "x": 180, "y": 100, "wires": [["ex3_func"]]}, {"id": "ex3_func", "type": "function", "z": "ex3_tab", "name": "商品検索", "func": "var itemId = msg.req.params.id;\n\nvar items = {\n \"1\": {name: \"りんご\", price: 150},\n \"2\": {name: \"みかん\", price: 100},\n \"3\": {name: \"バナナ\", price: 120}\n};\n\nif (items[itemId]) {\n msg.payload = {\n success: true,\n item: {id: itemId, name: items[itemId].name, price: items[itemId].price}\n };\n msg.statusCode = 200;\n} else {\n msg.payload = {\n success: false,\n error: \"商品が見つかりません\",\n requestedId: itemId\n };\n msg.statusCode = 404;\n}\n\nmsg.headers = {\"Content-Type\": \"application/json\"};\nreturn msg;", "outputs": 1, "timeout": 0, "initialize": "", "finalize": "", "libs": [], "x": 400, "y": 100, "wires": [["ex3_response"]]}, {"id": "ex3_response", "type": "http response", "z": "ex3_tab", "name": "Response", "statusCode": "", "headers": {}, "x": 580, "y": 100, "wires": []} ]

演習4: CRUD APIの作成 上級

📋 課題: メモを管理するシンプルなREST APIを作成してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント

データ保存: flow.set('notes', notesArray)

データ取得: flow.get('notes') || []

新規ID生成: Date.now()

削除: array.filter() で該当IDを除外

✅ 解答例フロー
[ {"id": "ex4_tab", "type": "tab", "label": "演習4"}, {"id": "ex4_get_all", "type": "http in", "z": "ex4_tab", "name": "GET /api/notes", "url": "/api/notes", "method": "get", "upload": false, "skipBodyParsing": false, "swaggerDoc": "", "x": 160, "y": 80, "wires": [["ex4_func_get_all"]]}, {"id": "ex4_func_get_all", "type": "function", "z": "ex4_tab", "name": "全件取得", "func": "var notes = flow.get('notes') || [];\nmsg.payload = {notes: notes, count: notes.length};\nmsg.headers = {\"Content-Type\": \"application/json\"};\nreturn msg;", "outputs": 1, "timeout": 0, "initialize": "", "finalize": "", "libs": [], "x": 370, "y": 80, "wires": [["ex4_response"]]}, {"id": "ex4_post", "type": "http in", "z": "ex4_tab", "name": "POST /api/notes", "url": "/api/notes", "method": "post", "upload": false, "skipBodyParsing": false, "swaggerDoc": "", "x": 170, "y": 140, "wires": [["ex4_func_post"]]}, {"id": "ex4_func_post", "type": "function", "z": "ex4_tab", "name": "新規作成", "func": "var notes = flow.get('notes') || [];\nvar newNote = {\n id: Date.now(),\n text: msg.payload.text || \"(内容なし)\",\n createdAt: new Date().toISOString()\n};\nnotes.push(newNote);\nflow.set('notes', notes);\nmsg.payload = {success: true, note: newNote};\nmsg.statusCode = 201;\nmsg.headers = {\"Content-Type\": \"application/json\"};\nreturn msg;", "outputs": 1, "timeout": 0, "initialize": "", "finalize": "", "libs": [], "x": 370, "y": 140, "wires": [["ex4_response"]]}, {"id": "ex4_get_one", "type": "http in", "z": "ex4_tab", "name": "GET /api/notes/:id", "url": "/api/notes/:id", "method": "get", "upload": false, "skipBodyParsing": false, "swaggerDoc": "", "x": 180, "y": 200, "wires": [["ex4_func_get_one"]]}, {"id": "ex4_func_get_one", "type": "function", "z": "ex4_tab", "name": "1件取得", "func": "var notes = flow.get('notes') || [];\nvar noteId = parseInt(msg.req.params.id);\nvar note = notes.find(function(n) { return n.id === noteId; });\nif (note) {\n msg.payload = {success: true, note: note};\n msg.statusCode = 200;\n} else {\n msg.payload = {success: false, error: \"Not found\"};\n msg.statusCode = 404;\n}\nmsg.headers = {\"Content-Type\": \"application/json\"};\nreturn msg;", "outputs": 1, "timeout": 0, "initialize": "", "finalize": "", "libs": [], "x": 370, "y": 200, "wires": [["ex4_response"]]}, {"id": "ex4_delete", "type": "http in", "z": "ex4_tab", "name": "DELETE /api/notes/:id", "url": "/api/notes/:id", "method": "delete", "upload": false, "skipBodyParsing": false, "swaggerDoc": "", "x": 190, "y": 260, "wires": [["ex4_func_delete"]]}, {"id": "ex4_func_delete", "type": "function", "z": "ex4_tab", "name": "削除", "func": "var notes = flow.get('notes') || [];\nvar noteId = parseInt(msg.req.params.id);\nvar originalLength = notes.length;\nnotes = notes.filter(function(n) { return n.id !== noteId; });\nflow.set('notes', notes);\nif (notes.length < originalLength) {\n msg.payload = {success: true, message: \"Deleted\"};\n msg.statusCode = 200;\n} else {\n msg.payload = {success: false, error: \"Not found\"};\n msg.statusCode = 404;\n}\nmsg.headers = {\"Content-Type\": \"application/json\"};\nreturn msg;", "outputs": 1, "timeout": 0, "initialize": "", "finalize": "", "libs": [], "x": 370, "y": 260, "wires": [["ex4_response"]]}, {"id": "ex4_response", "type": "http response", "z": "ex4_tab", "name": "Response", "statusCode": "", "headers": {}, "x": 560, "y": 160, "wires": []} ]

✅ 7. まとめ

🎯 重要ポイント:

⚠️ 注意事項:

🔧 8. トラブルシューティング

症状原因解決方法
リクエストがタイムアウト HTTP Response がない 必ず HTTP Response を配置
404 Not Found(サーバー側) URLが間違っている HTTP In のURL設定を確認
POSTのボディが空 Content-Typeがない クライアント側でヘッダー設定
CORSエラー クロスオリジン制限 CORSヘッダーを追加
レスポンスが空(クライアント側) URLが間違い / サーバーダウン URLを確認、ブラウザでアクセスしてみる
401 Unauthorized 認証情報がない/間違い APIキーやトークンを確認
タイムアウト(クライアント側) サーバーの応答が遅い タイムアウト時間を延長
JSONパースエラー レスポンスがJSONでない Return を「UTF-8文字列」に変更
文字化け エンコーディング問題 charset=utf-8 を追加

CORSヘッダーの追加方法

// Functionノードで設定 msg.headers = { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE", "Access-Control-Allow-Headers": "Content-Type" }; return msg;

🏭 9. 実務活用例

ケース1: センサーデータ受信エンドポイント

Raspberry Pi からHTTP POSTでセンサーデータを受け取り、データベースやクラウドに転送

POST /api/sensor データ検証・保存 Response

ケース2: Webhook受信

GitHub、Slack、LINE等の外部サービスからのWebhook通知を受信して処理

POST /webhook イベント処理 200 OK

ケース3: 外部API連携

天気情報やクラウドサービスのAPIを定期的に呼び出し、データを取得・通知

5分ごと 天気API 閾値判定 通知

ケース4: 機器制御API + モニタリング

IoT機器の状態取得・制御コマンド送信用のREST APIを提供しつつ、外部APIでモニタリングデータを送信

GET /api/device 状態取得 Response
定期実行 データ収集 クラウドAPI

💡 代替APIリスト(サンプルAPIが使えない場合):

📚 Node-RED 公式ドキュメント - HTTP ノード

🏠