Node.jsで実験データファイルを扱う
前回JavaScriptで変数、関数、条件分岐、ループを学びました。しかしそのコードはすべてブラウザの中で実行されていました。ブラウザ内のJavaScriptはセキュリティ上、コンピュータのファイルを読み書きできません。
研究者にとってこれは致命的な制約です。実験結果のCSVを読み、FASTA配列ファイルをパースし、分析結果を新しいファイルに保存するには — JavaScriptがコンピュータのファイルシステムに直接アクセスする必要があります。
Node.jsがまさにこの問題を解決します。JavaScriptをブラウザの外で実行できるようにした**ランタイム(Runtime)**です。実験室の例えで言えば — JavaScriptというプロトコルをブラウザという特定の装置でしか実行できなかったのが、Node.jsのおかげでどのコンピュータでも実行できるようになったのです。
ランタイムとは?
ランタイム(Runtime)= コードを実行する環境です。
- ブラウザ = JavaScriptランタイム(Webページ内のみ)
- Node.js = JavaScriptランタイム(コンピュータのどこでも)
qPCR装置でしか動かなかった分析ソフトウェアを、どのコンピュータでも実行できるようにしたのと同じです。
# インストール確認(ターミナルで)node --version# v22.x.x のようなバージョンが出ればインストール済み
# JavaScriptファイルの実行node my_script.jsブラウザでは<script>タグの中にコードを入れましたが、Node.jsでは.jsファイルを作ってターミナルでnode ファイル名.jsで実行します。
モジュール:機能ボックスを取り出して使う
Node.jsがファイルを読み、ネットワーク通信をし、パスを処理できるのは**モジュール(Module)**のおかげです。モジュールは特定の機能をまとめた道具箱です。
実験室でPCRキット、DNA抽出キット、電気泳動キットをそれぞれ取り出して使うように、Node.jsでも必要なモジュールをrequire()で取り出します:
const fs = require("fs");
const path = require("path");fs— ファイルシステム(File System)モジュール。ファイルの読み書き、削除path— パス処理モジュール。OS別のパスの違いを自動処理
これらのモジュールはNode.jsに内蔵されているため、別途インストールなしですぐ使えます。
ファイル読み込み:fs.readFileSync
最も基本的な操作 — ファイルを読んで内容を出力します:
const fs = require("fs");
const data = fs.readFileSync("samples.csv", "utf8");
console.log(data);readFileSyncのSyncは**同期(synchronous)**という意味です。ファイルを読み終わるまで次の行に進みません。遠心分離機が止まるまでそばで待つようなものです。
"utf8"は文字エンコーディング — これを省略すると人が読めないBuffer(バイナリデータ)が出力されます。
実験データを読んで処理する例:
const fs = require("fs");
const raw = fs.readFileSync("qc_results.csv", "utf8");
const lines = raw.trim().split("\n");
const header = lines[0].split(",");
console.log("カラム:", header);
console.log("データ行数:", lines.length - 1);
for (let i = 1; i < lines.length; i++) {
const cols = lines[i].split(",");
const sampleId = cols[0];
const od = parseFloat(cols[1]);
if (od < 0.5) {
console.log(sampleId, "— OD", od, "— FAIL");
}
}CSVファイルを読んで行単位で分割し、OD値が基準未満のサンプルをフィルタリングします。Excelを開かなくてもターミナルで直接QC判定が可能です。
同期 vs 非同期:なぜ2種類あるのか?
readFileSync(同期)は直感的ですが問題があります。ファイルが大きいと読み込み中にプログラム全体が止まります。シーケンシングの生データのような数GBのファイルを同期で読むと、その間何もできません。
readFile(非同期)がこの問題を解決します:
const fs = require("fs");
fs.readFile("sequences.fasta", "utf8", function(err, data) {
if (err) {
console.log("ファイル読み込み失敗:", err.message);
return;
}
console.log("配列データの長さ:", data.length);
});
console.log("ファイル要求完了、他の作業を続行");実行結果:
ファイル要求完了、他の作業を続行
配列データの長さ: 4823910順序に注目してください。console.log("ファイル要求完了...")が先に出力されます。readFileはファイル読み込みを開始だけしてすぐ次の行に進みます。ファイル読み込みが完了した時点でfunction(err, data) — コールバック(callback)関数が呼ばれます。
外部シーケンシング業者にサンプルを送るのと同じです。送った後、結果が届くまでぼーっと待たずに他の実験を続け、結果が届いたらその時確認します。
| 同期(Sync) | 非同期(Async) | |
|---|---|---|
| 関数 | fs.readFileSync() | fs.readFile() |
| 動作 | 完了まで待機 | 要求後すぐ次の行へ |
| 結果の受け取り | 戻り値(const data = ...) | コールバック関数(function(err, data)) |
| 例え | 遠心分離機の前で待つ | シーケンシング外注して他の実験 |
| 適した場面 | 小さな設定ファイル、初期化 | 大容量データ、サーバーリクエスト処理 |
コールバックとエラー処理
非同期関数のコールバックは常に(err, data)の形です。最初の引数がエラー、2番目が結果。Node.jsの約束事です:
const fs = require("fs");
fs.readFile("experiment_log.txt", "utf8", function(err, data) {
if (err) {
console.log("エラー種類:", err.code);
console.log("エラーメッセージ:", err.message);
return;
}
console.log("ログ内容:", data);
});ファイルがなければerr.codeは"ENOENT"(Error NO ENTry)— 「ファイルが見つかりません」という意味です。このパターンを覚えてください:
- まず
errを確認 - エラーがあれば処理して
return - エラーがなければ
dataを使用
ファイル書き込み:分析結果の保存
読み込みと同じくらい重要なのが書き込みです。QC判定結果をファイルに保存するには:
const fs = require("fs");
const results = [
"Sample_ID,OD,Status",
"S001,1.85,PASS",
"S002,0.42,FAIL",
"S003,2.10,PASS"
];
const output = results.join("\n");
fs.writeFileSync("qc_report.csv", output, "utf8");
console.log("QCレポート保存完了:", results.length - 1, "件");writeFileSyncはファイルがなければ新規作成し、あれば上書きします。既存の内容の後に追加するにはfs.appendFileSync()を使います。
簡単なWebサーバーを作る
Node.jsの本当の力はサーバーを作れることです。httpモジュールで3行あればWebサーバーが動きます:
const http = require("http");
const server = http.createServer(function(req, res) {
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.end("LIMSサーバー稼働中");
});
server.listen(3000, function() {
console.log("サーバー起動: http://localhost:3000");
});node server.jsを実行してブラウザでhttp://localhost:3000にアクセスすると「LIMSサーバー稼働中」が表示されます。
しかしこの状態でURLに応じて異なるデータを送ったり、POSTリクエストを処理しようとするとコードが急激に複雑になります。次に学ぶExpressがまさにこの複雑さを解決してくれるフレームワークです。
npm:他の開発者のツールを利用する
Node.jsにはnpm(Node Package Manager)というパッケージマネージャが一緒にインストールされます。他の開発者が作ったライブラリを1行のコマンドでインストールできます:
# プロジェクト初期化(package.json生成)npm init -y
# パッケージインストール例npm install csv-parsernpm install express実験室で試薬カタログを見て必要な試薬を注文するように、npmは数百万のJavaScriptパッケージカタログから必要なものを選んでインストールします。package.jsonは「このプロジェクトにどの試薬(パッケージ)が必要か」を記した試薬リストです。
やってみよう(Faded Example)
空欄を埋めてCSVファイルを読んで行数を数えるNode.jsコードを完成させてください。
const fs = require("");const data = fs.readFileSync("samples.csv", "");const lines = data.trim().("\n");console.log("合計", lines., "行");
よくあるエラーと解決法
Q: Error: Cannot find module 'fs'エラーが出ます
ブラウザで実行している可能性が高いです。fsモジュールはNode.js専用です。ターミナルでnode ファイル名.jsで実行してください。ブラウザコンソールやHTMLの<script>タグ内では使用できません。
Q: ファイルを読んだのに変な文字が出力されます(<Buffer 48 65 6c ...>)
readFileSyncに"utf8"エンコーディングを忘れています。エンコーディングなしで呼ぶとBuffer(生のバイト)が返されます。fs.readFileSync("file.txt", "utf8")に修正してください。
Q: ENOENT: no such file or directoryエラーが出ます
ファイルパスが間違っています。Node.jsはnodeコマンドを実行したディレクトリ基準で相対パスを解釈します。ls(Mac/Linux)またはdir(Windows)でファイルが現在のディレクトリにあるか確認してください。確実にするならpath.join(__dirname, "ファイル名")で絶対パスを構成します。
Q: 非同期関数の結果を外で使えません
let result;
fs.readFile("data.txt", "utf8", function(err, data) {
result = data;
});
console.log(result); // undefined!readFileはすぐ次の行に進むため、コールバックが実行される前にconsole.logが先に実行されます。結果を使うコードは必ずコールバック関数の中に入れてください。またはreadFileSyncを使えばこの問題はありません。
Q: 「ランタイム(Runtime)」って正確には何ですか?
ランタイムは特定の言語のコードを実行できるようにする実行環境そのものを指します。
JavaScriptは元々ブラウザの中でしか実行できませんでした。HTMLファイルの<script>タグの中にコードを入れ、ブラウザで開かないと動きませんでした。この時ブラウザがJavaScriptのランタイムでした。
Node.jsをコンピュータにインストールすると、ブラウザなしでもターミナルでnode script.jsでJavaScriptを実行できます。この時Node.jsがJavaScriptのランタイムです。
ブラウザでJS実行: HTMLファイル → ブラウザ(ランタイム)で開く
Node.jsでJS実行: .jsファイル → ターミナルでnodeコマンド(ランタイム)で実行例えるなら — 顕微鏡なしでは細胞を観察できないように、ランタイムなしではコードを実行できません。コードを書くこと(VS Codeでタイピング)と実行すること(ランタイムが解釈)は別のステップです。