Node.js練習問題集【基礎API編】


Node.jsとは、ブラウザの外でJavaScriptを実行するためのランタイムのことで、サーバー開発・CLIツール・ビルドツールの土台として使われています。本記事は、そのNode.js固有の基礎API(fs・path・モジュール・process・イベントループ)を「壊れたコードを直しながら」身につける中級者向けの練習問題集です。

Stack Overflow Developer Survey 2025(177カ国・49,000人超が回答)によると、Node.jsはWebフレームワーク・技術カテゴリで利用率48.7%の1位であり、2位のReact(44.7%)を上回っています。それだけ使われている技術なのに、「JavaScriptの文法は書けるが、fsやイベントループになると手が止まる」という人は少なくありません。原因は知識不足ではなく、Node.js固有のAPIを手を動かして試す場所がないことです。

この記事では、サンプル問題5問を「壊れたコード → ヒント → 解答と解説」の形式で解答付きで掲載し、続きの15問はブラウザだけで解ける演習アプリで出題します。仕上げには、アプリでは体験できないhttpサーバーとExpressのローカル実行課題も用意しました。JavaScriptの基礎文法がまだの方は JavaScript練習問題23選(基礎文法編) から始めてください。


差分チェックツールで効率UPお手本コードと自分のコードを比較して、違いを一目で確認できます。練習前にブックマークしておくと便利です。
Diff Checkerを開く →

この練習問題集の構成と進め方

対象レベルは、JavaScriptの基礎文法(変数・関数・配列・条件分岐)を理解済みの中級者です。出題は全20問で、テーマ別の内訳は次のとおりです。各テーマの代表問題を本記事にサンプルとして掲載し、全問はブラウザ演習アプリで解けます。

テーマ出題数本記事のサンプル
fs(ファイル操作)5問問1
path(パス操作)3問問2
モジュール(CJS / ESM)4問問3
process(引数・環境変数)3問問4
イベントループ3問問5
Stream・Buffer2問アプリのみ

次の手順で取り組むと、Node.js固有の考え方が効率よく身につきます。

  1. まず解答を見ずに、壊れたコードの「どこが・なぜ」おかしいかを予想する
  2. ヒントを開いて、考える方向が合っているか確認する
  3. 解答例と解説で「なぜそうなるのか」と「次の一手」まで理解する
  4. 本記事の5問を終えたら、演習アプリで残りの15問に挑戦する

環境構築:Node.js v24(LTS)を入れる

Node.js公式のダウンロードページ(2026年7月29日時点)では、LTS版として v24.18.0(コードネーム Krypton)、Current版として v26.5.0 が提供されています。Node.js公式は「Production applications should only use Active LTS or Maintenance LTS releases(本番アプリケーションはActive LTSまたはMaintenance LTSのリリースのみを使うべき)」と明記しているため、練習環境も v24系のLTS を選んでおけば間違いありません。なお v20(Iron)は2026年4月30日にサポート終了(EOL)を迎えているため、これから始めるなら選択肢に入りません。

Node.js公式のダウンロードページからインストーラーを入れたら、ターミナルで次の2つを確認します。

node -v   # v24.18.0 のように表示されればOK
npm -v    # npmも一緒に入っている

「今すぐ問題だけ解きたい」「インストールは後回しにしたい」という場合は、本記事のサンプル5問を読んだあと、環境構築なしで動かせるブラウザ演習アプリから始めても構いません。ブラウザで使えるコーディング環境を広く知りたい方は ブラウザだけでコードを書ける実行環境5選 も参考になります。


問1. fs:ファイルを読んだはずが Promise が表示される

notes.txt の中身を表示してから copy.txt に複製するプログラムです。中身が表示されるはずが Promise { <pending> } と出力され、書き込みでもエラーになります。原因を見つけて直してください。

// index.js(CommonJS)
const fs = require('node:fs/promises');

async function main() {
  const text = fs.readFile('notes.txt', 'utf8');
  console.log(text); // 期待: ファイルの中身 / 実際: Promise { <pending> }
  await fs.writeFile('copy.txt', text); // ここでもエラーになる
}

main();
ヒント

fs/promises のAPIは、何を返すからその名前なのでしょうか。readFile の戻り値をそのまま使う前に、必要なキーワードが1つ抜けています。

解答例と解説
const fs = require('node:fs/promises');

async function main() {
  const text = await fs.readFile('notes.txt', 'utf8');
  console.log(text); // ファイルの中身
  await fs.writeFile('copy.txt', text);
}

main();

なぜ:fs/promises のAPIはすべてPromiseを返すため、await を付けないと戻り値は「これから解決される箱」のままです。後続の writeFile にはPromiseオブジェクトがそのまま渡り、文字列でもBufferでもないため ERR_INVALID_ARG_TYPE エラーになります。Promise版APIのインポートは、CommonJSなら require('node:fs/promises')、ESMなら import * as fs from 'node:fs/promises'; と書きます(Node.js公式APIドキュメント)。
次の一手:第2引数の 'utf8' を外して実行すると、戻り値が文字列ではなく Buffer になることを確認しましょう。なお node:fs にはコールバック版APIもあり、公式は「最大の性能が必要な場面ではコールバック版が望ましい」としていますが、実務の読み書きでは async/await と素直に組み合わせられる fs/promises を使うのが読みやすい選択です。


問2. path:文字列連結のパスがWindowsで壊れる

フォルダ名とファイル名からパスを組み立てて、拡張子を取り出すプログラムです。macでは動いているように見えますが、Windowsで壊れる箇所と、report.v2.txt のようなファイル名で壊れる箇所があります。2つとも直してください。

// index.js(CommonJS)
const dir = 'data/reports';
const file = 'report.v2.txt';

const fullPath = dir + '/' + file;
console.log(fullPath);

const ext = file.split('.')[1];
console.log(ext); // 期待: txt / 実際: v2
ヒント

OSごとの区切り文字の違いと「最後のドット以降」を、標準モジュール node:path はどう扱ってくれるでしょうか。joinextname を調べてみましょう。

解答例と解説
const path = require('node:path');

const dir = 'data/reports';
const file = 'report.v2.txt';

const fullPath = path.join(dir, file);
console.log(fullPath); // OSに合った区切りで結合される

const ext = path.extname(file);
console.log(ext); // .txt

なぜ:パスの区切り文字はOSによって異なります(path.sep で確認でき、Windowsは \)。文字列連結で / を直書きすると、区切りの混在や二重スラッシュの原因になります。path.join() は実行環境に合わせて正しく結合し、余分な区切りも正規化してくれます。拡張子も同様で、split('.')[1] は「最初のドットの次」を返すため、ドットを複数含むファイル名で壊れます。path.extname() は「最後のドット以降」を返す仕様なので安全です。
次の一手:path.join('data', 'reports')path.resolve('data', 'reports') の出力を比べて、相対パスのまま結合するjoinと、絶対パスに解決するresolveの違いを確認してみましょう。


問3. モジュール:require is not defined と怒られる

プロジェクトの package.json"type": "module" を設定したところ、今まで動いていたコードが ReferenceError: require is not defined in ES module scope で止まるようになりました。原因を説明して、動くように直してください。

{
  "name": "practice",
  "type": "module"
}
// index.js
const path = require('node:path'); // ReferenceError!

console.log(path.join('data', 'app.log'));
ヒント

"type": "module" が設定されていると、.js ファイルはどちらのモジュール方式として読み込まれるでしょうか。require はどちらの方式の書き方でしょうか。

解答例と解説
// 直し方①: ESMの構文に書き換える(推奨)
import path from 'node:path';

console.log(path.join('data', 'app.log'));

// 直し方②: このファイルだけCJSのままにしたい場合は
// 拡張子を index.cjs に変える(中身は require のままでOK)

なぜ:Node.js公式ドキュメントは「最も近い親 package.json"type" フィールドが "module" の場合、.js で終わるファイルはESモジュールとして読み込まれる」と定めています。ESMのスコープに require は存在しないため、CJSの構文を書くと ReferenceError になります。逆に "type" が無い(または "commonjs")場合、.js はCJS扱いです。拡張子 .mjs / .cjs"type" の設定より優先して方式を確定させます。
次の一手:逆方向の相互運用も試しましょう。CJS側からESMを読み込む require(esm) は、v23.0.0・v22.12.0・v20.19.0 でフラグ不要になり、v25.4.0 で実験的機能ではなくなりました(Node.js公式ドキュメント)。ただし読み込めるのは「トップレベル await を含まない完全に同期なモジュール」に限られます。小さな .mjs を作って require() で読み込めるか実験してみてください。


問4. process:コマンドライン引数と環境変数が読めない

node greet.js Taro のように名前を渡して挨拶するCLIです。名前の代わりにスクリプトのパスが表示され、DEBUG=true node greet.js Taro と実行してもデバッグ表示が出ません。2つの原因を直してください。

// greet.js(CommonJS)
const name = process.argv[1];
console.log(`Hello, ${name}!`); // 期待: Hello, Taro! / 実際: パスが出る

if (process.env.DEBUG === true) {
  console.log('debug mode'); // DEBUG=true を付けても表示されない
}
ヒント

process.argv の先頭2つには何が入っているでしょうか。また、環境変数の値は必ずどの型で渡ってくるでしょうか。

解答例と解説
const name = process.argv[2] ?? 'guest';
console.log(`Hello, ${name}!`); // Hello, Taro!

if (process.env.DEBUG === 'true') {
  console.log('debug mode');
}

なぜ:process.argv[0] にNode.js本体の実行パス、[1] にスクリプトのパスが入り、ユーザーが渡した引数は [2] 以降に並びます。また環境変数 process.env の値は常に文字列です。シェルで DEBUG=true と書いても渡ってくるのは文字列の 'true' なので、真偽値の true と厳密比較すると絶対に一致しません。
次の一手:引数が複数あるCLIでは process.argv.slice(2) で「ユーザー引数だけの配列」を作るのが定石です。さらに、引数が無いときに使い方を表示して process.exit(1) で異常終了コードを返す分岐も足してみましょう。


問5. イベントループ:6つのログの出力順を予測する

今回は壊れたコードではなく予測問題です。次のコードを CommonJS(index.cjs)として実行したとき、A〜Fはどの順番で出力されるでしょうか。「必ずこの順になる」と言い切れないペアが1組あります。それがどれかも含めて答えてください。

// index.cjs(CommonJSとして実行する)
console.log('A');

setTimeout(() => console.log('B'), 0);
setImmediate(() => console.log('C'));
Promise.resolve().then(() => console.log('D'));
process.nextTick(() => console.log('E'));

console.log('F');
ヒント

まず同期コードがすべて実行されます。その直後に処理される「2つのキュー」の優先順位と、タイマー系2つ(setTimeout(0)setImmediate)の関係を考えましょう。

解答と解説

答えは A → F → E → D → (B・C:順不同) です。

なぜ:まず同期コードのA・Fが実行されます。次に、process.nextTick() はイベントループのフェーズに属さず「現在の操作が完了した直後」に処理されるキューで、CJSモジュールではPromiseのマイクロタスクより常に先に実行されます(E → D)。最後のB・Cは引っかけで、メインモジュール直下で呼んだ setTimeout(fn, 0)setImmediate() の実行順は、Node.js公式ガイドが「non-deterministic(非決定的)」と明記しているとおり、実行のたびに入れ替わり得ます。I/Oコールバックの中でスケジュールした場合だけ setImmediate() が必ず先になり、公式は「推論しやすいため、あらゆる場面で setImmediate() の使用を推奨する」としています。

ここが落とし穴(ESMだと答えが変わる):同じコードを index.mjs(ESM)として実行すると、順序は A → F → D → E に変わります。Node.js公式ドキュメントは、CJSでは process.nextTick()queueMicrotask() より先に実行されるが、ESMは既にマイクロタスクキューの一部として処理されるため逆転する、と明記しています。ネット上の解説の多くはCJS前提で書かれているので、「nextTickが常に最優先」と丸暗記していると足をすくわれます。
次の一手:もう1つ、イベントループのフェーズ順にも注意が必要です。Node.js公式ガイドは「libuv 1.45.0(Node.js 20)以降、タイマーは各反復の poll フェーズの後に実行される」と補足しており、「timersフェーズが先頭」と説明している記事はNode.js 20より前の情報です。両ファイル(.cjs / .mjs)を実際に作って、出力の違いを自分の環境で確かめてみましょう。


残り15問はブラウザ演習アプリで解ける

ここまでの5問を含む全20問(fs・path・モジュール・process・イベントループ・Stream/Buffer)は、ブラウザ上の演習アプリで解けます。エディタにコードを書いて実行すると、仮想のNode環境(fs・path・processなどの主要APIを再現した実行環境)が判定してくれるため、Node.jsをインストールしていない環境でも今すぐ練習を始められます

本記事のサンプル5問も収録しているので、「記事で読んで理解した問題を、実際に書いて確かめる」使い方もおすすめです。


ローカル実行課題:httpサーバーとExpress

サーバーを立てて待ち受ける体験だけは、ブラウザの仮想環境では再現できません。ここからは、環境構築を済ませた手元のNode.jsで挑戦するローカル実行課題です。

課題1. node:http でJSONを返すサーバーを立てる

標準モジュール node:http だけを使い、http://localhost:3000/ にアクセスすると {"message":"hello"} をJSONとして返すサーバーを作ってください。条件は「Content-Typeを application/json にする」「/ 以外のパスでは404を返す」の2つです。

解答例と確認方法
// server.js(CommonJS)
const http = require('node:http');

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'hello' }));
  } else {
    res.writeHead(404, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'not found' }));
  }
});

server.listen(3000, () => {
  console.log('http://localhost:3000 で待ち受け中');
});

node server.js で起動したら、別のターミナルで curl http://localhost:3000/curl -i http://localhost:3000/unknown を実行し、JSON本文とステータスコード(200 / 404)を確認してください。終了は Ctrl + C です。ブラウザのJavaScriptと違い、プログラムが終了せず「待ち続ける」のがサーバーの動作だと体感できるはずです。

課題2. Express 5 でルーティングを書き換える

課題1と同じ動作を、フレームワークのExpressで書き直してください。Express公式サイトおよびnpmレジストリで確認できる最新版は 5.2.1(2026年7月29日時点、動作要件 Node.js 18以上)で、npm install express でこのv5系が入ります。加えて「GET /users/:id でURLの :id をJSONに含めて返す」ルートを1本増やすのが今回の追加条件です。

解答例と確認方法
// app.js(CommonJS)
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.json({ message: 'hello' });
});

app.get('/users/:id', (req, res) => {
  res.json({ userId: req.params.id });
});

app.use((req, res) => {
  res.status(404).json({ error: 'not found' });
});

app.listen(3000, () => {
  console.log('http://localhost:3000 で待ち受け中');
});

curl http://localhost:3000/users/42{"userId":"42"} が返れば成功です。res.json() がContent-Type設定とJSON変換をまとめて面倒を見てくれる点、404を「最後のミドルウェア」として書ける点が、課題1の素のhttpモジュールとの違いです。標準APIを先に触っておくと、フレームワークが何を省略してくれているのかが具体的に分かります。


次のステップと関連記事

Node.jsの基礎APIを押さえたら、次は「フロントエンドとサーバーサイドの役割分担」を整理すると視界が開けます。ReactやVueとNode.jsがどう組み合わさるのかは React.js・Vue.js・Node.jsの違いと使い分け で詳しく解説しています。

ブラウザ側のJavaScript力を鍛えたい方は、テーマ別の練習問題シリーズへどうぞ。

体系的に学び直したい場合は学習ガイドからどうぞ。


よくある質問(FAQ)

Node.jsの練習問題に取り組む際に、よくいただく質問をまとめました。

Q. Node.jsの練習問題はどのレベルから始めればいいですか?

JavaScriptの基礎文法(変数・関数・配列・条件分岐)を理解していれば始められます。本記事の問1(fs)から順に解き、詰まったら基礎文法編の練習問題に戻るのがおすすめです。Node.js固有なのはAPIであって、文法はブラウザのJavaScriptと同じです。

Q. ブラウザだけでNode.jsの練習はできますか?

fs・path・processなどの基礎APIの練習なら、当サイトの演習アプリのようにNode環境を再現したブラウザ実行環境でできます。ただしhttpサーバーを立てて待ち受ける体験だけは手元のNode.jsが必要なので、基礎はブラウザで・サーバーはローカルで、と使い分けるのが効率的です。

Q. どのバージョンのNode.jsで練習すればいいですか?

2026年7月時点ではLTS版のv24系を選んでください。Node.js公式が本番用途に推奨しているのはActive LTSまたはMaintenance LTSであり、練習環境を本番と揃えておくと知識がそのまま活きます。v20は2026年4月30日にサポートが終了しています。

Q. CommonJSとESMはどちらで練習すべきですか?

新しく書くコードはESM(import / export)を基本にしつつ、CommonJS(require)も読める状態を目指してください。npmの既存パッケージや現場のコードにはCJSが大量に残っており、問5のようにモジュール形式で挙動が変わるケースもあるため、両方の対応関係を知っていることが中級者の条件になります。

Q. フロントエンドのJavaScript練習と何が違いますか?

文法は同じですが、扱う対象が違います。フロントエンドはDOM・イベント・fetchが中心なのに対し、Node.jsはファイル・パス・プロセス・サーバーというOS寄りの対象を扱います。windowやdocumentが存在しない代わりに、fsやprocessといったNode.js固有のAPIを使う点が本質的な違いです。

Q. この練習問題の次は何をすればいいですか?

ローカル実行課題のExpressを発展させて、小さなAPIサーバーを1本作り切るのがおすすめです。ルートを増やし、fsで読んだJSONファイルを返し、エラー処理を足す——本記事の全テーマが1つのアプリに合流します。作ったものはポートフォリオにもなります。

\ 全20問をブラウザで今すぐ解く /