Express 5完全実践ガイド〜TypeScript・middleware・認証・エラーハンドリング・REST API設計【2026年版】〜

「Express 5の新機能と最新ベストプラクティスを実コードで把握したい」「TypeScript・middleware・認証・エラーハンドリング・テスト・デプロイまで全部一気通貫で学びたい」。そんな声に応える完全実践ガイドです。本記事は Express 5 + Node.js 22 LTS を前提に、最小サーバーから本番運用までを 40 個以上のコピペで動くコードで解説します。Hono / Fastify との比較、Express 4 からの移行も網羅します。

この記事を最後まで読むと身につくこと

  • Express 5 の最小サーバー〜本番運用までの全コード
  • TypeScript + Zod + JWT による型安全な REST API の組み立て方
  • middleware の順序・async エラー処理・グレースフルシャットダウン
  • helmet / cors / rate-limit / compression / morgan など必須エコシステム
  • supertest + vitest によるテスト、Docker / PM2 によるデプロイ
  1. 1. Express 5 の概要と Node.js 22 環境セットアップ
    1. 1.1 Express 5 で何が変わったか
    2. 1.2 Node.js / npm のバージョン確認
    3. 1.3 プロジェクト初期化
    4. 1.4 Express 5 のインストール
    5. 1.5 推奨 tsconfig.json
    6. 1.6 開発スクリプト
  2. 2. 最小 Express サーバーと REST ルーティングの基礎
    1. 2.1 最小の Hello World サーバー
    2. 2.2 app.get / post / put / patch / delete
    3. 2.3 パスパラメータとクエリパラメータ
    4. 2.4 レスポンス整形のパターン
  3. 3. Router によるモジュール化と大規模設計
    1. 3.1 Router の基本
    2. 3.2 親アプリへのマウント
    3. 3.3 Router のネストと共通プレフィックス
    4. 3.4 Router 単位の middleware 適用
  4. 4. middleware の基礎・順序・必須エコシステム
    1. 4.1 middleware の基本シグネチャ
    2. 4.2 middleware の登録順序が重要
    3. 4.3 body-parser(Express 組み込み)
    4. 4.4 CORS の設定
    5. 4.5 helmet によるセキュリティヘッダ
    6. 4.6 express-rate-limit でレート制限
    7. 4.7 圧縮(compression)
    8. 4.8 ログ:morgan(開発)/ pino(本番)
  5. 5. リクエストバリデーションとエラーハンドリング
    1. 5.1 Zod によるリクエストバリデーション
    2. 5.2 Express 5 の async middleware(自動エラー伝播)
    3. 5.3 Express 4 で必要だった async wrapper(参考)
    4. 5.4 カスタムエラークラス
    5. 5.5 エラーハンドリング middleware
    6. 5.6 404 ハンドラ
  6. 6. 認証:JWT / セッション / Cookie
    1. 6.1 JWT 発行と検証
    2. 6.2 認証 middleware
    3. 6.3 パスワードハッシュとログイン
    4. 6.4 cookie-parser
    5. 6.5 express-session(セッションベース認証)
    6. 6.6 Passport.js(ローカル戦略)
  7. 7. ファイル・ストリーミング・リアルタイム通信
    1. 7.1 静的ファイル配信
    2. 7.2 multer によるファイルアップロード
    3. 7.3 テンプレートエンジン(EJS)
    4. 7.4 Pug テンプレート
    5. 7.5 SSE(Server-Sent Events)
    6. 7.6 WebSocket(socket.io)
  8. 8. テスト・本番運用・ベストプラクティス
    1. 8.1 supertest + vitest によるルートテスト
    2. 8.2 認証付きエンドポイントのテスト
    3. 8.3 グレースフルシャットダウン
    4. 8.4 環境変数バリデーション
    5. 8.5 trust proxy(リバプロ配下の本番)
    6. 8.6 Dockerfile(マルチステージ)
    7. 8.7 PM2 によるプロセス管理
    8. 8.8 Express 4 → 5 の移行ポイント
    9. 8.9 Hono / Fastify との比較表
    10. 8.10 まとめチェックリスト
    11. 関連記事

1. Express 5 の概要と Node.js 22 環境セットアップ

1.1 Express 5 で何が変わったか

Express 5 は 2024 年に正式版がリリースされ、最大の特徴は async/await の自動エラー伝播、Node.js 18 以上の必須化、依存パッケージの整理、path-to-regexp v8 への更新です。Express 4 で必須だった express-async-errors や手書きの try/catch ラッパーが不要になり、コードが大幅にシンプルになります。

1.2 Node.js / npm のバージョン確認

# Node.js 22 LTS (2024-10 リリース) を推奨
node -v
# v22.11.0

npm -v
# 10.9.0

1.3 プロジェクト初期化

mkdir my-express-app && cd my-express-app
npm init -y
# package.json の "type" を ESM に
npm pkg set type=module

1.4 Express 5 のインストール

# 安定版の Express 5 をインストール
npm install express@5

# TypeScript と型定義
npm install -D typescript tsx @types/node @types/express

# tsconfig.json の初期化
npx tsc --init --target esnext --module nodenext --moduleResolution nodenext 
  --esModuleInterop --strict --outDir dist

1.5 推奨 tsconfig.json

{
  "compilerOptions": {
    "target": "ES2023",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": ["ES2023"],
    "outDir": "dist",
    "rootDir": "src",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "declaration": false
  },
  "include": ["src/**/*"]
}

1.6 開発スクリプト

{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js",
    "test": "vitest run",
    "lint": "eslint ."
  }
}

2. 最小 Express サーバーと REST ルーティングの基礎

2.1 最小の Hello World サーバー

// src/index.ts
import express from "express";

const app = express();
const PORT = Number(process.env.PORT ?? 3000);

app.get("/", (req, res) => {
  res.json({ message: "Hello Express 5!" });
});

app.listen(PORT, () => {
  console.log(`listening on http://localhost:${PORT}`);
});

2.2 app.get / post / put / patch / delete

import express, { Request, Response } from "express";
const app = express();
app.use(express.json());

// GET 一覧
app.get("/users", (req: Request, res: Response) => {
  res.json({ users: [] });
});

// POST 作成
app.post("/users", (req, res) => {
  const { name } = req.body as { name: string };
  res.status(201).json({ id: 1, name });
});

// PUT 全更新
app.put("/users/:id", (req, res) => {
  res.json({ id: req.params.id, ...req.body });
});

// PATCH 部分更新
app.patch("/users/:id", (req, res) => {
  res.json({ id: req.params.id, patch: req.body });
});

// DELETE 削除
app.delete("/users/:id", (req, res) => {
  res.status(204).end();
});

2.3 パスパラメータとクエリパラメータ

// /users/42?include=posts
app.get("/users/:id", (req, res) => {
  const id = Number(req.params.id);           // 42
  const include = String(req.query.include);  // "posts"
  res.json({ id, include });
});

// 複数パラメータ
app.get("/orgs/:orgId/repos/:repoId", (req, res) => {
  const { orgId, repoId } = req.params;
  res.json({ orgId, repoId });
});

2.4 レスポンス整形のパターン

// JSON
res.json({ ok: true });

// ステータス + JSON
res.status(201).json({ id });

// テキスト
res.type("text/plain").send("ok");

// HTML
res.type("html").send("<h1>hi</h1>");

// 共通ラッパー(API レスポンスの規約化)
function ok<T>(res: Response, data: T, status = 200) {
  return res.status(status).json({ ok: true, data });
}
function fail(res: Response, code: string, message: string, status = 400) {
  return res.status(status).json({ ok: false, error: { code, message } });
}

3. Router によるモジュール化と大規模設計

3.1 Router の基本

// src/routes/users.ts
import { Router } from "express";

export const usersRouter = Router();

usersRouter.get("/", (req, res) => res.json([]));
usersRouter.get("/:id", (req, res) => res.json({ id: req.params.id }));
usersRouter.post("/", (req, res) => res.status(201).json(req.body));

3.2 親アプリへのマウント

// src/index.ts
import express from "express";
import { usersRouter } from "./routes/users.js";
import { postsRouter } from "./routes/posts.js";

const app = express();
app.use(express.json());

app.use("/api/v1/users", usersRouter);
app.use("/api/v1/posts", postsRouter);

3.3 Router のネストと共通プレフィックス

// /api/v1 の下にさらにグループを束ねる
import { Router } from "express";
import { usersRouter } from "./users.js";
import { postsRouter } from "./posts.js";

export const v1Router = Router();
v1Router.use("/users", usersRouter);
v1Router.use("/posts", postsRouter);

// index.ts
app.use("/api/v1", v1Router);

3.4 Router 単位の middleware 適用

import { Router } from "express";
import { requireAuth } from "../middleware/auth.js";

export const adminRouter = Router();
adminRouter.use(requireAuth("admin"));        // この Router 全体に認証

adminRouter.get("/stats", (req, res) => res.json({ ok: true }));
adminRouter.post("/purge", (req, res) => res.json({ purged: true }));

4. middleware の基礎・順序・必須エコシステム

4.1 middleware の基本シグネチャ

import { Request, Response, NextFunction } from "express";

// 最小 middleware: 必ず next() を呼ぶ
function logger(req: Request, res: Response, next: NextFunction) {
  console.log(req.method, req.url);
  next();
}

app.use(logger);

4.2 middleware の登録順序が重要

// Express は app.use の順に middleware を実行する
// 1) ボディパース → 2) ログ → 3) 認証 → 4) ルート → 5) エラー
app.use(express.json());                  // ボディパースは最初
app.use(morgan("combined"));              // ログ
app.use(requireAuth());                   // 認証
app.use("/api", apiRouter);               // ルート
app.use(errorHandler);                    // エラーは最後

4.3 body-parser(Express 組み込み)

// JSON ボディ(application/json)
app.use(express.json({ limit: "1mb" }));

// URL エンコード(application/x-www-form-urlencoded)
app.use(express.urlencoded({ extended: true, limit: "1mb" }));

// 生のバイト列(Webhook 検証など)
app.use("/webhook", express.raw({ type: "application/json", limit: "100kb" }));

4.4 CORS の設定

npm install cors
npm install -D @types/cors
import cors from "cors";

app.use(
  cors({
    origin: (origin, cb) => {
      const allow = ["https://example.com", "https://app.example.com"];
      if (!origin || allow.includes(origin)) cb(null, true);
      else cb(new Error("CORS not allowed"));
    },
    credentials: true,
    methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
  })
);

4.5 helmet によるセキュリティヘッダ

npm install helmet
import helmet from "helmet";

app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        "default-src": ["'self'"],
        "img-src": ["'self'", "data:", "https:"],
        "script-src": ["'self'"],
      },
    },
    crossOriginEmbedderPolicy: false,
  })
);

4.6 express-rate-limit でレート制限

npm install express-rate-limit
import rateLimit from "express-rate-limit";

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,   // 15 分
  max: 100,                    // 1 IP あたり 100 回まで
  standardHeaders: "draft-7",
  legacyHeaders: false,
  message: { ok: false, error: { code: "RATE_LIMITED" } },
});

app.use("/api/", apiLimiter);

4.7 圧縮(compression)

import compression from "compression";

// gzip / brotli ネゴシエーション(レスポンス自動圧縮)
app.use(compression({ threshold: 1024 }));

4.8 ログ:morgan(開発)/ pino(本番)

import morgan from "morgan";
app.use(morgan("dev"));
// 本番は pino-http(JSON 構造化ログで集約しやすい)
import pinoHttp from "pino-http";
import pino from "pino";

const logger = pino({ level: process.env.LOG_LEVEL ?? "info" });
app.use(pinoHttp({ logger }));

5. リクエストバリデーションとエラーハンドリング

5.1 Zod によるリクエストバリデーション

npm install zod
import { z } from "zod";
import { Request, Response, NextFunction } from "express";

// スキーマ定義
const CreateUser = z.object({
  body: z.object({
    name: z.string().min(1).max(80),
    email: z.string().email(),
    age: z.number().int().min(0).max(150).optional(),
  }),
});

// 汎用バリデータ middleware
export function validate<T extends z.ZodTypeAny>(schema: T) {
  return (req: Request, res: Response, next: NextFunction) => {
    const result = schema.safeParse({
      body: req.body,
      query: req.query,
      params: req.params,
    });
    if (!result.success) {
      return res.status(400).json({
        ok: false,
        error: { code: "VALIDATION", issues: result.error.issues },
      });
    }
    Object.assign(req, result.data);
    next();
  };
}

// 使い方
app.post("/users", validate(CreateUser), (req, res) => {
  res.status(201).json({ id: 1, ...req.body });
});

5.2 Express 5 の async middleware(自動エラー伝播)

// Express 5 では async ハンドラから throw すれば自動で error middleware に飛ぶ
app.get("/users/:id", async (req, res) => {
  const user = await db.user.findById(req.params.id);
  if (!user) throw new HttpError(404, "USER_NOT_FOUND");
  res.json(user);
});

5.3 Express 4 で必要だった async wrapper(参考)

// Express 4 では必要 / Express 5 では不要
export const ah =
  <T extends (req: Request, res: Response, next: NextFunction) => Promise<unknown>>(fn: T) =>
  (req: Request, res: Response, next: NextFunction) =>
    Promise.resolve(fn(req, res, next)).catch(next);

// Express 4 での使い方
app.get("/users/:id", ah(async (req, res) => { /* ... */ }));

5.4 カスタムエラークラス

// src/errors.ts
export class HttpError extends Error {
  constructor(public status: number, public code: string, message?: string) {
    super(message ?? code);
  }
}

export class NotFoundError extends HttpError {
  constructor(code = "NOT_FOUND") { super(404, code); }
}
export class UnauthorizedError extends HttpError {
  constructor(code = "UNAUTHORIZED") { super(401, code); }
}

5.5 エラーハンドリング middleware

// src/middleware/error.ts
import { ErrorRequestHandler } from "express";
import { HttpError } from "../errors.js";

export const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
  if (res.headersSent) return next(err);

  if (err instanceof HttpError) {
    return res.status(err.status).json({
      ok: false,
      error: { code: err.code, message: err.message },
    });
  }

  console.error(err);
  res.status(500).json({
    ok: false,
    error: { code: "INTERNAL", message: "internal server error" },
  });
};

5.6 404 ハンドラ

// すべてのルートの後 / errorHandler の前
app.use((req, res) => {
  res.status(404).json({ ok: false, error: { code: "NOT_FOUND" } });
});
app.use(errorHandler);

6. 認証:JWT / セッション / Cookie

6.1 JWT 発行と検証

npm install jsonwebtoken bcryptjs
npm install -D @types/jsonwebtoken @types/bcryptjs
import jwt from "jsonwebtoken";

const SECRET = process.env.JWT_SECRET!;

export function signToken(payload: object) {
  return jwt.sign(payload, SECRET, { expiresIn: "1h", algorithm: "HS256" });
}

export function verifyToken<T = unknown>(token: string): T {
  return jwt.verify(token, SECRET) as T;
}

6.2 認証 middleware

import { Request, Response, NextFunction } from "express";
import { verifyToken } from "../auth.js";
import { UnauthorizedError } from "../errors.js";

declare global {
  namespace Express {
    interface Request { user?: { id: string; role: "user" | "admin" } }
  }
}

export function requireAuth(role?: "user" | "admin") {
  return (req: Request, _res: Response, next: NextFunction) => {
    const h = req.headers.authorization;
    if (!h?.startsWith("Bearer ")) throw new UnauthorizedError();
    try {
      const payload = verifyToken<{ sub: string; role: "user" | "admin" }>(h.slice(7));
      req.user = { id: payload.sub, role: payload.role };
      if (role && payload.role !== role) throw new UnauthorizedError("FORBIDDEN");
      next();
    } catch {
      throw new UnauthorizedError();
    }
  };
}

6.3 パスワードハッシュとログイン

import bcrypt from "bcryptjs";
import { signToken } from "./auth.js";

app.post("/auth/register", async (req, res) => {
  const { email, password } = req.body as { email: string; password: string };
  const hash = await bcrypt.hash(password, 12);
  const user = await db.user.create({ email, password: hash });
  res.status(201).json({ id: user.id });
});

app.post("/auth/login", async (req, res) => {
  const { email, password } = req.body;
  const user = await db.user.findByEmail(email);
  if (!user) throw new UnauthorizedError();
  const ok = await bcrypt.compare(password, user.password);
  if (!ok) throw new UnauthorizedError();
  const token = signToken({ sub: user.id, role: user.role });
  res.json({ token });
});

6.4 cookie-parser

npm install cookie-parser
npm install -D @types/cookie-parser
import cookieParser from "cookie-parser";

app.use(cookieParser(process.env.COOKIE_SECRET));

app.post("/auth/cookie-login", (req, res) => {
  res.cookie("sid", "abc123", {
    httpOnly: true,
    secure: true,
    sameSite: "lax",
    signed: true,
    maxAge: 1000 * 60 * 60 * 24 * 7,
  });
  res.json({ ok: true });
});

6.5 express-session(セッションベース認証)

npm install express-session connect-redis ioredis
import session from "express-session";
import { RedisStore } from "connect-redis";
import { Redis } from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);

app.use(
  session({
    store: new RedisStore({ client: redis, prefix: "sess:" }),
    secret: process.env.SESSION_SECRET!,
    resave: false,
    saveUninitialized: false,
    cookie: { secure: true, httpOnly: true, sameSite: "lax", maxAge: 86_400_000 },
  })
);

6.6 Passport.js(ローカル戦略)

npm install passport passport-local
npm install -D @types/passport @types/passport-local
import passport from "passport";
import { Strategy as LocalStrategy } from "passport-local";

passport.use(
  new LocalStrategy(async (username, password, done) => {
    const user = await db.user.findByEmail(username);
    if (!user) return done(null, false);
    const ok = await bcrypt.compare(password, user.password);
    return ok ? done(null, user) : done(null, false);
  })
);

app.use(passport.initialize());
app.post("/auth/login", passport.authenticate("local", { session: false }), (req, res) =>
  res.json({ token: signToken({ sub: (req.user as any).id }) })
);

7. ファイル・ストリーミング・リアルタイム通信

7.1 静的ファイル配信

import path from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

app.use("/static", express.static(path.join(__dirname, "../public"), {
  maxAge: "1d",
  immutable: true,
  etag: true,
}));

7.2 multer によるファイルアップロード

npm install multer
npm install -D @types/multer
import multer from "multer";

const upload = multer({
  storage: multer.diskStorage({
    destination: "./uploads",
    filename: (req, file, cb) => cb(null, `${Date.now()}-${file.originalname}`),
  }),
  limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
  fileFilter: (req, file, cb) => {
    if (!/^image/(png|jpe?g|webp)$/.test(file.mimetype)) {
      return cb(new Error("INVALID_FILE_TYPE"));
    }
    cb(null, true);
  },
});

app.post("/upload", upload.single("image"), (req, res) => {
  res.json({ filename: req.file?.filename });
});

// 複数ファイル
app.post("/upload-multi", upload.array("images", 10), (req, res) => {
  res.json({ count: (req.files as Express.Multer.File[]).length });
});

7.3 テンプレートエンジン(EJS)

npm install ejs
app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "../views"));

app.get("/hello", (req, res) => {
  res.render("hello", { name: req.query.name ?? "world" });
});
<!-- views/hello.ejs -->
<!DOCTYPE html>
<html lang="ja">
<head><meta charset="utf-8" /><title>Hello</title></head>
<body><h1>Hello, <%= name %>!</h1></body>
</html>

7.4 Pug テンプレート

npm install pug
app.set("view engine", "pug");
// views/index.pug
// doctype html
// html
//   head
//     title= title
//   body
//     h1 Hello #{name}
app.get("/", (req, res) => res.render("index", { title: "top", name: "Express" }));

7.5 SSE(Server-Sent Events)

app.get("/sse", (req, res) => {
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache, no-transform");
  res.setHeader("Connection", "keep-alive");
  res.flushHeaders();

  const timer = setInterval(() => {
    res.write(`data: ${JSON.stringify({ t: Date.now() })}nn`);
  }, 1000);

  req.on("close", () => clearInterval(timer));
});

7.6 WebSocket(socket.io)

npm install socket.io
import http from "node:http";
import { Server as IOServer } from "socket.io";

const server = http.createServer(app);
const io = new IOServer(server, { cors: { origin: "*" } });

io.on("connection", (socket) => {
  socket.on("message", (msg) => io.emit("message", msg));
});

server.listen(3000);

8. テスト・本番運用・ベストプラクティス

8.1 supertest + vitest によるルートテスト

npm install -D vitest supertest @types/supertest
// src/app.ts (テストしやすいよう listen 前の app を export)
import express from "express";
export const app = express();
app.use(express.json());
app.get("/health", (req, res) => res.json({ ok: true }));
// tests/health.test.ts
import { describe, it, expect } from "vitest";
import request from "supertest";
import { app } from "../src/app.js";

describe("GET /health", () => {
  it("200 を返す", async () => {
    const res = await request(app).get("/health");
    expect(res.status).toBe(200);
    expect(res.body).toEqual({ ok: true });
  });
});

8.2 認証付きエンドポイントのテスト

import request from "supertest";
import { app } from "../src/app.js";
import { signToken } from "../src/auth.js";

const token = signToken({ sub: "u1", role: "user" });

it("認証ユーザはアクセスできる", async () => {
  const res = await request(app)
    .get("/api/v1/me")
    .set("Authorization", `Bearer ${token}`);
  expect(res.status).toBe(200);
});

it("トークンなしは 401", async () => {
  const res = await request(app).get("/api/v1/me");
  expect(res.status).toBe(401);
});

8.3 グレースフルシャットダウン

import http from "node:http";
import { app } from "./app.js";

const server = http.createServer(app);
server.listen(3000);

function shutdown(signal: NodeJS.Signals) {
  console.log(`received ${signal}, shutting down...`);
  server.close((err) => {
    if (err) { console.error(err); process.exit(1); }
    // ここで DB プール / Redis 接続を閉じる
    process.exit(0);
  });
  // 強制終了タイムアウト
  setTimeout(() => process.exit(1), 15_000).unref();
}

process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);

8.4 環境変数バリデーション

import { z } from "zod";

const Env = z.object({
  NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
  PORT: z.coerce.number().default(3000),
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
});

export const env = Env.parse(process.env);

8.5 trust proxy(リバプロ配下の本番)

// Nginx / ALB / Cloud Run の背後では必須
app.set("trust proxy", 1);

// req.ip が X-Forwarded-For ベースで取れるようになる
app.get("/ip", (req, res) => res.json({ ip: req.ip }));

8.6 Dockerfile(マルチステージ)

FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build && npm prune --production

FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
USER node
CMD ["node", "dist/index.js"]

8.7 PM2 によるプロセス管理

npm install -g pm2

# ecosystem.config.cjs
module.exports = {
  apps: [{
    name: "api",
    script: "dist/index.js",
    instances: "max",
    exec_mode: "cluster",
    env: { NODE_ENV: "production" },
  }],
};

pm2 start ecosystem.config.cjs
pm2 logs api
pm2 restart api --update-env

8.8 Express 4 → 5 の移行ポイント

// 1) async ハンドラからの reject が自動で next(err) に伝播
// 2) req.param() / app.del() / res.send(status) 等の廃止 API を置換
// 3) path-to-regexp v8: ":id?" など一部記法が変更(:id{/...} へ)
// 4) body-parser を組み込みに(express.json / express.urlencoded)

// 例: Express 4 のレガシ書き方
app.del("/x", (req, res) => res.send(204));
// ↓ Express 5
app.delete("/x", (req, res) => res.status(204).end());

8.9 Hono / Fastify との比較表

項目 Express 5 Fastify Hono
主なターゲット Node.js Node.js Edge / Workers / Node
速度 標準的 非常に高速 非常に高速
エコシステム 圧倒的 豊富 成長中
型安全 @types/express 標準で強い 標準で最も強い
学習コスト 低い 低い
採用基準 既存資産・教育コスト最優先 性能と型を両立 Edge デプロイ前提

8.10 まとめチェックリスト

  • Express 5 + Node 22 で type: "module" を採用したか
  • helmet / cors / rate-limit / compression / morgan(pino) を入れたか
  • Zod でリクエストバリデーションを統一したか
  • HttpError + errorHandler でレスポンス規約を揃えたか
  • JWT / セッションのどちらを採用するか方針が決まっているか
  • supertest でハッピーパス + 401/400/500 をカバーしたか
  • SIGTERM/SIGINT のグレースフルシャットダウンを実装したか
  • trust proxy / 環境変数バリデーションを本番に入れたか
学習リソース・スクールで体系化する

Express 5 はシンプルですが、認証・テスト・運用まで身につけると現場で即戦力です。独学が辛い人は短期集中で叩き込む選択肢もあります。

  • テックアカデミー:Node.js / Express を含む Web 開発が学べる(オンライン専門)
  • 侍エンジニア:マンツーマンで Express の API を成果物にできる
  • DMM WEBCAMP:Web エンジニア転職を見据えた長期コース
  • レバテックキャリア:Node.js / Express の実務求人を確認しキャリア設計

関連記事

コメント

タイトルとURLをコピーしました