express の使い方 — Node.js定番Webフレームワーク完全ガイド
一言でいうと
ExpressはNode.js上で動作する、軽量かつ柔軟なWebアプリケーションフレームワークです。ルーティング・ミドルウェア・HTTPユーティリティを最小限の構成で提供し、REST APIからフルスタックWebアプリまで幅広く構築できます。
どんな時に使う?
- REST APIサーバーの構築 — CRUD操作を提供するバックエンドAPIを素早く立ち上げたい時
- Webアプリケーションのバックエンド — テンプレートエンジンと組み合わせてSSRするサーバーを作りたい時
- BFF(Backend for Frontend)やプロキシサーバー — フロントエンドとマイクロサービス群の間に中間層を置きたい時
インストール
# npm
npm install express
# yarn
yarn add express
# pnpm
pnpm add express
TypeScriptで使う場合は型定義も追加します。
npm install -D @types/express typescript @types/node
注意: この記事はExpress v5.2.1 を対象としています。v5はv4から複数の破壊的変更があります。v4系を使用している場合はAPIの差異にご注意ください。
基本的な使い方(Express入門)
最もシンプルなサーバーの例です。
import express, { Request, Response } from 'express';
const app = express();
const PORT = 3000;
// JSONボディのパース(Express 4.16+ / 5.x 組み込み)
app.use(express.json());
// ルート定義
app.get('/', (req: Request, res: Response) => {
res.json({ message: 'Hello, Express!' });
});
// サーバー起動
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
# 実行(ts-node使用の場合)
npx ts-node server.ts
よく使うAPI — Express主要メソッド5選
1. app.get() / app.post() / app.put() / app.delete() — ルーティング
HTTPメソッドに対応するルートハンドラを登録します。
import express, { Request, Response } from 'express';
const app = express();
app.use(express.json());
interface User {
id: number;
name: string;
}
const users: User[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
// 一覧取得
app.get('/users', (req: Request, res: Response) => {
res.json(users);
});
// 個別取得(v5ではパスパラメータの型が改善されている)
app.get('/users/:id', (req: Request, res: Response) => {
const user = users.find((u) => u.id === Number(req.params.id));
if (!user) {
res.status(404).json({ error: 'User not found' });
return;
}
res.json(user);
});
// 新規作成
app.post('/users', (req: Request, res: Response) => {
const newUser: User = {
id: users.length + 1,
name: req.body.name,
};
users.push(newUser);
res.status(201).json(newUser);
});
// 更新
app.put('/users/:id', (req: Request, res: Response) => {
const user = users.find((u) => u.id === Number(req.params.id));
if (!user) {
res.status(404).json({ error: 'User not found' });
return;
}
user.name = req.body.name;
res.json(user);
});
// 削除
app.delete('/users/:id', (req: Request, res: Response) => {
const index = users.findIndex((u) => u.id === Number(req.params.id));
if (index === -1) {
res.status(404).json({ error: 'User not found' });
return;
}
users.splice(index, 1);
res.status(204).send();
});
2. app.use() — ミドルウェアの登録
リクエスト処理パイプラインに共通処理を挿入します。Expressの設計思想の中核です。
import express, { Request, Response, NextFunction } from 'express';
const app = express();
// リクエストログミドルウェア
const requestLogger = (req: Request, res: Response, next: NextFunction) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.originalUrl} ${res.statusCode} - ${duration}ms`);
});
next();
};
// 全ルートに適用
app.use(requestLogger);
// 特定パス配下にのみ適用
app.use('/api', express.json());
// 静的ファイル配信
app.use('/public', express.static('public'));
app.get('/', (req: Request, res: Response) => {
res.send('Hello');
});
app.listen(3000);
3. express.Router() — ルーターの分割
大規模アプリケーションではルーティングをモジュール分割するのが定石です。
// routes/users.ts
import { Router, Request, Response } from 'express';
const router = Router();
router.get('/', (req: Request, res: Response) => {
res.json({ users: [] });
});
router.get('/:id', (req: Request, res: Response) => {
res.json({ id: req.params.id });
});
// パラメータ前処理(このRouter内の :id すべてに適用)
router.param('id', (req: Request, res: Response, next, id: string) => {
if (!/^\d+$/.test(id)) {
res.status(400).json({ error: 'ID must be a number' });
return;
}
next();
});
export default router;
// app.ts
import express from 'express';
import usersRouter from './routes/users';
const app = express();
app.use(express.json());
app.use('/api/users', usersRouter);
app.listen(3000);
4. エラーハンドリングミドルウェア
引数が4つのミドルウェアはエラーハンドラとして認識されます。
import express, { Request, Response, NextFunction } from 'express';
const app = express();
app.get('/error-example', (req: Request, res: Response, next: NextFunction) => {
// 同期エラーはv5では自動的にキャッチされる
throw new Error('Something went wrong');
});
app.get('/async-error', async (req: Request, res: Response) => {
// v5ではasyncハンドラのrejectも自動キャッチされる(v4では別途対応が必要だった)
const data = await fetchSomethingThatMightFail();
res.json(data);
});
// カスタムエラークラス
class AppError extends Error {
constructor(
public statusCode: number,
message: string,
) {
super(message);
this.name = 'AppError';
}
}
// エラーハンドリングミドルウェア(必ず引数4つ)
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
if (err instanceof AppError) {
res.status(err.statusCode).json({ error: err.message });
return;
}
res.status(500).json({ error: 'Internal Server Error' });
});
app.listen(3000);
// ダミー関数
async function fetchSomethingThatMightFail() {
return { ok: true };
}
v5の重要な変更: Express v5ではasyncハンドラ内でthrowされたエラーやPromiseのrejectが自動的にエラーハンドリングミドルウェアに渡されます。v4で必要だった
express-async-errorsやtry-catch + next(err)パターンは不要になりました。
5. req / res の主要プロパティ・メソッド
リクエスト・レスポンスオブジェクトでよく使うものをまとめます。
import express, { Request, Response } from 'express';
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get('/demo', (req: Request, res: Response) => {
// --- Request ---
console.log(req.query); // クエリパラメータ { page: '1', limit: '10' }
console.log(req.params); // パスパラメータ(この例では空)
console.log(req.body); // リクエストボディ(POST/PUTなど)
console.log(req.headers); // リクエストヘッダー
console.log(req.method); // 'GET'
console.log(req.path); // '/demo'
console.log(req.hostname); // 'localhost'
console.log(req.ip); // クライアントIP
console.log(req.get('Content-Type')); // 特定ヘッダー取得
// --- Response ---
res
.status(200) // ステータスコード設定
.set('X-Custom-Header', 'value') // レスポンスヘッダー設定
.json({ // JSONレスポンス送信
message: 'OK',
query: req.query,
});
// その他のレスポンスメソッド
// res.send('テキスト'); // テキスト/HTML送信
// res.sendFile('/path/to/file'); // ファイル送信
// res.redirect('/other'); // リダイレクト
// res.download('/path/to/file'); // ファイルダウンロード
// res.cookie('name', 'value'); // Cookie設定(cookie-parser不要でセット可能)
// res.clearCookie('name'); // Cookie削除
});
app.listen(3000);
類似パッケージとの比較
| 特徴 | Express | Fastify | Koa | Hono |
|---|---|---|---|---|
| 設計思想 | ミドルウェア中心・最小構成 | プラグイン中心・高速 | async/await中心・軽量 | Web Standards準拠・マルチランタイム |
| パフォーマンス | ○ | ◎ | ○ | ◎ |
| エコシステム | ◎(最大) | ○(成長中) | △(縮小傾向) | ○(急成長中) |
| TypeScriptサポート | △(型定義は別パッケージ) | ◎(組み込み) | ○ | ◎(組み込み) |
| 学習コスト | 低い | 中程度 | 低い | 低い |
| ミドルウェア互換性 | Express用が膨大 | 独自プラグイン体系 | Koa専用 | 独自(Express互換アダプタあり) |
| 対応ランタイム | Node.js | Node.js | Node.js | Node.js / Deno / Bun / Cloudflare Workers |
選定の目安:
- 既存資産・情報量を重視 → Express
- パフォーマンス最優先 → Fastify または Hono
- エッジ環境やマルチランタイム → Hono
- 新規プロジェクトでNode.js限定 → Fastify が有力候補
注意点・Tips
v4 → v5 の主な破壊的変更
Express v5はメジャーバージョンアップであり、以下の変更点に注意が必要です。
// ❌ v5で削除されたAPI
// app.del() → app.delete() を使用
// req.param('name') → req.params.name または req.query.name を使用
// res.json(obj, statusCode) → res.status(code).json(obj) を使用
// ✅ v5でのパスパターン変更
// v4: /users/:id? (オプショナルパラメータ)
// v5: /users{/:id} (新しい構文)
app.get('/users{/:id}', (req: Request, res: Response) => {
// req.params.id は undefined の可能性がある
res.json({ id: req.params.id ?? 'all' });
});
// ✅ v5ではサブアプリの req.ip がデフォルトで親アプリの設定を継承
セキュリティのベストプラクティス
import express from 'express';
import helmet from 'helmet'; // npm install helmet
const app = express();
// セキュリティヘッダーを一括設定
app.use(helmet());
// bodyサイズ制限(DoS対策)
app.use(express.json({ limit: '10kb' }));
// 本番環境ではExpressの情報を隠す
app.disable('x-powered-by'); // helmetを使えば自動で無効化される
// trust proxy設定(リバースプロキシ背後の場合)
app.set('trust proxy', 1); // ロードバランサー1段の場合
よくあるハマりポイント
// ❌ レスポンスを二重送信してしまう
app.get('/bad', (req: Request, res: Response) => {
if (!req.query.token) {
res.status(401).json({ error: 'Unauthorized' });
// return を忘れると下の行も実行される!
}
res.json({ data: 'secret' }); // Error: Cannot set headers after they are sent