Node.jsシニアバックエンド面接質問集

Milad Bonakdar
著者
Event Loop、Streams、APIセキュリティ、システム設計、スケーリング、パフォーマンス、テスト、本番運用責任を扱う30問で、Node.jsシニアバックエンド面接に備えましょう。
Node.jsシニアバックエンド面接質問集
Node.jsのシニア面接では、構文だけでなく、Event Loop、バックプレッシャー、API境界、分散システム、Observability、セキュリティ、本番運用での判断が問われます。強い回答は、Node.jsの内部動作と、実際のサービスで行った設計判断を結びつけて説明します。
この30問は、シニアらしい簡潔な説明を練習するためのものです。各テーマについて、スケーリング判断、性能問題、信頼性インシデント、設計上のトレードオフなど、自分の経験から1つ例を用意しておきましょう。
高度な Node.js の概念 (10個の質問)
1. Node.js のイベントループについて詳しく説明してください。異なるフェーズは何ですか?
回答: Event Loopは、JavaScriptが通常1つのメインスレッドで動きながら、Node.jsがノンブロッキングI/Oを扱えるようにする仕組みです。待ち時間のある処理や重い処理はOSまたはlibuvが扱い、完了後にJavaScriptが実行するコールバックとしてキューに入ります。
- Timers:
setTimeout()とsetInterval()のコールバックを実行します。近年のNode.jsでは、pollフェーズの処理量がタイマー実行のタイミングに影響することがあります。 - Pending callbacks: 次のループ反復に延期された一部の低レベルI/Oコールバックを実行します。
- Idle, prepare: libuv内部用のフェーズです。
- Poll: 新しいI/Oイベントを取得し、I/O関連のコールバックを実行します。キューが空の場合、Node.jsはI/Oを待つか、
setImmediate()があれば次へ進みます。 - Check:
setImmediate()のコールバックを実行します。 - Close callbacks:
socket.on('close', ...)などのcloseハンドラーを実行します。
シニアの回答では、マイクロタスクキューにも触れるべきです。process.nextTick() はPromiseのマイクロタスクより先に実行され、どちらもEvent Loopが続行する前に処理されます。使いすぎるとI/Oを待たせます。
希少性: 非常に一般的 難易度: 難しい
2. process.nextTick() と setImmediate() の違いは何ですか?
回答:
process.nextTick(): イベントループの一部ではありません。現在の操作が完了した直後、ただしイベントループが続行する前に発生します。setImmediate()よりも優先度が高くなります。過度に使用すると、イベントループをブロックする可能性があります(飢餓状態)。setImmediate(): イベントループのチェックフェーズの一部です。ポーリングフェーズの後に実行されます。
希少性: 一般的 難易度: 普通
3. Node.js はシングルスレッドの場合、どのように並行処理を処理しますか?
回答: Node.js は、イベント駆動型のノンブロッキング I/O モデルを使用します。
- メインスレッド: JavaScript コードを実行します (V8 エンジン)。
- Libuv: イベントループとスレッドプールを提供する C ライブラリ(デフォルトは4スレッド)。
- メカニズム: 非同期操作(ファイル I/O やネットワークリクエストなど)が開始されると、Node.js はそれを Libuv にオフロードします。Libuv は、そのスレッドプール(ファイル I/O、DNS 用)またはシステムカーネルの非同期メカニズム(ネットワーク用)を使用します。操作が完了すると、コールバックはメインスレッドによって実行されるように、イベントループキューにプッシュされます。
希少性: 一般的 難易度: 普通
4. Node.js の Streams とその種類について説明してください。
回答: Streams は、連続したチャンクでソースからデータを読み取ったり、宛先にデータを書き込んだりできるオブジェクトです。データをすべてメモリにロードする必要がないため、メモリ効率が高くなります。
- 種類:
- Readable: データを読み取るため (例:
fs.createReadStream)。 - Writable: データを書き込むため (例:
fs.createWriteStream)。 - Duplex: 読み取りと書き込みの両方 (例: TCP ソケット)。
- Transform: 出力が入力に基づいて計算される Duplex ストリーム (例:
zlib.createGzip)。
- Readable: データを読み取るため (例:
希少性: 一般的 難易度: 普通
5. Streams における Backpressure と、その処理方法は何ですか?
回答: バックプレッシャーは、Readable StreamがWritable側の処理速度を超えてデータを生成するときに起こります。無視するとバッファが増え、GC負荷が高まり、プロセスがメモリ不足になる可能性があります。
stream.pipeline()またはnode:stream/promisesのpipelineを使う: Streamを接続し、エラー伝播とクリーンアップを正しく行えます。.write()の戻り値を尊重する:falseが返ったら、追加書き込みの前にdrainを待ちます。- 慎重にチューニングする:
highWaterMarkは特定のワークロードで有効ですが、計測なしに上げるとメモリ側へ問題を移すだけです。 - アップロードでは: ファイル全体をメモリに載せず、Object Storageや処理パイプラインへ直接ストリーミングします。
希少性: 普通 難易度: 難しい
6. cluster モジュールはどのように機能しますか?
回答:
Node.js はシングルスレッドであるため、単一の CPU コアで実行されます。cluster モジュールを使用すると、同じサーバーポートを共有する子プロセス(ワーカー)を作成できます。
- マスタープロセス: ワーカーを管理します。
- ワーカープロセス: それぞれがアプリケーションのインスタンスを実行します。
- 利点: 使用可能なすべての CPU コアを利用して、スループットを向上させることができます。
希少性: 一般的 難易度: 普通
7. Worker Threads vs Cluster モジュール: どちらをいつ使用しますか?
回答:
- Cluster: 別のプロセスを作成します。それぞれが独自のメモリ空間と V8 インスタンスを持っています。HTTP サーバー(I/O バウンド)のスケーリングに最適です。
- Worker Threads: 単一のプロセス内にスレッドを作成します。メモリを共有します(
SharedArrayBuffer経由)。メインのイベントループをブロックしないように、CPU 集中型タスク(例:画像処理、暗号化)に最適です。
希少性: 普通 難易度: 難しい
8. キャッチされない例外と未処理の Promise の拒否をどのように処理しますか?
回答:
- キャッチされない例外:
process.on('uncaughtException', cb)をリッスンします。アプリケーションの状態が破損している可能性があるため、通常はエラーをログに記録して(PM2 などのプロセス管理ツールを使用して)プロセスを再起動するのが最善です。 - 未処理の拒否:
process.on('unhandledRejection', cb)をリッスンします。 - ベストプラクティス: 常に
try/catchブロックと Promise で.catch()を使用します。
希少性: 一般的 難易度: 簡単
9. package-lock.json の役割は何ですか?
回答: これは、生成された正確なツリーを記述するもので、後続のインストールで、中間的な依存関係の更新に関係なく、同一のツリーを生成できます。これにより、プロジェクトがすべてのマシン(CI/CD、他の開発者)でまったく同じように動作することが保証されます。
希少性: 一般的 難易度: 簡単
10. Express.js の Middleware の概念について説明してください。
回答:
Middleware 関数は、リクエストオブジェクト (req)、レスポンスオブジェクト (res)、およびアプリケーションのリクエスト-レスポンスサイクル内の次の Middleware 関数 (next) にアクセスできる関数です。
- タスク: コードの実行、req/res オブジェクトの変更、リクエスト-レスポンスサイクルの終了、次の Middleware の呼び出し。
- 順序: 定義された順序で順番に実行されます。
希少性: 一般的 難易度: 簡単
システム設計とアーキテクチャ (10個の質問)
11. リアルタイムチャットアプリケーションをどのように設計しますか?
回答:
- プロトコル: 全二重通信用の WebSockets (
socket.ioまたはwsを使用)。 - バックエンド: Node.js は、多数の同時接続を処理するイベント駆動型の性質があるため、理想的です。
- スケーリング:
- Redis Pub/Sub: 複数のサーバーインスタンスがある場合、サーバー A に接続されたユーザーは、サーバー B のユーザーにメッセージを送信する必要があります。Redis Pub/Sub は、サーバー間でメッセージをブロードキャストするメッセージブローカーとして機能します。
- データベース:
- メッセージ: 高い書き込みスループットのための NoSQL (MongoDB/Cassandra)。
- ユーザー: リレーショナル (PostgreSQL) または NoSQL。
希少性: 非常に一般的 難易度: 難しい
12. Node.js のマイクロサービス: 通信パターン。
回答:
- 同期: HTTP/REST または gRPC。単純なリクエスト/レスポンスに適しています。
- 非同期: メッセージキュー (RabbitMQ、Kafka、SQS)。サービスの分離と負荷スパイクの処理に適しています。
- イベント駆動型: サービスがイベントを発行し、他のサービスがリッスンします。
希少性: 一般的 難易度: 普通
13. 分散トランザクション (Saga パターン) はどのように処理しますか?
回答: マイクロサービスでは、トランザクションが複数のサービスにまたがる場合があります。ACID を保証するのは困難です。
- Saga パターン: ローカルトランザクションのシーケンス。各ローカルトランザクションはデータベースを更新し、イベントまたはメッセージを発行して、Saga の次のローカルトランザクションをトリガーします。
- 補償: ローカルトランザクションが失敗した場合、Saga は一連の補償トランザクションを実行して、先行するローカルトランザクションによって行われた変更を元に戻します。
希少性: 普通 難易度: 難しい
14. サーキットブレーカーパターンについて説明してください。
回答: 失敗する可能性が高い操作(例:ダウンしたマイクロサービスの呼び出し)をアプリケーションが繰り返し実行しようとするのを防ぎます。
- 状態:
- クローズ: リクエストは通過します。
- オープン: サービスを呼び出さずに、リクエストはすぐに失敗します(高速フェイル)。
- ハーフオープン: サービスが回復したかどうかを確認するために、限られた数のリクエストを許可します。
希少性: 普通 難易度: 普通
15. Node.js API をどのように保護しますか?
回答: 強い回答は、単なるパッケージ一覧ではなく脅威モデリングから始まります。Node.js APIでは次を押さえます。
- 認証と認可: 身元を検証し、オブジェクト単位の権限を強制し、クライアントが送るuser IDやtenant IDを信用しません。
- 入力検証: 境界で型、形式、範囲、content type、リクエストサイズをZodやJoiなどで検証します。
- 通信とヘッダー: HTTPS、必要に応じた安全なCookie、CORS allowlist、Helmetやプラットフォームによるヘッダー制御を使います。
- 悪用対策: rate limit、timeout、body size制限、reverse proxyで低速または大量アクセスを抑えます。
- 依存関係とシークレット: 依存関係をlockし、脆弱なパッケージを監視し、シークレットをコードに置かず、漏洩時はローテーションします。
- Observability: セキュリティ上重要な失敗を、機密情報を漏らさずに記録します。
希少性: 一般的 難易度: 普通
16. サーバーレスとは何ですか?また、Node.js とどのように適合しますか?
回答: サーバーレス(例:AWS Lambda)を使用すると、サーバーをプロビジョニングまたは管理せずにコードを実行できます。
- Node.js の適合性: Node.js は、起動時間(コールドスタート)が速く、軽量であるため、サーバーレスに最適です。
- ユースケース: API エンドポイント、イベント処理 (S3 アップロード)、スケジュールされたタスク。
希少性: 普通 難易度: 普通
17. GraphQL と REST について説明してください。いつ GraphQL を使用しますか?
回答:
- REST: 複数のエンドポイント、データの過剰フェッチまたは過少フェッチ。
- GraphQL: 単一のエンドポイント、クライアントは必要なものだけを要求します。
- GraphQL の使用: 複雑なデータ要件がある場合、異なるデータ形状を必要とする複数のクライアント (Web、モバイル) がある場合、またはネットワークラウンドトリップを減らす場合。
希少性: 一般的 難易度: 普通
18. Node.js でのキャッシュの実装方法。
回答:
- インメモリ:
node-cache(単一インスタンスには適していますが、再起動時にデータが失われ、共有されません)。 - 分散: Redis (業界標準)。
- 戦略: Cache-Aside、Write-Through。
- HTTP キャッシュ: ETag、Cache-Control ヘッダーを使用します。
希少性: 一般的 難易度: 普通
19. データベース接続プーリング。
回答: リクエストごとに新しいデータベース接続を開くのはコストがかかります。
- プーリング: 再利用できるデータベース接続のキャッシュを維持します。
- Node.js:
pg(PostgreSQL) やmongooseなどのライブラリは、プーリングを自動的に処理します。ワークロードと DB の制限に基づいて、プールサイズを構成する必要があります。
希少性: 普通 難易度: 普通
20. Node.js でのファイルアップロードの処理方法。
回答:
- Multipart/form-data: ファイルアップロードの標準エンコード。
- ライブラリ:
multer(Express 用の Middleware)、formidable、busboy。 - ストレージ: サーバーのファイルシステムにファイルを保存しないでください (ステートレス)。AWS S3 などのクラウドストレージにアップロードします。ファイルをメモリにロードしないように、ファイルを S3 に直接ストリーミングします。
希少性: 一般的 難易度: 普通
パフォーマンスとテスト (10個の質問)
21. Node.js のメモリリークをどのようにデバッグしますか?
回答:
- 症状: 時間の経過とともにメモリ使用量が増加する (RSS)、最終的なクラッシュ。
- ツール:
- Node.js インスペクター:
--inspectフラグ、Chrome DevTools で接続します。 - ヒープスナップショット: スナップショットを取得して比較し、ガベージコレクションされていないオブジェクトを見つけます。
process.memoryUsage(): プログラムで監視します。
- Node.js インスペクター:
希少性: 一般的 難易度: 難しい
22. Node.js アプリケーションのプロファイリング。
回答: プロファイリングは、CPU ボトルネックを特定するのに役立ちます。
- 組み込みプロファイラー:
node --prof app.js。ログファイルを生成します。node --prof-process isolate-0x...logで処理します。 - Clinic.js: パフォーマンスの問題を診断するためのツールスイート (
clinic doctor、clinic flame、clinic bubbleprof)。
希少性: 普通 難易度: 難しい
23. 「イベントループをブロックしない」ルールについて説明してください。
回答: スレッドが 1 つしかないため、長時間実行される同期操作(例:重い計算、同期ファイル読み取り、複雑な正規表現)を実行すると、イベントループが停止します。他のリクエストは処理できません。
- 解決策: 計算を分割する (setImmediate)、Worker Threads を使用する、またはマイクロサービスにオフロードする。
希少性: 非常に一般的 難易度: 簡単
24. Node.js のユニットテストと統合テスト。
回答:
- ユニットテスト: 個々の関数/モジュールを分離してテストします。依存関係をモックします。(ツール:Jest、Mocha、Chai)。
- 統合テスト: モジュールがどのように連携するかをテストします (例:API エンドポイント + データベース)。(ツール: Supertest)。
希少性: 一般的 難易度: 簡単
25. TDD (テスト駆動開発) とは何ですか?
回答: コードの前にテストを記述する開発プロセス。
- 失敗するテストを記述します (赤)。
- テストに合格するために最小限のコードを記述します (緑)。
- コードをリファクタリングします (リファクタリング)。
希少性: 普通 難易度: 普通
26. 本番環境の Node.js アプリでログをどのように処理しますか?
回答: 本番ログは、構造化され、検索しやすく、Observabilityツールに安全に送れる必要があります。
- Loggerを使う: 散発的な
console.logではなく、Pino、Winston、またはプラットフォームのLoggerを使います。 - 構造: request ID、安全な範囲のuser/tenant識別子、route、status、latency、error metadataをJSONで出力します。
- レベル: error、warn、info、debugを一貫して使います。
- マスキング: token、password、完全な決済情報、個人の機密コンテンツをログに出しません。
- 相関: logs、metrics、tracesを結び、複数サービスにまたがる障害を調査できるようにします。
希少性: 一般的 難易度: 簡単
27. セマンティックバージョニング (SemVer) について説明してください。
回答:
形式: MAJOR.MINOR.PATCH (例: 1.2.3)。
- MAJOR: 互換性のない API の変更。
- MINOR: 後方互換性のある機能。
- PATCH: 後方互換性のあるバグ修正。
^vs~:^1.2.3は<2.0.0に更新されます。~1.2.3は<1.3.0に更新されます。
希少性: 一般的 難易度: 簡単
28. 環境変数とは何ですか?また、どのように管理しますか?
回答:
- 目的: DB URL、API キーなど、環境 (開発、ステージング、本番) によって異なる構成。
- 使用法:
process.env.VARIABLE_NAME。 - 管理: ローカル開発用の
.envファイル (dotenvパッケージを使用)。本番環境では、OS またはコンテナ/プラットフォームの設定で設定します。
希少性: 一般的 難易度: 簡単
29. Node.js アプリケーションをどのようにデプロイしますか?
回答:
- プロセス管理ツール: PM2 (アプリを稼働させ続け、再起動、ログを処理します)。
- リバースプロキシ: Nginx (SSL、静的ファイル、ロードバランシングを処理します) -> Node.js アプリ。
- コンテナ化: Docker (標準)。
- オーケストレーション: Kubernetes。
- CI/CD: GitHub Actions、Jenkins。
希少性: 一般的 難易度: 普通
30. イベントエミッターとは何ですか?
回答:
events モジュールは、Node.js イベント駆動型アーキテクチャの中核です。
- 使用法:
- 多くのコアモジュールがそれを拡張します:
fs.ReadStream、net.Server、http.Server。
希少性: 一般的 難易度: 簡単


