📁 Node-RED Write File / Read File ノードガイド

📚 目次

📖 1. 概要

Node-RED には2種類のファイル操作ノードがあります:

📝 Write File(書き込み)

Write File

役割: ファイルへの書き込み・追記・削除

用途:

  • ログファイルの作成
  • データの永続化
  • 設定ファイルの保存

📖 Read File(読み込み)

Read File

役割: ファイルからの読み込み

用途:

  • 設定ファイルの読み込み
  • データファイルの処理
  • テンプレートの読み込み

📂 ファイルキャビネットに例えると:Write File ノードは書類をキャビネットに入れる作業、Read File ノードはキャビネットから書類を取り出す作業です。

⚙️ 2. Write File(書き込み)ノード

プロパティ

プロパティ説明デフォルト
ファイル名書き込み先ファイルパス(空)
動作書き込み動作の種類append to file
メッセージの入力のたびに改行を追加末尾に改行を追加true
ディレクトリが存在しない場合は作成ディレクトリがなければ作成false
文字コード文字エンコーディングdefault

Action(動作)オプション

オプション説明用途
append to fileファイル末尾に追記ログファイル、時系列データ
overwrite fileファイルを上書き設定ファイル、状態保存
delete fileファイルを削除一時ファイルの削除

動的なファイル名

Filename を空にして、msg.filename でファイルパスを指定できます。

// Function ノードで動的にファイル名を設定 msg.filename = "/home/pi/logs/sensor_" + new Date().toISOString().slice(0,10) + ".log"; msg.payload = "センサーデータ: " + msg.payload; return msg;

📖 3. Read File(読み込み)ノード

プロパティ

プロパティ説明デフォルト
ファイル名読み込むファイルパス(空)
出力形式出力形式a single utf8 string
文字コード文字エンコーディングutf8

Output(出力形式)オプション

オプション説明用途
a single utf8 stringファイル全体を文字列で設定ファイル、テンプレート
a single Buffer objectファイル全体をBufferでバイナリファイル、画像
a msg per line1行ごとにメッセージ出力CSVの行処理、ログ解析
a single streamストリームとして出力大きなファイルの処理

💡 ポイント:

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

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

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

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

📥 サンプルフローJSON(クリックで展開)
[ { "id": "file_sample_tab", "type": "tab", "label": "File サンプル", "disabled": false, "info": "" }, { "id": "file_comment1", "type": "comment", "z": "file_sample_tab", "name": "━━━ ファイルへの書き込み(追記) ━━━", "info": "", "x": 200, "y": 40, "wires": [] }, { "id": "file_inject1", "type": "inject", "z": "file_sample_tab", "name": "ログ追記", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "センサー値: 25.5℃", "payloadType": "str", "x": 140, "y": 100, "wires": [["file_func_timestamp"]] }, { "id": "file_func_timestamp", "type": "function", "z": "file_sample_tab", "name": "タイムスタンプ追加", "func": "var now = new Date().toISOString();\nmsg.payload = now + \" - \" + msg.payload;\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 330, "y": 100, "wires": [["file_write_append"]] }, { "id": "file_write_append", "type": "file", "z": "file_sample_tab", "name": "ログ追記", "filename": "/tmp/nodered_sample.log", "filenameType": "str", "appendNewline": true, "createDir": true, "overwriteFile": "false", "encoding": "utf8", "x": 520, "y": 100, "wires": [["file_debug1"]] }, { "id": "file_debug1", "type": "debug", "z": "file_sample_tab", "name": "書き込み完了", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "\"書き込み完了\"", "statusType": "str", "x": 690, "y": 100, "wires": [] }, { "id": "file_comment2", "type": "comment", "z": "file_sample_tab", "name": "━━━ ファイルの読み込み ━━━", "info": "", "x": 180, "y": 180, "wires": [] }, { "id": "file_inject2", "type": "inject", "z": "file_sample_tab", "name": "ファイル読み込み", "props": [], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 160, "y": 240, "wires": [["file_read"]] }, { "id": "file_read", "type": "file in", "z": "file_sample_tab", "name": "ログ読み込み", "filename": "/tmp/nodered_sample.log", "filenameType": "str", "format": "utf8", "chunk": false, "sendError": false, "encoding": "utf8", "allProps": false, "x": 350, "y": 240, "wires": [["file_debug2"]] }, { "id": "file_debug2", "type": "debug", "z": "file_sample_tab", "name": "ファイル内容", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 530, "y": 240, "wires": [] }, { "id": "file_comment3", "type": "comment", "z": "file_sample_tab", "name": "━━━ 上書き保存 ━━━", "info": "", "x": 160, "y": 320, "wires": [] }, { "id": "file_inject3", "type": "inject", "z": "file_sample_tab", "name": "設定を保存", "props": [], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 150, "y": 380, "wires": [["file_func_config"]] }, { "id": "file_func_config", "type": "function", "z": "file_sample_tab", "name": "設定オブジェクト", "func": "msg.payload = JSON.stringify({\n \"appName\": \"My IoT App\",\n \"version\": \"1.0.0\",\n \"settings\": {\n \"interval\": 5000,\n \"threshold\": 30\n },\n \"updatedAt\": new Date().toISOString()\n}, null, 2);\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 330, "y": 380, "wires": [["file_write_overwrite"]] }, { "id": "file_write_overwrite", "type": "file", "z": "file_sample_tab", "name": "設定ファイル保存", "filename": "/tmp/nodered_config.json", "filenameType": "str", "appendNewline": false, "createDir": true, "overwriteFile": "true", "encoding": "utf8", "x": 530, "y": 380, "wires": [["file_debug3"]] }, { "id": "file_debug3", "type": "debug", "z": "file_sample_tab", "name": "保存完了", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "\"設定保存完了\"", "statusType": "str", "x": 700, "y": 380, "wires": [] }, { "id": "file_comment4", "type": "comment", "z": "file_sample_tab", "name": "━━━ 動的ファイル名(日付別ログ) ━━━", "info": "", "x": 210, "y": 460, "wires": [] }, { "id": "file_inject4", "type": "inject", "z": "file_sample_tab", "name": "イベント発生", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "ボタンが押されました", "payloadType": "str", "x": 150, "y": 520, "wires": [["file_func_dynamic"]] }, { "id": "file_func_dynamic", "type": "function", "z": "file_sample_tab", "name": "日付別ファイル名", "func": "var today = new Date().toISOString().slice(0, 10);\nmsg.filename = \"/tmp/events_\" + today + \".log\";\n\nvar timestamp = new Date().toISOString();\nmsg.payload = timestamp + \" | \" + msg.payload;\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 330, "y": 520, "wires": [["file_write_dynamic"]] }, { "id": "file_write_dynamic", "type": "file", "z": "file_sample_tab", "name": "動的ファイル", "filename": "", "filenameType": "msg", "appendNewline": true, "createDir": true, "overwriteFile": "false", "encoding": "utf8", "x": 520, "y": 520, "wires": [["file_debug4"]] }, { "id": "file_debug4", "type": "debug", "z": "file_sample_tab", "name": "ファイル名確認", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "filename", "targetType": "msg", "statusVal": "filename", "statusType": "msg", "x": 700, "y": 520, "wires": [] }, { "id": "file_comment5", "type": "comment", "z": "file_sample_tab", "name": "━━━ エラーハンドリング ━━━", "info": "", "x": 170, "y": 600, "wires": [] }, { "id": "file_inject5", "type": "inject", "z": "file_sample_tab", "name": "存在しないファイル", "props": [{"p": "filename", "v": "/nonexistent/path/file.txt", "vt": "str"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 170, "y": 660, "wires": [["file_read_error"]] }, { "id": "file_read_error", "type": "file in", "z": "file_sample_tab", "name": "読み込み試行", "filename": "filename", "filenameType": "msg", "format": "utf8", "chunk": false, "sendError": false, "encoding": "utf8", "allProps": false, "x": 360, "y": 660, "wires": [["file_debug5"]] }, { "id": "file_debug5", "type": "debug", "z": "file_sample_tab", "name": "読み込み結果", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "x": 540, "y": 660, "wires": [] }, { "id": "file_catch", "type": "catch", "z": "file_sample_tab", "name": "ファイルエラー捕捉", "scope": ["file_read_error"], "uncaught": false, "x": 350, "y": 720, "wires": [["file_debug_error"]] }, { "id": "file_debug_error", "type": "debug", "z": "file_sample_tab", "name": "エラー内容", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "error.message", "targetType": "msg", "statusVal": "error.message", "statusType": "msg", "x": 530, "y": 720, "wires": [] } ]

使用パターン

パターン1: ファイルへの書き込み(追記)

サンプルフローの「ファイルへの書き込み(追記)」を参照してください。

メッセージ 追記 完了

設定: Action: append to file, Add newline: ✓

パターン2: ファイルの読み込み

サンプルフローの「ファイルの読み込み」を参照してください。

読み込み Read File 内容表示

設定: Output: a single utf8 string

パターン3: 上書き保存

サンプルフローの「上書き保存」を参照してください。

新データ 上書き 保存完了

設定: Action: overwrite file(既存ファイルを置き換え)

パターン4: 動的ファイル名(日付別ログ)

サンプルフローの「動的ファイル名(日付別ログ)」を参照してください。

データ 日付でファイル名生成 動的ファイル

設定: Filename: 空(msg.filename で指定)

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

サンプルフローの「エラーハンドリング」を参照してください。

読み込み試行 成功時 Catch エラー時

ポイント: Catch ノードでファイルエラー(存在しない、権限なし等)をキャッチ

📝 5. 演習問題

演習1: 基本的なログ出力 初級

📋 課題: Inject ノードのボタンを押すたびに、タイムスタンプ付きのメッセージをログファイルに追記してください。

✅ 成功の条件:

💡 ヒント

File ノードの Action を「append to file」に設定し、「Add newline」をオンにします。

✅ 解答例フロー
[ {"id": "ex1_tab", "type": "tab", "label": "演習1"}, {"id": "ex1_inject", "type": "inject", "z": "ex1_tab", "name": "ログ追加", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "イベント発生", "payloadType": "str", "x": 140, "y": 100, "wires": [["ex1_func"]]}, {"id": "ex1_func", "type": "function", "z": "ex1_tab", "name": "タイムスタンプ", "func": "msg.payload = new Date().toISOString() + \" - \" + msg.payload;\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 300, "y": 100, "wires": [["ex1_file"]]}, {"id": "ex1_file", "type": "file", "z": "ex1_tab", "name": "ログ追記", "filename": "/tmp/exercise1.log", "filenameType": "str", "appendNewline": true, "createDir": true, "overwriteFile": "false", "encoding": "utf8", "x": 460, "y": 100, "wires": [["ex1_debug"]]}, {"id": "ex1_debug", "type": "debug", "z": "ex1_tab", "name": "完了", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "\"追記完了\"", "statusType": "str", "x": 610, "y": 100, "wires": []} ]

演習2: ファイルの読み込み 初級

📋 課題: 演習1で作成したログファイルを読み込み、内容をDebugに表示してください。

✅ 成功の条件:

💡 ヒント

File In ノードを使用し、Output を「a single utf8 string」に設定します。

✅ 解答例フロー
[ {"id": "ex2_tab", "type": "tab", "label": "演習2"}, {"id": "ex2_inject", "type": "inject", "z": "ex2_tab", "name": "読み込み", "props": [], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 140, "y": 100, "wires": [["ex2_file_in"]]}, {"id": "ex2_file_in", "type": "file in", "z": "ex2_tab", "name": "ログ読み込み", "filename": "/tmp/exercise1.log", "filenameType": "str", "format": "utf8", "chunk": false, "sendError": false, "encoding": "utf8", "allProps": false, "x": 300, "y": 100, "wires": [["ex2_debug"]]}, {"id": "ex2_debug", "type": "debug", "z": "ex2_tab", "name": "内容表示", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 470, "y": 100, "wires": []} ]

演習3: JSON設定ファイルの保存と読み込み 中級

📋 課題: 設定オブジェクトをJSONファイルとして保存し、別のフローで読み込んでオブジェクトとして使用してください。

✅ 成功の条件:

💡 ヒント

保存時: JSON.stringify() で文字列化、読み込み時: JSON ノードで解析

✅ 解答例フロー
[ {"id": "ex3_tab", "type": "tab", "label": "演習3"}, {"id": "ex3_inject1", "type": "inject", "z": "ex3_tab", "name": "設定保存", "props": [], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 140, "y": 80, "wires": [["ex3_func1"]]}, {"id": "ex3_func1", "type": "function", "z": "ex3_tab", "name": "設定作成", "func": "msg.payload = JSON.stringify({\n server: \"localhost\",\n port: 8080,\n debug: true\n}, null, 2);\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 290, "y": 80, "wires": [["ex3_file_write"]]}, {"id": "ex3_file_write", "type": "file", "z": "ex3_tab", "name": "JSON保存", "filename": "/tmp/config.json", "filenameType": "str", "appendNewline": false, "createDir": true, "overwriteFile": "true", "encoding": "utf8", "x": 450, "y": 80, "wires": [["ex3_debug1"]]}, {"id": "ex3_debug1", "type": "debug", "z": "ex3_tab", "name": "保存完了", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "\"保存完了\"", "statusType": "str", "x": 620, "y": 80, "wires": []}, {"id": "ex3_inject2", "type": "inject", "z": "ex3_tab", "name": "設定読込", "props": [], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 140, "y": 160, "wires": [["ex3_file_read"]]}, {"id": "ex3_file_read", "type": "file in", "z": "ex3_tab", "name": "JSON読込", "filename": "/tmp/config.json", "filenameType": "str", "format": "utf8", "chunk": false, "sendError": false, "encoding": "utf8", "allProps": false, "x": 290, "y": 160, "wires": [["ex3_json"]]}, {"id": "ex3_json", "type": "json", "z": "ex3_tab", "name": "JSON解析", "property": "payload", "action": "", "pretty": false, "x": 450, "y": 160, "wires": [["ex3_debug2"]]}, {"id": "ex3_debug2", "type": "debug", "z": "ex3_tab", "name": "設定オブジェクト", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "payload.server", "statusType": "msg", "x": 630, "y": 160, "wires": []} ]

演習4: 日付別ローテーションログ 上級

📋 課題: センサーデータを日付別のファイルに保存し、7日以上前のファイルを自動削除する仕組みを作成してください。

✅ 成功の条件:

💡 ヒント

msg.filename で動的にファイル名を指定。削除は Action: delete file を使用。

✅ 解答例フロー
[ {"id": "ex4_tab", "type": "tab", "label": "演習4"}, {"id": "ex4_inject", "type": "inject", "z": "ex4_tab", "name": "センサーデータ", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "25.5", "payloadType": "num", "x": 160, "y": 80, "wires": [["ex4_func_write"]]}, {"id": "ex4_func_write", "type": "function", "z": "ex4_tab", "name": "日付別ファイル名", "func": "var today = new Date().toISOString().slice(0,10);\nmsg.filename = \"/tmp/sensor_\" + today + \".log\";\nmsg.payload = new Date().toISOString() + \",\" + msg.payload;\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 350, "y": 80, "wires": [["ex4_file_write"]]}, {"id": "ex4_file_write", "type": "file", "z": "ex4_tab", "name": "ログ追記", "filename": "", "filenameType": "msg", "appendNewline": true, "createDir": true, "overwriteFile": "false", "encoding": "utf8", "x": 530, "y": 80, "wires": [["ex4_debug1"]]}, {"id": "ex4_debug1", "type": "debug", "z": "ex4_tab", "name": "保存先", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "filename", "targetType": "msg", "statusVal": "filename", "statusType": "msg", "x": 690, "y": 80, "wires": []}, {"id": "ex4_inject_del", "type": "inject", "z": "ex4_tab", "name": "古いファイル削除", "props": [], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 170, "y": 160, "wires": [["ex4_func_del"]]}, {"id": "ex4_func_del", "type": "function", "z": "ex4_tab", "name": "7日前のファイル名", "func": "var d = new Date();\nd.setDate(d.getDate() - 7);\nvar oldDate = d.toISOString().slice(0,10);\nmsg.filename = \"/tmp/sensor_\" + oldDate + \".log\";\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 370, "y": 160, "wires": [["ex4_file_del"]]}, {"id": "ex4_file_del", "type": "file", "z": "ex4_tab", "name": "ファイル削除", "filename": "", "filenameType": "msg", "appendNewline": true, "createDir": false, "overwriteFile": "delete", "encoding": "utf8", "x": 550, "y": 160, "wires": [["ex4_debug2"]]}, {"id": "ex4_debug2", "type": "debug", "z": "ex4_tab", "name": "削除結果", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "filename", "targetType": "msg", "statusVal": "\"削除試行\"", "statusType": "str", "x": 700, "y": 160, "wires": []} ]

✅ 6. まとめ

🎯 重要ポイント:

⚠️ 注意事項:

🏭 7. 実務活用例

ケース1: センサーデータのログ記録

IoTセンサーからのデータを時系列でログファイルに記録。日付別ローテーション。

ケース2: 設定ファイルの管理

JSON/YAML形式の設定ファイルを読み込み、フロー起動時に設定を適用。

ケース3: CSVデータのバッチ処理

CSVファイルを1行ずつ読み込み、データベースへのインポート処理。

ケース4: レポート生成

収集したデータからレポートを生成し、ファイルとして保存・メール送信。

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

🏠