---
title: "Express.jsとは？Node.jsバックエンド入門とExpress 5.3の変更点・4系からの移行"
url: "https://www.issoh.co.jp/tech/details/3857/"
published: 2024-10-16
updated: 2026-10-28
categories: ["Node.js"]
publisher: "株式会社一創"
---

# Express.jsとは？Node.jsバックエンド入門とExpress 5.3の変更点・4系からの移行

Express.jsはNode.jsでバックエンドを書くときの事実上の標準フレームワークです。ただ、いま調べ物をすると情報の世代がずれています。日本語の解説記事の多くはExpress 4を前提にしていますが、`npm install express` で入るのは**Express 5**で、2026年10月9日には**5.3.0**が出ました。Content-Typeが合わないときの `req.body` が `{}` ではなく `undefined` になる、`app.get('/*')` が起動時にエラーになる——記事どおりに書いて動かない原因はたいていこの世代差です。この記事では、Expressが何を肩代わりしているのかをNode.js標準の `node:http` との差分で押さえ、Express 5.3時点の書き方と4系からの移行点を、手元で実行した結果つきで整理します。

## まとめ

- **Express.jsとは**：`node:http` だけでは手書きになるURL分岐（ルーティング）、共通処理の連鎖（ミドルウェア）、ボディのパースや静的ファイル配信、レスポンス生成を引き受ける最小構成のWebフレームワーク。DB接続・認証・バリデーションは同梱しない。
- **最新版は5.3.0（2026年10月9日）**。4系は `latest-4` タグの**4.22.3**（2026年9月14日）。新規開発は5系を選ぶ。
- **4系のEOLはまだ決まっていない**。2025年の計画では「2026年10月1日より前にはしない」とされていたが、2026年10月28日時点の公式サポート表で4.xの終了日は「ongoing」のまま。ただし機能追加は来ない。
- **移行で最初に踏む地雷は3つ**：Content-Typeが合わないときの `req.body` が `{}` ではなく `undefined`、ワイルドカードは `/*splat` と命名必須、`res.send(200)` のような数値ステータスの廃止。公式codemodはこのうち `/*` を書き換えない。
- **async関数のthrowが自動でエラーハンドラへ流れる**のがExpress 5最大の実利。`express-async-errors` は不要になる。
- **Node.jsは18以上が必須**だが、18と20はすでにサポート終了。本番はNode.js 24か、2026年10月28日にLTS入りする予定の26を選ぶ。

## Express.jsの役割：node:httpとの機能差

Express.jsは2010年にTJ Holowaychukが公開し、現在はOpenJS Foundationのプロジェクトとして開発されているNode.js用のWebフレームワークです。機能を並べるより、Node.jsに最初から入っている `node:http` で同じものを書いたときに何が残るかを見たほうが、役割を正確につかめます。Node.js自体の仕組みは[Node.jsとは？仕組み・版の選び方・標準機能で動かす手順を実装目線で解説](/tech/details/17433/)で扱っています。

### Expressが肩代わりしている処理

`node:http` でJSONを返すAPIを2本用意すると、こうなります。

```
import http from 'node:http';

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://${req.headers.host}`);

  if (req.method === 'GET' && url.pathname === '/users') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify([{ id: 1 }]));
    return;
  }
  if (req.method === 'POST' && url.pathname === '/users') {
    let body = '';
    req.on('data', (chunk) => { body += chunk; });
    req.on('end', () => {
      const data = JSON.parse(body);   // 壊れたJSONで例外、サイズ上限も自前
      res.writeHead(201, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify(data));
    });
    return;
  }
  res.writeHead(404).end();
});
server.listen(3000);
```

メソッドとパスの分岐、ボディのストリーム受信とJSONパース、Content-Typeとステータスコードの手当て、404のフォールバック——アプリ固有のロジックはほとんど無いのに、定型処理で埋まります。ルートが増えるほど、この `if` の連鎖では分岐の順序や共通処理の重複を管理する負担が増えます。

Expressでは同じものをこう書きます。

```
import express from 'express';

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

app.get('/users', (req, res) => {
  res.json([{ id: 1 }]);
});

app.post('/users', (req, res) => {
  res.status(201).json(req.body);
});

app.listen(3000);
```

Expressが引き受けているのは、(1)メソッド＋パスによるルーティング、(2)リクエストを順に通す共通処理＝ミドルウェア、(3)ボディのパースや静的ファイル配信、(4) `res.json()` のようなレスポンス生成のショートカットです。データベース接続・認証・バリデーション・ORMは**同梱していません**。RailsやDjangoのようなフルスタックではなく、必要なものをnpmから選んで足す前提の設計で、この薄さがExpressの性格そのものです。

### 「Expressを使わない」判断が成り立つ場面

Node.jsには `fetch` や `URL` などのWeb標準APIが本体に入っており、素の `node:http` でも昔ほど苦しくありません。ルートが2〜3本のヘルスチェック用サーバーや、AWS Lambdaのようにルーティングをプラットフォーム側（API Gateway）が持つ構成では、Expressを挟む必然性は薄くなります。

判断基準は**ルートが増えるか、共通処理（認証・ログ・CORS・エラー整形）を横断的にかけたいか**の2つです。どちらかがYesなら、ルーティングとミドルウェアの仕組みを自作せずに済むExpressを第一候補にします。どちらもNoなら `node:http` のままでかまいません。

## Express 5と4の違い：移行前に押さえる破壊的変更

いちばん事故が起きるのがここです。Express 4を前提にした日本語記事も残っていますが、新規インストールで入るのは5系です。

### バージョンの現況と4系のサポート期限

npmレジストリの配布タグを2026年10月28日に確認した結果は次のとおりです。

| 系統  | npmタグ      | 最新版    | 公開日        | Node.js要件 |
| --- | ---------- | ------ | ---------- | --------- |
| 5.x | `latest`   | 5.3.0  | 2026-10-09 | 18以上      |
| 4.x | `latest-4` | 4.22.3 | 2026-09-14 | 0.10以上    |

Express 5.0.0は2024年9月に公開され、5.1.0が2025年3月31日に `latest` タグを取得しました。そのときの[Express公式ブログのLTS計画](https://expressjs.com/en/blog/2025-03-31-v5-1-latest-release/)では、4.xは2025年4月1日にMAINTENANCE（セキュリティ修正と優先度の高いバグ修正だけを受ける段階）へ移り、EOLは「2026年10月1日より前にはしない（no sooner than）」とされました。同じ表の注記には「v4は特例で、MAINTENANCEを延長する可能性がある」「目標であって約束ではない」とも書かれています。

実際、その日付を過ぎた2026年10月28日時点でも、[公式のVersion Supportページ](https://expressjs.com/en/support/)は4.xのサポート終了日を「ongoing」としており、EOLの告知はありません。4.22.3でも依存の `qs` を6.16系に上げる修正が入っています。とはいえ4系に新機能は来ないので、「まだ切られていない」を理由に移行を先送りするのは得策ではありません。EOLの告知が出てから慌てないよう、移行の段取りは今のうちに組んでおくべきです。

Node.js側は、[Node.jsのリリーススケジュール](https://github.com/nodejs/release)で18と20がすでにEOL（20は2026年4月30日）、22と24はMaintenance LTS（24は2026年10月20日から・2028年4月30日まで）、26は2026年10月28日にActive LTSへ移る予定です。Express 5の下限は18ですが、本番で選ぶのは24か26です。26の変更点は[Node.js 26の新機能と破壊的変更・LTS移行日｜24からの移行手順【26.11.1対応】](/tech/details/12014/)、プロジェクトごとの切り替えは[Nodebrewとは？Node.jsのインストール・切り替え手順と1.2.0の注意点【2026年】](/tech/details/3343/)で確認できます。

### Express 5の破壊的変更：リクエスト・ルート・レスポンス

[公式の移行ガイド（Express 5への移行）](https://expressjs.com/ja/guide/migrating-5/)から、既存コードが実際に動かなくなる変更を抜き出すと次のとおりです。

| 項目                                   | Express 4                | Express 5                               |
| ------------------------------------ | ------------------------ | --------------------------------------- |
| パーサ登録済み・Content-Type不一致時の `req.body` | `{}`                     | `undefined`                             |
| ワイルドカード                              | `/*`                     | `/*splat`（命名必須・値は配列）                    |
| 任意パラメータ                              | `/:file.:ext?`           | `/:file{.:ext}`                         |
| 数値ステータス送信                            | `res.send(200)`          | 廃止 → `res.sendStatus(200)`              |
| ボディ＋ステータス                            | `res.json(obj, 201)`     | 廃止 → `res.status(201).json(obj)`        |
| リダイレクト                               | `res.redirect(url, 301)` | `res.redirect(301, url)`（引数順が逆）         |
| `req.query`                          | 書き換え可                    | getter（代入不可）                            |
| `urlencoded` の `extended`            | 既定 `true`                | 既定 `false`                              |
| `express.static` の `dotfiles`        | ドットファイルは無視・ドットディレクトリ内は配信 | 既定 `ignore`                             |
| 削除されたメソッド                            | `app.del()`              | `app.delete()`                          |
| 削除されたメソッド                            | `req.param()`            | `req.params` / `req.query` / `req.body` |
| 削除されたメソッド                            | `res.sendfile()`         | `res.sendFile()`                        |

実務でいちばん多いのは `req.body` です。Express 4では `express.json()` を登録していてもContent-Typeが対象外なら `req.body` は空オブジェクトになり、`req.body.name` は静かに `undefined` を返していました。Express 5では `req.body` 自体が `undefined` なので、同じコードが `TypeError: Cannot read properties of undefined` で落ちます。4.22.3と5.3.0で、`express.json()` を登録した状態に `text/plain` のボディを送ると、4系は `{}`、5系は `undefined` でした。パーサを1つも登録していない場合は、4系でも `undefined` です。

ルーティング側は、内部のパスマッチャがpath-to-regexp v8に上がった影響です。`app.get('/*', handler)` は登録した時点で `Missing parameter name at index 2: /*` という例外を投げ、サーバーが起動しません。`app.get('/files/*splat', handler)` と定義すると、`/files/a/b` に対して `req.params.splat` は `['a', 'b']` の**配列**になります。正規表現文字を直接埋めていた `'/[a|b]/:slug'` のような書き方は、パスの配列 `['/a/:slug', '/b/:slug']` に置き換えます。

### Express 5で消える定型：async関数のエラー処理

移行の手間に見合う最大の見返りがこれです。Express 4では、非同期ハンドラ内で `throw` された例外はフレームワークに捕捉されず、プロセスを落とすか、リクエストが応答なしでハングしました。そのため全ハンドラに `try/catch` を書いて `next(err)` に渡すか、`express-async-errors` を当てるのが定石でした。

Express 5は、**ハンドラが返した拒否済みPromiseをエラーハンドリングミドルウェアへ自動で転送します**。

```
// Express 5：try/catch も next(err) も不要
app.get('/users/:id', async (req, res) => {
  const user = await db.findUser(req.params.id);   // ここで reject しても
  if (!user) throw new NotFoundError();            // ここで throw しても
  res.json(user);                                  // 下のエラーハンドラへ届く
});

// エラーハンドラは引数4つ（この形でだけエラー用と認識される）
app.use((err, req, res, next) => {
  // 最小例。5xxの文言を隠す本番向けの形は後述
  res.status(err.status ?? 500).json({ message: err.message });
});
```

エラーハンドラは引数が**4つ**（`err, req, res, next`）であることでエラー用と判別されるため、`next` を使わないからと省略すると通常のミドルウェア扱いになり、エラーを受け取れません。また**すべてのルート定義より後ろ**に置く必要があります。

### 公式codemodで自動移行できる範囲と残る手作業

Expressチームは移行用のcodemodを公開しています。4系のコード（`app.del`・`res.send(200)`・`res.redirect('/y', 301)`・`res.json(obj, 201)`・`req.param('id')`・`'/*'`・`res.sendfile` を1ファイルに並べたもの）に対して実行しました。

```
npx codemod@latest @expressjs/v5-migration-recipe
```

結果は次のとおりです。

- `app.delete`・`res.sendStatus(200)`・`res.redirect(301, '/y')`・`res.status(201).json(...)`・`req.params.id`・`res.sendFile` へ書き換わった。ただし、`req.param('id')` がボディやクエリから値を取得していた場合、`req.params.id` への変更では動作を維持できない。
- **`app.get('/*', ...)` はそのまま残った**。起動時の例外は手で `/*splat` に直す必要がある。
- `res.sendFile` には `{ dotfiles: 'allow' }` が自動で足された。codemodは「4系の挙動を残す」とコメントを付けるが、`'allow'` は `.env` を含むすべてのドットファイルを配信する設定で、4系の既定より広い（`express.static` を4.22.3で試すと、`.env` は404、`.well-known` 内のファイルは200だった）。不要なら消す。
- `package.json` の `express` は `^5.0.0` に上がり、使っていなくても `mime-types` が依存に追加された。
- 対象ディレクトリがGit管理下でないと「not tracked by Git」で止まる。差分を確認できる状態で実行する前提の作り。

`req.body` の `undefined` 化と `urlencoded` の `extended` 既定値は、codemodでも起動時のエラーでも検出されず、**実行時に初めて壊れます**。手順は、Node.jsを24以上に上げる→codemodを当てて差分を読む→`/*` を `grep` で探して命名する→`express.json()` と `express.urlencoded({ extended: true })` を明示する、の順です。着手前に `supertest` でHTTPレベルのテストを用意しておくと、実行時にしか出ない壊れ方を拾えます（テストランナーは[Vitest 4の新機能と移行ガイド｜最新バージョンは5.0・4.1系の保守と更新手順](/tech/details/9465/)を参照）。

## Express 5.3.0で変わった挙動：5.2系から上げる前の確認点

5.3.0はマイナー版ですが、[リリースノート](https://github.com/expressjs/express/releases/tag/v5.3.0)には出力が変わる変更が含まれます。5.2.1と5.3.0で同じコードを動かし、差が出たものを並べます（Node.js 26.5.0で実行）。

| コード                                          | 5.2.1の出力                 | 5.3.0の出力                 |
| -------------------------------------------- | ------------------------ | ------------------------ |
| `res.attachment('user.html')`                | `filename="user.html"`   | `filename=user.html`     |
| `Content-Type: text/plain; foo=bar` で `send` | `charset=utf-8; foo=bar` | `foo=bar; charset=utf-8` |
| 既定エラーハンドラのログ                                 | スタックのみ                   | `[cause]` と独自プロパティも出力    |

ファイル名の引用符が外れるのは `content-disposition` 2.0系への更新によるもので、RFC 6266上は同じ意味です。壊れるのは、`Content-Disposition` や `Content-Type` のヘッダ文字列を完全一致で検証しているテストのほうです。5.3.0に上げてスナップショットテストが落ちたら、まずここを疑ってください。

既定のエラーハンドラがエラーオブジェクト全体を出すようになったため、`new Error('...', { cause })` で包んだ原因がログに残ります。ORMの例外が持つ詳細プロパティも出るので、ログ基盤に機微な値が流れていないかは確認が要ります。

セキュリティ面では、依存の `body-parser` が `^2.3.0` に上がりました。`express.json({ limit: 'abc' })` のように不正な `limit` を渡すと、以前は上限が黙って無効になっていたのが、初期化時に `option limit "abc" is invalid` で例外を投げます。設定ミスで起動しなくなったら、それは上限が効いていなかった状態が表に出ただけです。`req.ip` を左右する `proxy-addr` も2.0.8へ上がっています（後述の「セキュリティ更新の追い方」で説明）。

## Expressの使い方：インストールからAPI公開まで

### セットアップとサーバー起動

ESM（`import`）で書くため `"type": "module"` を指定します。

```
mkdir my-api && cd my-api
npm init -y
npm pkg set type=module
npm install express        # 5.3.0 が入る（2026年10月時点）
npm install -D nodemon
```

```
// app.js
import express from 'express';

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

app.use(express.json());                                  // JSONボディを解釈
app.use(express.urlencoded({ extended: true }));          // フォームのネストを許可
app.use(express.static('public'));                        // public/ を静的配信

app.get('/health', (req, res) => res.json({ status: 'ok' }));

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

`node app.js` で起動し、`http://localhost:3000/health` が `{"status":"ok"}` を返せば成功です。開発中は `npx nodemon app.js` にすると保存のたびに再起動します（Node.js 22以降なら `node --watch app.js` でも可）。`express.static('public')` では `public/style.css` が `/style.css` で配信され、ディレクトリ名はURLに出ません。

### ルーティング：メソッド・パスパラメータ・クエリ

```
// パスパラメータ： /users/42 → req.params.id === '42'（文字列）
app.get('/users/:id', (req, res) => {
  res.json({ id: Number(req.params.id) });
});

// クエリ文字列： /search?q=node&page=2 → req.query.q, req.query.page
app.get('/search', (req, res) => {
  const { q, page = '1' } = req.query;   // 5系では req.query への代入は不可
  res.json({ q, page: Number(page) });
});

// Express 5のワイルドカード：命名必須・値は配列
app.get('/files/*splat', (req, res) => {
  res.json({ segments: req.params.splat });   // /files/a/b → ['a','b']
});   // /files 自体は404。含めるなら '/files{/*splat}'
```

`req.params.id` のような通常の名前付きパラメータは文字列で、ワイルドカードは文字列配列です。通常のパラメータを数値として扱うなら明示的に変換します。ルートが増えたら `express.Router()` でファイルを分け、`app.use('/users', usersRouter)` のようにプレフィックスを付けてマウントします。

### POSTのJSON・フォームデータ：標準パーサによる受信

古い記事は `npm install body-parser` から始まりますが、**Express 4.16以降、body-parserの機能は本体に取り込まれています**。追加インストールは不要で、`express.json()` と `express.urlencoded()` をそのまま使います。

```
app.use(express.json({ limit: '1mb' }));                 // Content-Type: application/json
app.use(express.urlencoded({ extended: true }));         // HTMLフォーム（x-www-form-urlencoded）

app.post('/users', (req, res) => {
  if (!req.body?.name) {                                 // 5系では req.body 自体が undefined になりうる
    return res.status(400).json({ message: 'name is required' });
  }
  res.status(201).json({ id: 1, name: req.body.name });
});
```

`req.body` が `undefined` のままなら、原因はほぼ2つです。`express.json()` を `app.post()` より**後ろ**に登録している（ミドルウェアは登録順に実行される）か、クライアントが `Content-Type: application/json` を送っていないかです。パーサはContent-Typeを見て動くので、ヘッダが無ければ何もしません。ファイルアップロード（`multipart/form-data`）は本体では扱えず、`multer`（2026年10月時点で2.5.0）などを足します。

## ミドルウェアの仕組み：next()と登録順

ミドルウェアは `(req, res, next)` を受け取る関数で、**登録した順に**リクエストを通していきます。`next()` を呼べば次へ進み、`res.json()` などで応答すればそこで打ち切られます。`next()` も応答もしなければ、リクエストは宙吊りになります。ミドルウェアで応答が止まる場合は、まず各分岐でnext()かレスポンス送信に到達しているかを確認します。

```
// 全リクエストの所要時間をログに出す
app.use((req, res, next) => {
  const start = process.hrtime.bigint();
  res.on('finish', () => {
    const ms = Number(process.hrtime.bigint() - start) / 1e6;
    console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${ms.toFixed(1)}ms`);
  });
  next();                       // 呼ばないとここで止まる
});

// 特定パスへの適用例：ヘッダの有無だけを確認。実際の認証にはトークンの検証が必要
app.use('/admin', (req, res, next) => {
  if (!req.headers.authorization) return res.status(401).end();
  next();
});
```

「登録順がすべて」という性質から、配置は決まってきます。ログやCORSのような全体にかけるものを最上段、`express.json()` などのパーサをルート定義より前、認証を必要なルートの直前、**404ハンドラとエラーハンドラを最下段**に置きます。

## バックエンドフレームワークの選定：Fastify・Hono・NestJSとの使い分け

### Express・Fastify・Hono・NestJSの設計思想と適用場面

| フレームワーク | 設計思想                   | 向く場面                                 |
| ------- | ---------------------- | ------------------------------------ |
| Express | 最小構成・自由に組み立てる          | 一般的なREST API、既存資産と情報量を重視             |
| Fastify | JSON Schemaで検証・シリアライズ  | スキーマ駆動でスループットを詰めたいAPI                |
| Hono    | Web標準のRequest/Response | Cloudflare Workers・Deno・Bunなど複数ランタイム |
| NestJS  | DI＋デコレータでレイヤを規定        | 大規模・多人数で構造を揃えたい開発                    |

NestJSはExpressの**競合ではなく上位レイヤ**です。NestJSは既定でExpressをHTTPアダプタとして内部に抱えており、NestJSを選ぶことはExpressを捨てることを意味しません（ValidationPipeなどNestJS側の作法は[DTOとは？意味・Entityとの違いとNestJSのValidationPipe設定【v12実測】](/tech/details/6208/)で扱っています）。逆にHonoはNode.js固有のAPIに依存せずWeb標準のRequest/Responseで組まれているため、エッジランタイムへ載せる前提ならExpressより素直に収まります。HonoのAPIと書き味は[Hono vs FastAPI（Python）の違い｜Honoとは・メリット・使い方を実行結果で比較【Hono 4.13】](/column/details/3001/)が詳しいです。

### Expressを選ぶべきでない場面

次のどれかに当てはまるなら、Expressは第一候補から外して構いません。

**Cloudflare Workersなどのエッジ環境で動かす場合。**ExpressはNode.jsの `http` モジュールに強く依存しており、Web標準APIベースのランタイムでは互換レイヤ頼みになります。最初からHonoで書くほうが摩擦は小さくなります。

**複数チームでモノリスを開発し、レイヤ構造とDIの規約を統一したい場合。**Expressは構造を一切強制しないため、ディレクトリ構成もDIも各自の裁量になり、規模が増えるほど設計のばらつきが直接コストになります。レイヤ構造とDIを枠組みとして強制するNestJSのほうが、長期の保守は安定します。

**リクエスト単価のスループットが要件に直結する場合。**Fastifyはレスポンスのシリアライズ自体をJSON Schemaから最適化する設計で、同じJSONを返す用途ではExpressより有利です。ただしDBや外部APIの待ち時間が支配的なアプリでは、フレームワークだけを替えてもレイテンシの改善は限定的です。フレームワークの速さを理由にExpressを避ける前に、ボトルネックが本当にフレームワーク層にあるかを計測してください。DBや外部APIが支配的なら、先にその待ち時間を改善します。

既存の業務システムとExpressで作ったAPIをどうつなぐかまで含めて検討する場合は、弊社の[API開発・システム連携](/service/system/api/)でも設計から支援しています。

## 本番運用の初期設定：CORS・エラー処理・TypeScript・セキュリティ更新

### CORS（コルス）：別オリジンへのレスポンス共有設定

CORS（Cross-Origin Resource Sharing、読み方は「コルス」）は、別オリジンのJavaScriptにレスポンスの読み取りを許可するため、サーバーが応答ヘッダでブラウザに条件を伝える仕組みです。CORS自体はAPIの認証・認可を行いません。ReactやVueの開発サーバー（例：`http://localhost:5173`）からExpress（`http://localhost:3000`）を呼ぶと、ブラウザがCORSポリシーでブロックします。ExpressにCORS機能は同梱されていないため、Express公式org（expressjs/cors）が管理するミドルウェアを入れます。プリフライトの仕組みとエラーの読み方は[CORSとは？仕組み・プリフライト・サーバー設定例・エラー解決を実装目線で解説](/tech/details/13763/)で扱っています。

```
npm install cors
```

```
import cors from 'cors';

app.use(cors({
  origin: ['http://localhost:5173', 'https://app.example.com'],   // 許可オリジンを列挙
  credentials: true,                                              // Cookie を送る場合
}));
```

`app.use(cors())` と引数なしで書くと `Access-Control-Allow-Origin: *` になり、資格情報を含まないリクエストでは、あらゆるオリジンのJavaScriptによるレスポンスの読み取りを許します。認証情報を使わず広く公開するAPIでは本番でも使えます。読み取り元を限定するAPIでは、許可するオリジンを明示してください。`credentials: true` とワイルドカードのオリジンは仕様上併用できないため、Cookieを使うなら必ずオリジンを列挙します。

### 404ハンドラとエラーハンドラの最下段配置

```
// すべてのルート定義の後ろ：どのルートにも当たらなかった場合
app.use((req, res) => {
  res.status(404).json({ message: 'Not Found' });
});

// さらに後ろ：引数4つのエラーハンドラ
app.use((err, req, res, next) => {
  console.error(err);
  if (res.headersSent) return next(err);
  const candidate = err.status ?? err.statusCode;
  const status = Number.isInteger(candidate) && candidate >= 400 && candidate <= 599 ? candidate : 500;
  res.status(status).json({
    message: status < 500 ? err.message : 'Internal Server Error',   // 500の詳細は外に出さない
  });
});
```

500系のメッセージやスタックトレースをJSONで返すと、内部構造の露出につながります。500以上のエラーとステータスを持たない想定外の例外は固定文言に丸め、詳細はログにだけ残します。レスポンスの送信が始まった後のエラーは `next(err)` で既定のエラーハンドラに渡し、接続を閉じさせます。

### TypeScript対応：@types/expressとRequest拡張

Express本体はJavaScriptで書かれており、型定義は `@types/express`（2026年10月時点で5.0.6）を別途入れます。

```
npm install -D typescript @types/express @types/node tsx
```

```
import express, { type Request, type Response } from 'express';

const app = express();

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

認証ミドルウェアで `req.user` のような独自プロパティを生やすと、TypeScriptは「`Request` に `user` は存在しない」とエラーを出します。`as any` で潰さず、宣言のマージで `Request` 自体を拡張します。

```
// types/express.d.ts
declare global {
  namespace Express {
    interface Request {
      user?: { id: string; role: string };
    }
  }
}
export {};
```

このファイルを `tsconfig.json` の `include` に含めれば、`req.user?.role` が型付きで参照できます。

### セキュリティ更新の追い方：本体よりミドルウェアの修正が多い

Expressチームは[公式ブログでセキュリティリリース](https://expressjs.com/en/blog/2026-09-30-security-releases/)をほぼ毎月まとめて告知しています。2026年9月30日の回では、`compression`・`cookies`・`hbs`・`morgan`・`multer`・`multiparty`・`proxy-addr` の7件が修正されました。Express本体ではなく、周辺のミドルウェアや依存モジュールが対象になる回が大半です。

この回でCriticalとされたのが `proxy-addr` 2.0.8未満の不具合です。`app.set('trust proxy', '::ffff:10.0.0.0/8')` のように信頼するプロキシをIPv4射影IPv6表記で書くと、すべての接続元を「信頼できるプロキシ」とみなし、攻撃者が `X-Forwarded-For` で `req.ip` を任意の値にできました。IPで絞った管理画面やレート制限が素通りになる種類の不具合です。

運用では次の2つを決めておきます。

- `package-lock.json` がある状態で `npm update express compression multer` のように使っているミドルウェア名を並べて更新し、`npm ls proxy-addr` で推移的依存の版まで確認する。
- `trust proxy` は `true` ではなく、実際に前段にいるロードバランサのアドレスで指定する。ホップ数を指定する場合は、設定値より短い経路でクライアントが到達できないことを確認する。

なお、Node.js本体の脆弱性は別系統で告知されます。影響の確認方法は[Node.jsの脆弱性とは｜影響の確認方法と対応バージョン・アップデート手順【2026年10月最新】](/tech/details/10243/)にまとめています。

## よくある質問（FAQ）

### Express.jsとNode.jsの違いは？

Node.jsはJavaScriptの実行環境（ランタイム）で、Express.jsはNode.js上で動くWebフレームワークです。Node.jsだけでもサーバーは書けますが（標準の `node:http`）、ルーティングやボディのパースを自前で組むことになります。ExpressはNode.jsを置き換えるものではなく、その定型部分を引き受けるライブラリです。

### ExpressとReactの違いは？

動く場所が違います。Reactはブラウザで画面を組み立てるフロントエンドのUIライブラリで、ExpressはサーバーでHTTPリクエストを受けて応答するバックエンドのフレームワークです。両者は競合せず、ReactのアプリがExpressで作ったAPIをfetchで呼ぶ組み合わせが典型です。その際、オリジンが違えばExpress側にCORSの設定が要ります。

### Express 4と5のどちらを使うべき？

新規開発は5系です。`npm install express` で入るのは5系（2026年10月時点で5.3.0）です。4系は4.22.3まで保守が続いていますが、2025年4月からセキュリティ修正と重大なバグ修正だけの段階にあり、EOLの日付は2026年10月28日時点で未発表です。既存の4系アプリも、Node.jsを24以上に上げたうえで移行を計画してください。

### body-parserはインストールが必要？

不要です。Express 4.16以降、JSONとURLエンコードのパーサは本体に入っており、`express.json()` と `express.urlencoded()` で足ります。`npm install body-parser` から始まる解説はそれ以前の情報です。ファイルアップロード（`multipart/form-data`）だけは本体で扱えず、`multer` などが要ります。

### req.bodyがundefinedになるのはなぜ？

Express 5では、パーサが処理しなかったリクエストの `req.body` は、4系の `{}` ではなく `undefined` です。`express.json()` を登録していない、ルート定義より後ろに登録している、リクエストに `Content-Type: application/json` が付いていない、のいずれかを疑ってください。

## 関連記事

- [Node.jsとは？仕組み・版の選び方・標準機能で動かす手順を実装目線で解説](/tech/details/17433/)
- [Node.js 26の新機能と破壊的変更・LTS移行日｜24からの移行手順【26.11.1対応】](/tech/details/12014/)
- [CORSとは？仕組み・プリフライト・サーバー設定例・エラー解決を実装目線で解説](/tech/details/13763/)
- [Node.jsの脆弱性とは｜影響の確認方法と対応バージョン・アップデート手順【2026年10月最新】](/tech/details/10243/)
- [REST APIとGraphQLの違いと使い分け｜選定基準と運用コスト](/tech/details/3591/)

---

出典: [Express.jsとは？Node.jsバックエンド入門とExpress 5.3の変更点・4系からの移行](<https://www.issoh.co.jp/tech/details/3857/>)（株式会社一創）
