Node.js の概要と仕組み
Node.js は、サーバーサイドにおける JavaScript 実行を可能にするオープンソースのランタイム環境です。Google が開発した JavaScript エンジン「V8」を基盤としており、サーバー上で動作するアプリケーションに必要な最適化が施されています。
この環境を利用することで、以下のような多様なアプリケーション構築が可能になります。
- HTTP Web サーバーの構築
- マイクロサービスおよびサーバーレス API バックエンド
- データベース接続ドライバー
- 対話型 CLI ツール
- デスクトップアプリケーションおよびそのプラグイン
- IoT デバイス向けのリアルタイム通信
- システムスクリプトやファイル処理ツール
- 機械学習モデルの実行環境
アーキテクチャと動作原理
Node.js の最大の特徴は、単一スレッドで動作するイベント駆動型の非ブロッキング I/O モデルにあります。これにより、ブラウザ外で JavaScript コードを実行し、オペレーティングシステムの I/O、ファイルシステム、ネットワークに直接アクセスすることが可能です。
単一スレッドであるため JavaScript の呼び出しスタックは一つであり、同時に実行できるタスクは一つだけです。しかし、「イベントループ」がコードの実行、イベントの収集・処理、キュー内の次のタスク実行を管理することで、高い并发処理能力を実現しています。
ここでの「スレッド」とは OS が独立して管理する命令序列を指し、「并发」とはイベントループが他の処理を終えた後にコールバックを実行する能力を意味します。
Node.js において I/O 操作はブロッキング操作として扱われますが、イベントループが非ブロッキング异步请求を実装します。イベントループの主要なフェーズは以下の通りです。
- Timers:
setTimeoutやsetIntervalによってスケジュールされたコールバックを実行 - Pending Callbacks: 保留中のコールバックを実行
- Poll: 入力 I/O イベントを取得し、関連するコールバックを実行
- Check: ポール段階の直後にコールバックを実行(
setImmediateなど) - Close Callbacks: 閉じるイベントのコールバックを実行
このモデルは、libuv ライブラリによって提供される非ブロッキング I/O API に支えられており、ファイルシステムやデータベース操作、ネットワーク応答などを効率的に処理します。
インストールと環境構築
Node.js を導入するには、いくつかの一般的な方法があります。
- 公式サイトからインストーラーをダウンロードして実行
- macOS や Linux 向けのパッケージマネージャー(Homebrew など)を利用
- バージョン管理ツール(nvm や fnm)を使用して複数のバージョンを切り替え
インストールが完了したら、以下のコマンドでバージョンを確認し、正しく設定されたか検証します。
node -v
バージョン表記には、長期サポート版(LTS)と、現在開発中の最新版が存在します。安定性を重視する場合は LTS の利用が推奨されます。
npm パッケージ管理の基礎
Node.js エコシステムには「npm レジストリ」が存在し、数百万以上のモジュールやライブラリが公開されています。これにより、Web サーバー、フレームワーク、CLI ツールなど、多様な機能を既存のコードを利用して実装できます。
package.json の構造
プロジェクトの根幹となる package.json は、プロジェクトの_manifest_ファイルです。このファイルは手動で作成することもできますが、通常は CLI コマンドを用いて生成します。
npm init
このコマンドはウィザード形式で项目名称、バージョン、説明などの入力を促します。すべての項目にデフォルト値を使用する場合は、-y オプションを付与します。
npm init -y
生成されたファイルの主要な属性は以下のグループに分類されます。
- メタ情報: 项目名称、説明、著者、ライセンスなど
- 依存関係:
dependencies(本番環境用)とdevDependencies(開発環境用) - スクリプト: ビルド、テスト、起動などのタスク定義
Node.js ランタイムはスクリプトの命名規則を推奨しており、プロジェクト間での一貫性を保つ役割を果たします。
start: エントリーポイントファイルを指定して Node を起動build: 成果物を生成するビルドプロセスtest: テストスイートの実行lint: コードの品質チェック(ESLint など)
スクリプトの実行は npm run <script-name> で行いますが、start と test は run を省略可能です。
実装例
実際にプロジェクトを作成して動作を確認してみましょう。
demo-appというディレクトリを作成し、その中にappフォルダを設けます。app/main.jsを作成し、以下のコードを記述します。
console.log('System ready');
- ディレクトリ内で
npm init -yを実行し、package.jsonを生成します。 - 生成されたファイルの内容を以下のように編集します。
{
"name": "demo-app-core",
"version": "0.1.0",
"description": "Node.js npm initialization demo",
"main": "app/main.js",
"scripts": {
"dev": "node app/main.js",
"check": "echo \"No tests configured\" && exit 1"
},
"author": "developer",
"license": "MIT"
}
- 以下のコマンドを実行して動作を確認します。
npm run dev
出力結果:
System ready
パッケージの追加と管理
既存のパッケージを利用することで、開発時間の短縮やコード品質の向上が図れます。ただし、依存関係のサイズ、ライセンス条項、メンテナンス状況については事前に評価が必要です。
パッケージのインストールは以下のコマンドで行います。
npm install <package-name>
これにより、グローバルレジストリからコードが取得され、プロジェクト内の node_modules ディレクトリに配置されます。-g オプションを付与するとグローバルインストールとなります。
依存関係には以下の 2 種類があります。
- 本番依存 (dependencies): アプリケーションの実行に必須なライブラリ(例:Web フレームワーク)
- 開発依存 (devDependencies): 開発段階でのみ必要なツール(例:テストランナー、リンター、バンドラー)
--production フラグを指定してインストールすると、本番依存のみがインストールされます。インストール済みのパッケージ一覧は npm list で確認でき、更新が必要なパッケージは npm outdated で把握できます。
セキュリティ脆弱性が見つかった場合は、ログ出力に従って npm audit コマンドを使用します。
npm audit fix: 問題のない範囲でバージョンを自動更新npm audit fix --force: 破壊的変更を含むバージョンへ更新
また、一時的なコマンド実行には npx が有用です。これは依存関係をダウンロードして実行後、自動的に清理するため、グローバルインストールを避けたい場合に適しています。
不要な依存関係の清理
プロジェクトからパッケージを削除するには、以下の方法があります。
- アンインストール:
npm uninstall <package-name>を実行すると、マニフェストファイルとnode_modulesの両方から削除されます。 - 不要ファイルの削除:
npm pruneコマンドは、マニフェストファイルに記載されていないnode_modules内のパッケージを削除します。これを使用する際は、事前にpackage.jsonから該当のエントリを削除しておく必要があります。
セマンティックバージョニング
バージョン番号は「メジャー.マイナー.パッチ」(例:1.2.3)の形式で表され、それぞれ以下の意味を持ちます。
- メジャーバージョン: 互換性のない変更が含まれる。コードの修正が必要になる可能性がある。
- マイナーバージョン: 後方互換性を保った新機能の追加。安全に更新可能。
- パッチバージョン: 後方互換性を保ったバグ修正。安全に更新可能。
package.json では、バージョン指定に以下の記号を使用して更新範囲を制御できます。
*: 最新のメジャーバージョンへ更新^: マイナーバージョンまで更新可能(例:^1.2.3は1.x.xの最新)~: パッチバージョンまで更新可能(例:~1.2.3は1.2.xの最新)
"devDependencies": {
"mocha": "^10.2.0"
}
package-lock.json の役割
package.json とともに package-lock.json が生成されます。このファイルは、インストールされたパッケージの正確なバージョンと依存関係ツリーを記録します。
バージョン管理システムにこのファイルをコミットすることで、環境問わず同一の依存関係構成を再現でき、インストールの最適化や変更履歴の追跡も可能になります。
インストール動作の優先順位は以下の通りです。
package.jsonとpackage-lock.jsonの仕様が一致している場合、ロックファイルの内容が優先され、指定された正確なバージョンがインストールされます。package.jsonのバージョン範囲がロックファイルと矛盾する場合、パッケージマネージャーはpackage.jsonの制約を満たす新しいバージョンを解決しようと試みます。