「エクスプレス・ベーシックス」
1. ネイティブHTTPからExpressへ
アリスは、ネイティブのHTTPモジュールを使ってAPIを記述するのに3日間を費やしましたが、コードは長くなり、メンテナンスが困難でした。URLを手作業で解析し、ルートマッチを手作業で記述し、リクエストボディを1行ずつ処理しなければならなかったからです。Expressに切り替えたところ、明確に定義されたルートと自動的にチェーンされるミドルウェアにより、同じ機能をわずか30行のコードで実装できるようになり、メンテナンス効率が10倍に向上しました。
ネイティブHTTPとExpressのコードの比較
| 比較 | ネイティブHTTP | Express |
|---|---|---|
| ルートの定義 | 手動 if/else マッチ req.url |
宣言型 app.get('/path', fn) |
| リクエストボディの解析 | data/end イベントを手動で監視し、バッファを構築 |
express.json() 1行で完了 |
| 応答送信済み | res.writeHead() + res.end() |
res.json() / res.send() |
| 静的ファイル | ファイルを手動で読み込み、Content-Type を設定 | express.static('public') |
| ミドルウェア | 組み込みサポートなし | app.use() パイプライン形式の構成 |
| コード行数(同等の機能の場合) | 約80行 | 約30行 |
- ExpressはNode.jsで最も人気のあるWebフレームワークであり、HTTPモジュールの内部的な詳細を抽象化しています。
- ルーティングに対する宣言型のアプローチにより、URLとハンドラの対応関係が一目瞭然になる
- このミドルウェアの仕組みは、関心の分離を実現しています。ロギング、認証、および解析が、それぞれ独自の機能を果たしています。
- 最も一般的な 2 つの API 開発ニーズに対応するため、
json()およびstatic()が含まれています - 広大なエコシステムにより、導入後すぐに利用できる多種多様なサードパーティ製ミドルウェアが提供されています
2. インストールと「Hello World」
(1) プロジェクトの初期化とExpressのインストール
mkdir my-express-app && cd my-express-app
npm init -y
npm install express
(2) 最小限の「Hello World」
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server running at http://localhost:3000');
});
node app.js
お使いのブラウザで http://localhost:3000 にアクセスするだけで、Hello World! を表示できます。
▶ 例:JSONレスポンスを返す
app.get('/api/hello', (req, res) => {
res.json({ message: 'Hello from Express!', status: 'ok' });
});
▶ 例:ルーティングパラメータ付きの「Hello」
app.get('/hello/:name', (req, res) => {
res.send(`Hello, ${req.params.name}!`);
});
3. ルーティング方式
エクスプレスルーティング方式のクイックリファレンス
| 手法 | 目的 | 冪等性 | 代表的なシナリオ |
|---|---|---|---|
app.get() |
リソースを取得 | はい | リスト/詳細を表示 |
app.post() |
リソースを作成 | いいえ | フォームを送信/レコードを追加 |
app.put() |
完全更新 | はい | レコード全体を置き換える |
app.delete() |
リソースを削除 | はい | レコードを削除 |
app.patch() |
部分更新 | いいえ | 個々のフィールドを編集 |
app.all() |
すべてのメソッドに一致 | — | 一般的な前処理 |
(1) RESTfulスタイルのルーティング
app.get('/users', (req, res) => { res.json({ users: [] }); });
app.post('/users', (req, res) => { res.status(201).json({ created: true }); });
app.put('/users/:id', (req, res) => { res.json({ updated: req.params.id }); });
app.delete('/users/:id', (req, res) => { res.json({ deleted: req.params.id }); });
(2) ルートマッチング
- 完全一致:
'/about'は/aboutのみに一致します - パラメータのマッピング:
'/users/:id'は/users/42にマッピングされ、その値はreq.params.idによって決定される - ワイルドカードによる一致:
'/files/*'は/files/a/b/cと一致する
▶ 例:クエリパラメータの読み取り
app.get('/search', (req, res) => {
const { q, page = '1' } = req.query;
res.json({ keyword: q, page: Number(page) });
});
▶ 例:複数のルートパラメータ
app.get('/posts/:postId/comments/:commentId', (req, res) => {
res.json(req.params);
});
4. ミドルウェアの概念
Express の設計の中核をなすのは、ミドルウェア・パイプラインです。各リクエストは一連の関数を順次通過し、各関数はリクエストやレスポンスを読み取ったり変更したり、あるいはリクエストを途中で打ち切ったりすることができます。
flowchart LR A[Request Request] --> B[Middleware1<br/>Logging] B --> C[Middleware2<br/>JSONAnalysis] C --> D[Middleware3<br/>Authentication and Validation] D --> E[Route Handling<br/>Business Logic] E --> F[Response Response]
(1) app.use() を使用してミドルウェアを登録する
const logger = (req, res, next) => {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next();
};
app.use(logger);
(2) ミドルウェアの実行順序
app.use()登録順に実行する。位置によってロジックが決まるnext()を呼び出すと、制御が次のミドルウェアに渡されるnext()が呼び出されない場合、リクエストは保留状態となります。その場合は、ご自身でレスポンスを送信する必要があります。
▶ 例:認証ミドルウェア
const auth = (req, res, next) => {
const token = req.headers['authorization'];
if (!token) return res.status(401).json({ error: 'No token' });
next();
};
app.use('/api', auth);
▶ 例:エラー処理ミドルウェア
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
5. 組み込みミドルウェア
組み込みミドルウェアの一覧
| ミドルウェア | 目的 | 実装 | 一般的な構成 |
|---|---|---|---|
express.json() |
JSONリクエスト本文の解析 | app.use(express.json()) |
{ limit: '10kb' } |
express.urlencoded() |
URLエンコードされたリクエスト本文の解析 | app.use(express.urlencoded({ extended: true })) |
{ extended: true/false } |
express.static() |
静的ファイルのホスティング | app.use(express.static('public')) |
{ maxAge: '1d' } |
(1) express.json() を使用したリクエスト本文の解析
app.use(express.json());
app.post('/api/users', (req, res) => {
console.log(req.body);
res.json({ received: req.body });
});
(2) 静的ファイルの配信には express.static() を使用する
想定されるプロジェクト構成:
my-express-app/
├── public/
│ ├── index.html
│ └── style.css
├── app.js
└── package.json
app.use(express.static('public'));
静的ファイルを直接読み込むには、http://localhost:3000/index.html にアクセスしてください。
▶ 例:複数のディレクトリを持つ静的ホスティング
app.use(express.static('public'));
app.use('/uploads', express.static('uploads'));
▶ 例:JSONリクエスト本文のサイズ制限
app.use(express.json({ limit: '100kb' }));
6. nodemon ホットリロード
nodemon と node の比較
| 比較項目 | node |
nodemon |
|---|---|---|
| ファイルの変更後 | 手動再起動 | 自動再起動 |
| 設置方法 | 組み込み型 | npm i -D nodemon |
| 起動コマンド | node app.js |
npx nodemon app.js |
| 本番環境 | 適用 | 非適用 |
| ディレクトリの指定 | なし | デフォルト:現在のディレクトリ;設定可能 |
(1) インストールと使用方法
npm install --save-dev nodemon
npx nodemon app.js
(2) package.json でのスクリプトの設定
{
"scripts": {
"dev": "nodemon app.js",
"start": "node app.js"
}
}
開発時には npm run dev(自動リロード)を、本番環境では npm start(安定稼働)を使用してください。
▶ 例:カスタム監視ディレクトリ
npx nodemon --watch src --ext js,ets app.js
7. 総合的な例:Express API アプリケーション
これまでに学んだ概念をすべて結びつけ、完全な小規模なAPIアプリケーションをゼロから構築してみましょう。
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.static('public'));
let todos = [
{ id: 1, task: 'Learn Express', done: false },
{ id: 2, task: 'Build an API', done: false },
];
app.get('/api/todos', (req, res) => res.json(todos));
app.post('/api/todos', (req, res) => {
const todo = { id: todos.length + 1, ...req.body, done: false };
todos.push(todo);
res.status(201).json(todo);
});
app.put('/api/todos/:id', (req, res) => {
const idx = todos.findIndex(t => t.id === Number(req.params.id));
if (idx === -1) return res.status(404).json({ error: 'Not found' });
todos[idx] = { ...todos[idx], ...req.body };
res.json(todos[idx]);
});
app.delete('/api/todos/:id', (req, res) => {
todos = todos.filter(t => t.id !== Number(req.params.id));
res.json({ deleted: true });
});
app.listen(3000, () => console.log('API running at http://localhost:3000'));
動作を開始したら、ブラウザから静的ページにアクセスしたり、APIツールを使用して/api/todosに対してCRUD操作を実行したりすることができます。
- Expressをインストールした後、
express.json()とexpress.static()を使用して、リクエスト本文の解析と静的ファイルのホスティングを1行で処理します - RESTful ルーティング
GET/POST/PUT/DELETECRUD サイクル全体を網羅 - 開発中、
nodemonと連携して行われたコードの変更は自動的に反映されるため、手動での再起動は必要ありません。
❓ よくある質問
app.use と app.get の違いは何ですか?app.use はすべてのリクエストメソッドに一致し、一般的にミドルウェアに使用されます。一方、app.get は GET リクエストにのみ一致し、ルートの定義に使用されます。app.use または app.get を通じて登録された順序で実行されます。next() を呼び出すと次のミドルウェアに進み、next() が呼び出されない場合、実行は終了します。Q: Expressだけが選択肢なのでしょうか? A: いいえ。Koaは軽量で、Fastifyは高速、Honoはエッジコンピューティングに対応していますが、Expressはエコシステムが最も成熟しており、学習リソースも最も充実しているため、初心者にとっては最適な選択肢と言えます。
Q: express.json() はルートの前に呼び出す必要がありますか? A: はい、app.use(express.json()) はルートの前に記述する必要があります。そうしないと、ミドルウェアは登録された順序で実行されるため、req.body が undefined になってしまいます。
Q: 4.xと5.xの違いは何ですか? A: Express 5.xでは、非推奨となったAPI(app.delなど)が削除され、ルートマッチングが改善され(path-to-regexp v8に対応)、一部の動作が非同期処理に適した形に改良されていますが、基本的な使い方はほぼ同じです。
Q: サービスを自動的に再起動するにはどうすればよいですか? A: nodemon (npm i -D nodemon) をインストールし、npx nodemon app.js で起動すると、ファイルの保存後に自動的に再起動されます。これは開発中に非常に便利です。
Q: app.use と app.get の違いは何ですか? A: app.use はすべての HTTP メソッドに一致し、パスのプレフィックスにも一致します(/api は /api/anything に一致します)。一方、app.get は GET メソッドにのみ一致し、パスが完全に一致する必要があります。前者はミドルウェア用、後者はルーティング用として使用されます。
Q: express.urlencoded の extended オプションとはどういう意味ですか? A: extended: trueは解析にqsライブラリを使用します(ネストされたオブジェクトに対応)。extended: falseはquerystringライブラリを使用します(ネストされたオブジェクトには対応していません)。また、trueはフォーム送信には通常、十分です。
📖 まとめ
- 1 Expressの基本概念と使い方:ネイティブHTTPからExpressまで
- 2 「Hello World」のインストール、基本概念、および使い方
- ルーティング手法の3つの基本概念と活用方法
- ミドルウェアの4つの基本概念と活用方法
- 組み込みミドルウェアの5つの基本概念と使用方法
- Nodemon Hot Reload の 6 つの基本概念と使い方
- 7 包括的な例:Express API アプリケーションの基本概念と使用方法
📝 練習問題
- このレッスンにあるすべてのコード例を完成させ、それぞれが正しく動作することを確認してください。
- 包括的な例を修正し、独自の拡張機能を追加する
- 公式ドキュメントを確認し、このレッスンで取り上げられていないAPIを1~2つ見つけ、それらのテストコードを作成してください。
- 振り返り:このレッスンで学んだことを、実際のプロジェクトにどのように活かしますか?
- このレッスンで学んだことと、これまでのレッスンの内容を組み合わせて、小さなプロジェクトを作成してみてください。



