静的ファイルの配信とエラー処理
このトピックを終えると
Expressで、HTML、CSS、画像などの静的ファイルを配信し、404(ページが見つかりません)と500(サーバーエラー)のエラーを構造的に処理できるようになります。
静的ファイルとは
ウェブサーバーが処理するファイルには、2種類あります。
- 動的ファイル: リクエストごとにサーバーがコードを実行して生成するレスポンス(API、データベースクエリの結果)
- 静的ファイル: サーバーがそのまま転送するだけファイル(HTML、CSS、JavaScript、画像、フォント)
ブログの記事一覧は動的ですが、ブログのロゴ画像とスタイルシートは静的です。静的ファイルは、リクエストが来るたびに新しく作成する必要がないため、別のミドルウェアが効率的に処理します。
express.static — 静的ファイルミドルウェア
Expressは、express.static()ミドルウェアを内蔵しています。
project/
├── app.js
├── public/
│ ├── index.html
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── main.js
│ └── images/
│ └── logo.pngconst express = require('express');
const path = require('path');
const app = express();
// publicフォルダを静的ファイルのルートとして設定
app.use(express.static(path.join(__dirname, 'public')));
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});これで、ブラウザでhttp://localhost:3000/css/style.cssにアクセスすると、public/css/style.cssファイルが返されます。URLに/publicを付けなくても、express.staticがpublicフォルダをルートにマッピングするためです。
URLプレフィックスの追加
// /assets/css/style.cssとしてアクセス
app.use('/assets', express.static(path.join(__dirname, 'public')));最初の引数がURLプレフィックスです。このようにすると、実際のファイルパスはpublic/css/style.cssですが、URLは/assets/css/style.cssになります。CDNやバージョン管理のためにパスを分離する場合に便利です。
複数のフォルダの指定
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'uploads')));2つのフォルダを静的ファイルのソースとして登録できます。最初のフォルダで見つからない場合、2番目のフォルダを検索します。順序が優先順位になります。
path.joinと__dirname — パスを安全に作成する
// Bad: 相対パス — 実行場所によって異なる
app.use(express.static('public'));
// Good: 絶対パス — どこで実行しても同じ
app.use(express.static(path.join(__dirname, 'public')));__dirnameは、現在のファイルが存在するディレクトリの絶対パスです。path.join()は、OSに合ったパス区切り文字(/または\)を自動的に処理します。Windowsで/を直接記述すると問題が発生する可能性があるため、常にpath.joinを使用します。
404処理 — ページが見つからない
Expressで、どのルートにも一致しない場合、リクエストは単に破棄されます。クライアントは応答を永遠に待ち続けます。これを防ぐには、すべてのルートの後に404ハンドラーを配置する必要があります。
// ルートの定義
app.get('/', (req, res) => {
res.send('Home');
});
app.get('/about', (req, res) => {
res.send('About');
});
// 404ハンドラー — すべてのルートの後に、エラーハンドラーの前に
app.use((req, res) => {
res.status(404).json({
error: 'Not Found',
message: `${req.method} ${req.url} does not exist`,
status: 404
});
});位置が重要です。app.use()は順序どおりに実行されるため、404ハンドラーを最初に配置すると、すべてのリクエストが404になります。必ずすべてのルート定義の後に配置する必要があります。
HTML 404ページ
APIサーバーではなく、ウェブサイトの場合、JSONではなくHTMLページを表示する方が自然です。
app.use((req, res) => {
res.status(404).sendFile(path.join(__dirname, 'public', '404.html'));
});500処理 — サーバー内部エラー
エラー処理ミドルウェアは、パラメータが4つです。Expressは、このシグネチャで通常のミドルウェアとエラーハンドラーを区別します。
// エラーハンドラー — 404ハンドラーの後に
app.use((err, req, res, next) => {
console.error(`[ERROR] ${err.stack}`);
res.status(err.status || 500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'production'
? 'Something went wrong'
: err.message,
status: err.status || 500
});
});注意すべき点2つ:
-
本番環境では、エラーメッセージを非表示にします。
err.messageにSQLクエリやファイルパスなどの機密情報が含まれている可能性があるためです。開発環境でのみ詳細なメッセージを表示します。 -
エラーをスローする方法: ルート内で
next(err)を呼び出すと、エラーハンドラーにジャンプします。
app.get('/users/:id', async (req, res, next) => {
try {
const user = await findUser(req.params.id);
if (!user) {
const err = new Error('User not found');
err.status = 404;
return next(err);
}
res.json(user);
} catch (err) {
next(err); // DBエラーなど、予期しないエラー
}
});全体の構造 — 正しい順序
const express = require('express');
const path = require('path');
const app = express();
// 1. 基本ミドルウェア
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
// 2. ルート
app.get('/api/users', (req, res) => { /* ... */ });
app.post('/api/users', (req, res) => { /* ... */ });
// 3. 404ハンドラー(ルートの後)
app.use((req, res) => {
res.status(404).json({ error: 'Not Found' });
});
// 4. エラーハンドラー(常に最後)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal Server Error' });
});
app.listen(3000);この順序は、Expressプロジェクトの標準的な構造です。基本ミドルウェア → ルート → 404 → エラー。この順序を破ると、予期しない動作が発生します。
実際に起こりがちな間違い
| 間違い | 症状 | 解決 |
|---|---|---|
| 404ハンドラーがルートより前 | すべてのリクエストが404 | 404を一番後ろに移動 |
| エラーハンドラーのパラメータが3つ | エラーがキャッチされない | (err, req, res, next) 4つ必須 |
express.staticのパスが相対 | 別のフォルダで実行するとファイルが見つからない | path.join(__dirname, ...) |
エラーでnext(err)を呼び出さない | エラーハンドラーに到達しない | try/catch + next(err) |
本番環境でerr.stackを公開 | セキュリティ上の脆弱性 | NODE_ENVで分岐 |
重要なまとめ
静的ファイルとエラー処理は、「サーバーが正常なとき」と「異常なとき」の両方を処理することです。express.staticはファイルを効率的に配信し、404/500ハンドラーは、例外的な状況でもユーザーに意味のある応答を提供します。Expressのミドルウェアの順序(パース → 静的 → ルート → 404 → エラー)を覚えておくと、何をどこに配置すればよいかわからなくなることはありません。