Node.js入門|サーバーサイドJavaScriptの基本と実践ガイド

kento_morota 21分で読めます
社内のAI・IT活用、技術がわかる相手に無料相談。 相談 →
目次

Node.jsは、JavaScriptをサーバーサイドで実行できるランタイム環境です。フロントエンドでJavaScriptを使っている開発者にとって、同じ言語でバックエンドも開発できるという大きなメリットがあります。

本記事では、Node.jsの基本概念、環境構築、モジュールシステム、非同期処理、ファイル操作、そして簡単なHTTPサーバーの構築まで、初心者が押さえるべき基礎を網羅的に解説します。

Node.jsとは何か

Node.jsは、GoogleのV8 JavaScriptエンジン上に構築されたサーバーサイドのJavaScriptランタイムです。2009年にRyan Dahlによって開発され、現在ではWebアプリケーション、CLI ツール、IoTなど幅広い分野で利用されています。

Node.jsの特徴

シングルスレッド+イベントループ:Node.jsはシングルスレッドで動作しますが、イベントループと非同期I/Oにより、多数の同時接続を効率的に処理できます。ファイル読み込みやDB問い合わせなどのI/O処理中にスレッドがブロックされないため、少ないリソースで高い並行性を実現します。

ノンブロッキングI/O:従来のサーバー(ApacheやPHPなど)はリクエストごとにスレッドを割り当てますが、Node.jsはI/O待ちの間に他のリクエストを処理するノンブロッキングモデルを採用しています。

npm/pnpmエコシステム:世界最大のパッケージレジストリであるnpmを通じて、数百万のパッケージを利用できます。

フルスタックJavaScript:フロントエンドとバックエンドで同じ言語を使えるため、チームの学習コストが下がり、コードの共有も容易です。

Node.jsが得意な領域

Node.jsは以下の用途に特に適しています。

APIサーバー(REST/GraphQL)、リアルタイムアプリケーション(チャット、通知)、マイクロサービス、CLIツール、SSR(サーバーサイドレンダリング)、ビルドツール・開発ツール。

一方、CPU集約型の処理(画像処理、複雑な計算)はシングルスレッドの制約により得意ではありませんが、Worker Threadsを使えば対応可能です。

環境構築とプロジェクト初期化

Node.jsのインストール

Node.jsのバージョン管理には、voltaやnvmの使用を推奨します。プロジェクトごとに異なるバージョンを切り替えられます。

# voltaでのインストール(推奨)
curl https://get.volta.sh | bash
volta install node@22

# バージョン確認
node -v   # v22.x.x
npm -v    # 10.x.x

# pnpmのインストール(推奨パッケージマネージャー)
volta install pnpm

プロジェクトの初期化

# プロジェクトディレクトリ作成
mkdir my-node-app && cd my-node-app

# package.json の生成
pnpm init

# TypeScriptのセットアップ(推奨)
pnpm add -D typescript @types/node tsx
npx tsc --init
// package.json
{
  "name": "my-node-app",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js"
  },
  "devDependencies": {
    "@types/node": "^22.0.0",
    "tsx": "^4.19.0",
    "typescript": "^5.7.0"
  }
}
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true
  },
  "include": ["src/**/*"]
}

モジュールシステム

Node.jsには2つのモジュールシステムがあります。従来のCommonJS(require/module.exports)と、現在主流のESモジュール(import/export)です。

ESモジュール(推奨)

// src/utils/math.ts - モジュールのエクスポート
export function add(a: number, b: number): number {
  return a + b;
}

export function multiply(a: number, b: number): number {
  return a * b;
}

export const PI = 3.14159;

// デフォルトエクスポート
export default class Calculator {
  private result = 0;

  add(n: number): this {
    this.result += n;
    return this;
  }

  getResult(): number {
    return this.result;
  }
}
// src/index.ts - モジュールのインポート
import Calculator, { add, multiply, PI } from "./utils/math.js";

console.log(add(1, 2));       // 3
console.log(multiply(3, 4));   // 12
console.log(PI);               // 3.14159

const calc = new Calculator();
console.log(calc.add(10).add(20).getResult()); // 30

Node.js組み込みモジュール

// node: プレフィックス付きで組み込みモジュールをインポート
import path from "node:path";
import { fileURLToPath } from "node:url";
import os from "node:os";

// __dirname の代替(ESモジュールでは直接使えない)
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

console.log("現在のファイル:", __filename);
console.log("現在のディレクトリ:", __dirname);
console.log("OS:", os.platform());
console.log("CPUコア数:", os.cpus().length);
console.log("空きメモリ:", Math.floor(os.freemem() / 1024 / 1024), "MB");

非同期処理の基本

Node.jsの最大の特徴は非同期処理です。I/O操作(ファイル読み書き、ネットワーク通信、DB問い合わせ)は非同期で実行され、完了を待たずに次の処理へ進みます。

async/await

// 非同期関数の基本
async function fetchData(url: string): Promise<unknown> {
  try {
    const response = await fetch(url);

    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    console.error("データ取得に失敗:", error);
    throw error;
  }
}

// 複数の非同期処理を並列実行
async function fetchMultiple() {
  const urls = [
    "https://api.example.com/users",
    "https://api.example.com/products",
    "https://api.example.com/orders",
  ];

  // Promise.all で並列実行(すべて成功が必要)
  const results = await Promise.all(
    urls.map((url) => fetchData(url))
  );

  // Promise.allSettled で並列実行(一部失敗してもOK)
  const settled = await Promise.allSettled(
    urls.map((url) => fetchData(url))
  );

  settled.forEach((result, i) => {
    if (result.status === "fulfilled") {
      console.log(`${urls[i]}: 成功`);
    } else {
      console.log(`${urls[i]}: 失敗 - ${result.reason}`);
    }
  });
}

イベントループの理解

// イベントループの実行順序を理解する
console.log("1: 同期処理");

setTimeout(() => {
  console.log("2: setTimeout(マクロタスク)");
}, 0);

Promise.resolve().then(() => {
  console.log("3: Promise.then(マイクロタスク)");
});

process.nextTick(() => {
  console.log("4: process.nextTick");
});

console.log("5: 同期処理");

// 出力順序:
// 1: 同期処理
// 5: 同期処理
// 4: process.nextTick
// 3: Promise.then(マイクロタスク)
// 2: setTimeout(マクロタスク)

同期処理が最初に実行され、その後process.nextTick、Promiseのマイクロタスク、setTimeoutのマクロタスクの順で処理されます。

ファイル操作

Node.jsのfsモジュール(のPromises API)を使ったファイル操作の基本を見ていきます。

ファイルの読み書き

import fs from "node:fs/promises";
import path from "node:path";

// ファイル読み込み
async function readConfig(filePath: string): Promise<Record<string, unknown>> {
  try {
    const content = await fs.readFile(filePath, "utf-8");
    return JSON.parse(content);
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") {
      console.log("ファイルが見つかりません。デフォルト設定を使用します。");
      return {};
    }
    throw error;
  }
}

// ファイル書き込み
async function writeLog(message: string): Promise<void> {
  const logDir = path.join(process.cwd(), "logs");
  const logFile = path.join(logDir, "app.log");

  // ディレクトリがなければ作成
  await fs.mkdir(logDir, { recursive: true });

  const timestamp = new Date().toISOString();
  const logEntry = `[${timestamp}] ${message}\n`;

  // 追記モード
  await fs.appendFile(logFile, logEntry, "utf-8");
}

// ディレクトリ内のファイル一覧
async function listFiles(dirPath: string): Promise<string[]> {
  const entries = await fs.readdir(dirPath, { withFileTypes: true });

  const files: string[] = [];
  for (const entry of entries) {
    const fullPath = path.join(dirPath, entry.name);
    if (entry.isFile()) {
      files.push(fullPath);
    } else if (entry.isDirectory()) {
      // 再帰的にサブディレクトリも探索
      const subFiles = await listFiles(fullPath);
      files.push(...subFiles);
    }
  }
  return files;
}

ストリームによる大容量ファイル処理

import { createReadStream, createWriteStream } from "node:fs";
import { createInterface } from "node:readline";
import { pipeline } from "node:stream/promises";
import { createGzip } from "node:zlib";

// 大容量ファイルを1行ずつ処理
async function processLargeFile(filePath: string): Promise<void> {
  const stream = createReadStream(filePath, "utf-8");
  const rl = createInterface({ input: stream });

  let lineCount = 0;
  for await (const line of rl) {
    lineCount++;
    // 1行ずつ処理(メモリに全体を読み込まない)
    if (line.includes("ERROR")) {
      console.log(`Line ${lineCount}: ${line}`);
    }
  }
  console.log(`Total lines: ${lineCount}`);
}

// ファイルをgzip圧縮
async function compressFile(input: string, output: string): Promise<void> {
  await pipeline(
    createReadStream(input),
    createGzip(),
    createWriteStream(output)
  );
  console.log(`Compressed: ${input} -> ${output}`);
}

HTTPサーバーの構築

標準ライブラリでの基本サーバー

import http from "node:http";

interface RouteHandler {
  (req: http.IncomingMessage, res: http.ServerResponse): void;
}

const routes: Record<string, Record<string, RouteHandler>> = {
  GET: {
    "/": (req, res) => {
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ message: "Hello, Node.js!" }));
    },
    "/api/health": (req, res) => {
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify({
        status: "ok",
        uptime: process.uptime(),
        memory: process.memoryUsage(),
      }));
    },
  },
  POST: {
    "/api/echo": (req, res) => {
      let body = "";
      req.on("data", (chunk) => (body += chunk));
      req.on("end", () => {
        res.writeHead(200, { "Content-Type": "application/json" });
        res.end(JSON.stringify({ echo: JSON.parse(body) }));
      });
    },
  },
};

const server = http.createServer((req, res) => {
  const method = req.method || "GET";
  const url = req.url || "/";
  const handler = routes[method]?.[url];

  if (handler) {
    handler(req, res);
  } else {
    res.writeHead(404, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ error: "Not Found" }));
  }
});

const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

環境変数の管理

// Node.js 22以降は --env-file フラグで .env を読み込み可能
// node --env-file=.env src/index.ts

// 環境変数へのアクセス
const config = {
  port: parseInt(process.env.PORT || "3000", 10),
  nodeEnv: process.env.NODE_ENV || "development",
  dbUrl: process.env.DATABASE_URL || "",
};

if (!config.dbUrl) {
  console.error("DATABASE_URL is required");
  process.exit(1);
}

エラーハンドリングとプロセス管理

グローバルなエラーハンドリング

// キャッチされなかったPromiseエラー
process.on("unhandledRejection", (reason, promise) => {
  console.error("Unhandled Rejection:", reason);
  // ログ記録後にプロセスを安全に終了
});

// キャッチされなかった例外
process.on("uncaughtException", (error) => {
  console.error("Uncaught Exception:", error);
  // クリーンアップ処理後にプロセスを終了
  process.exit(1);
});

// シグナルハンドリング(Graceful Shutdown)
function gracefulShutdown(signal: string) {
  console.log(`${signal} received. Shutting down gracefully...`);

  server.close(() => {
    console.log("HTTP server closed");
    // DB接続のクローズなど
    process.exit(0);
  });

  // 10秒以内にクローズできなければ強制終了
  setTimeout(() => {
    console.error("Force shutdown");
    process.exit(1);
  }, 10000);
}

process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
process.on("SIGINT", () => gracefulShutdown("SIGINT"));

カスタムエラークラス

class AppError extends Error {
  constructor(
    message: string,
    public statusCode: number = 500,
    public code: string = "INTERNAL_ERROR"
  ) {
    super(message);
    this.name = "AppError";
  }
}

class NotFoundError extends AppError {
  constructor(resource: string) {
    super(`${resource}が見つかりません`, 404, "NOT_FOUND");
  }
}

class ValidationError extends AppError {
  constructor(
    message: string,
    public errors: Record<string, string[]> = {}
  ) {
    super(message, 400, "VALIDATION_ERROR");
  }
}

// 使用例
function getUser(id: number) {
  const user = users.find((u) => u.id === id);
  if (!user) {
    throw new NotFoundError("ユーザー");
  }
  return user;
}

まとめ

Node.jsは、JavaScriptの知識をそのまま活かしてサーバーサイド開発ができる強力なランタイムです。

基本概念:シングルスレッド+イベントループによるノンブロッキングI/Oが最大の特徴です。I/O多重度の高いアプリケーション(API、リアルタイム通信)に特に適しています。

モジュールシステム:ESモジュール(import/export)が現在の標準です。package.jsonに"type": "module"を指定し、node:プレフィックスで組み込みモジュールをインポートします。

非同期処理:async/awaitで直感的に記述でき、Promise.allやPromise.allSettledで並列処理も容易です。

ファイル操作:fs/promisesのAPI で非同期にファイルを読み書きし、大容量ファイルはストリームで効率的に処理します。

HTTPサーバー:標準のhttpモジュールでサーバーが構築でき、実務ではExpressやFastifyなどのフレームワークを組み合わせて使います。

次のステップとして、Express.jsを使ったREST API構築や、データベースとの連携を学んでいくとよいでしょう。

#Node.js#JavaScript#サーバーサイド
ManageAI 先行モニター企業 募集中|先着5社・2026年11月末まで

生成AIの利用費、「誰が・何のために・いくら」まで見えていますか?

株式会社カイダンと共同開発したAI利用ガバナンスSaaS「ManageAI」は、OpenAI・Azure OpenAI・Claude・AWS Bedrock・Google Vertex AIの費用と利用状況を、部署・利用者・プロジェクト別に可視化します。導入は読み取り専用キーの登録だけ。初期費用0円・月額5万円(税別)で先行モニター企業を募集しています。

ニュースリリースを読む
共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

※ プライバシーポリシーに基づき管理します

関連記事

Related / 9 articles

  1. プログラミング

    DDoS攻撃の仕組みと対策入門|レイヤー別の防御とCDN・クラウドの活用

    DDoS攻撃をボリューム型・プロトコル型・アプリ層に分けて仕組みを解説し、自前サーバーで防げない理由、CloudflareやAWS Shieldの標準防御、オリジンIPの隠し方、レートリミットとBot対策、攻撃を受けたときの初動、費用が跳ね上がるDenial of Walletへの備えまでわかります。

  2. プログラミング

    WAFとは?仕組み・導入パターン・誤検知対策|Webアプリを攻撃から守る実践ガイド

    WAFがファイアウォールやIDSと何が違うのか、シグネチャとマネージドルールの仕組み、Cloudflare WAF・AWS WAF・ModSecurityの比較、フォーム送信がブロックされる誤検知の調査と例外設定、ログ監視、WAFが代替できないことまで実践的に解説します。

  3. プログラミング

    セキュリティヘッダー入門|CSP・HSTS・X-Frame-Optionsの設定と効果を実践解説

    CSP・HSTS・X-Frame-Options・X-Content-Type-Optionsなど主要セキュリティヘッダーが防ぐ攻撃と、CSPのReport-Onlyからの段階導入、nonce/hash、HSTS preloadの不可逆リスク、Nginx・Next.js・Astroでの設定例、確認方法を解説します。

  4. プログラミング

    クラウドの通信費(Egress)入門|データ転送量課金の仕組みと転送コストを抑える設計

    クラウドの「受信無料・送信有料」の原則、AZ間・リージョン間・インターネット向けの単価差、NATゲートウェイ処理料の罠、CDNで転送量を減らす方法、バックアップやログ転送の見落とし、請求書で転送料を特定する手順を解説。想定外の請求を防げます。

  5. プログラミング

    秘密情報をGitに入れない仕組み|.gitignore・git-secrets・履歴から漏れた鍵の削除

    APIキーや.envをGitにコミットしてしまう典型経路と、.gitignore・.env.exampleの運用、pre-commitでのgitleaks検知、GitHub secret scanningの活用、漏れた鍵の無効化と履歴書き換え(git filter-repo)の手順を解説。仕組みで再発を防げます。

  6. プログラミング

    開発・ステージング・本番環境の分離設計|環境差分をなくす構成とアクセス制御

    開発・ステージング・本番それぞれの目的と、構成をコードで揃える方法、環境別の設定注入、本番データを使わないテストデータ戦略、ステージングの保護(Basic認証・IP制限・noindex)、コストを抑える運用までを解説。環境差分による本番障害を防げます。

  7. プログラミング

    ngrok・Cloudflare Tunnelでローカルを公開|Webhook開発とデモ環境の作り方

    NAT内のローカル環境にStripeやLINEのWebhookを届けるトンネリングの仕組みを解説。ngrok・Cloudflare Tunnel・localtunnelの比較、固定ドメインと認証、リクエスト検査、公開時のセキュリティ、自宅サーバー公開への応用までわかります。

  8. プログラミング

    ローカル開発環境のHTTPS化|mkcert・hostsファイル・自己署名証明書の正しい使い方

    ローカル開発をHTTPS前提にすべき理由(Secure Cookie・Service Worker・OAuth)と、mkcertでローカルCAを作りhostsで独自ドメインを割り当ててVite・Next.js・Dockerで使う手順を解説。証明書警告を無視する癖の危険も理解できます。

  9. プログラミング

    localhost・0.0.0.0・127.0.0.1の違い|ポートとUnixソケットを理解して「つながらない」を解決

    localhost・127.0.0.1・0.0.0.0の意味の違い、Dockerで外から接続できない原因、host.docker.internal、ポート競合の調べ方、Unixソケットの利点と権限、1024未満ポートの制約を解説。「つながらない」を仕組みから解決できます。

ManageAI

先行モニター企業 募集中

生成AIのコストを「部署別」に、請求書より先に。

先着5社限定・2026年11月末まで。初期費用0円・月額5万円(税別)で、正式版と同等の機能に加え、開発者と伴走者による導入支援を追加費用なしで提供します。プロンプト原文は一切取得しません。

株式会社カイダン × Harmonic Society株式会社の共同事業|読み取り専用キーの登録だけで導入できます