『上級者向けExpress:エラー処理とセキュリティ・ミドルウェアの実践ガイド』
1. ボブの生産危機
ボブのAPIは、運用初日から問題に見舞われた。あるユーザーが文字化けしたメールアドレスを送信したため、データベースエラーが発生し、サイト全体がダウンしてしまった。また、誰かが500MBの画像をアップロードしたことで、ディスク容量が瞬く間に満杯になってしまった。さらに、競合他社が1秒間に1,000件のリクエストを送信するスクリプトを作成したため、サーバーが502エラーを返す事態となった。
たった1つの
app.use(errorHandler)で、生産事故の80%を防ぐことができます。
TEXT
Bob Mine Clearance Timeline:
Day 1 📧 Invalid email address → Database Error → 500 Error
Day 2 🖼️ 500MB Upload → Disk Full → Service Outage
Day 3 🤖 1000 req/s → CPU 100% → 502 Bad Gateway
Day 4 🔒 Add middleware → Take Them One by One → Stable service
2. エラー処理ミドルウェア
(1) 4パラメータ署名方式
Expressは、パラメータの数によってエラー用ミドルウェアを識別します。パラメータは4つ (err, req, res, next) である必要があります。1つでも欠けていると、通常のミドルウェアとなります。
▶ 例:グローバルエラー処理ミドルウェア
JAVASCRIPT
const express = require('express');
const app = express();
app.get('/boom', (req, res, next) => {
try {
throw new Error('Blown up on purpose');
} catch (err) {
next(err);
}
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
code: err.status || 500,
data: null,
message: err.message
});
});
app.listen(3000);
(2) カスタムビジネスエラークラス
▶ 例:ビジネスエラーとHTTPエラーの区別
JAVASCRIPT
class AppError extends Error {
constructor(message, status) {
super(message);
this.status = status;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
class NotFoundError extends AppError {
constructor(resource) {
super(`${resource} Not found`, 404);
}
}
class ValidationError extends AppError {
constructor(message) {
super(message, 400);
}
}
app.get('/users/:id', (req, res, next) => {
const user = findUser(req.params.id);
if (!user) return next(new NotFoundError('User'));
res.json({ code: 0, data: user, message: 'ok' });
});
(3) エラー処理手法の比較
| メソッド | パラメータ数 | スコープ | ユースケース | 非同期対応 |
|---|---|---|---|---|
app.use(errHandler) |
4 | すべてのグローバルエラー | 最終的なキャッチオール | 手動での処理が必要 next(err) |
try/catch + next(err) |
— | 単一ルート | 同期コード | 同期のみ |
express-async-errors |
0 | グローバル非同期エラー | async/await ルーティング | 自動 |
domain モジュール(非推奨) |
— | プロセスレベル | 推奨されません | — |
process.on('uncaughtException') |
— | プロセスレベル | 最終防衛線 | グローバル |
プロミス .catch() |
— | 単一のプロミス | 単一の非同期操作 | 単一 |
3. express-validator によるリクエストの検証
(1) 検証チェーンとミドルウェアの使い方
express-validator は validator.js をベースとしています。これは「バリデーションチェーン」を用いてルールを宣言的に定義し、バリデーションに失敗した際に自動的にエラーを収集します。
▶ 例:ユーザー登録の確認
JAVASCRIPT
const { body, validationResult } = require('express-validator');
app.post('/register',
body('username')
.isLength({ min: 3, max: 20 }).withMessage('The username must3-20Character')
.isAlphanumeric().withMessage('Usernames must consist of alphanumeric characters only.'),
body('email')
.isEmail().withMessage('Invalid email address')
.normalizeEmail(),
body('password')
.isLength({ min: 8 }).withMessage('Password must be at least 8 characters')
.matches(/\d/).withMessage('Passwords must contain numbers')
.matches(/[A-Z]/).withMessage('The password must contain uppercase letters.'),
body('age')
.optional()
.isInt({ min: 1, max: 150 }).withMessage('Age requirement1-150'),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
code: 400,
data: errors.array(),
message: 'Request verification failed'
});
}
next();
},
(req, res) => {
res.json({ code: 0, data: req.body, message: 'ok' });
}
);
(2) express-validator でよく使われるバリデータ
| バリデータ | 目的 | 例 | 関連する修飾子 |
|---|---|---|---|
isEmail() |
メール | body('email').isEmail() |
.normalizeEmail() |
isLength() |
長さ | body('name').isLength({min:2,max:50}) |
— |
isInt() / isFloat() |
番号 | query('page').isInt({min:1}) |
.toInt() |
isBoolean() |
ブール値 | body('active').isBoolean() |
.toBoolean() |
isDate() |
日付 | body('birthday').isDate() |
.toDate() |
isURL() |
URL | body('website').isURL() |
— |
isIn() |
列挙 | body('role').isIn(['admin','user']) |
— |
matches() |
正規表現 | body('code').matches(/^\d{6}$/) |
— |
isMongoId() |
ObjectId | param('id').isMongoId() |
— |
optional() |
オプション | body('nickname').optional().isLength({max:30}) |
{nullable:true} |
(3) express-validator と Joi の比較
| 比較基準 | express-validator | Joi |
|---|---|---|
| 統合方法 | Native Express ミドルウェア | スタンドアロンの検証ライブラリ。手動での統合が必要 |
| 構文スタイル | 連鎖呼び出し、フィールドごとの定義 | スキーマオブジェクト、一度きりの定義 |
| 基盤となる依存関係 | validator.js | カスタム実装 |
| エラーの収集 | validationResult() 自動収集 |
validate().error 手動処理 |
| ユースケース | Expressプロジェクトとの迅速な統合 | あらゆるNode.jsプロジェクト |
| 習得の難易度 | 低(validator.js に慣れている場合) | 中(独自のスキーマ構文) |
| 型変換 | .toInt() .toDate() など |
自動型変換 |
| コミュニティの規模 | 週間ダウンロード数 約150万 | 週間ダウンロード数 約500万 |
4. multer を使ったファイルのアップロード
(1) 3つのストレージ戦略
multerには、single、array、fieldsの3つのアップロードモードがあります。保存先としては、memoryStorage(メモリ)とdiskStorage(ディスク)が利用可能です。
▶ 例:ディスクストレージ + ファイルフィルタリング
JAVASCRIPT
const multer = require('multer');
const path = require('path');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
const ext = path.extname(file.originalname);
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1e9)}${ext}`;
cb(null, uniqueName);
}
});
const fileFilter = (req, file, cb) => {
const allowed = /\.(jpg|jpeg|png|gif|webp)$/i;
if (allowed.test(path.extname(file.originalname))) {
cb(null, true);
} else {
cb(new Error('Supports only jpg/png/gif/webp Format'), false);
}
};
const upload = multer({
storage,
fileFilter,
limits: { fileSize: 5 * 1024 * 1024 }
});
app.post('/avatar', upload.single('avatar'), (req, res) => {
if (!req.file) return res.status(400).json({ code: 400, data: null, message: 'Please upload the file' });
res.json({
code: 0,
data: { url: `/uploads/${req.file.filename}`, size: req.file.size },
message: 'ok'
});
});
app.post('/photos', upload.array('photos', 9), (req, res) => {
const urls = req.files.map(f => `/uploads/${f.filename}`);
res.json({ code: 0, data: urls, message: 'ok' });
});
(2) マルチ設定オプションの比較
| 構成項目 | タイプ | デフォルト値 | 説明 | 例 |
|---|---|---|---|---|
storage |
ストレージエンジン | memoryStorage |
ストレージエンジン | multer.diskStorage({...}) |
dest |
文字列 | — | 対象ディレクトリ(これか「storage」のいずれか) | 'uploads/' |
fileFilter |
機能 | すべて許可 | ファイルフィルタコールバック | (req,file,cb)=>{...} |
limits.fileSize |
数 | 制限なし | 1ファイルあたりの最大バイト数 | 5*1024*1024 |
limits.files |
数 | 無制限 | アップロード可能なファイルの最大数 | 9 |
limits.fields |
番号 | 無制限 | ファイル以外のフィールドの最大数 | 10 |
limits.fieldSize |
数値 | 1MB | ファイル以外のフィールドの最大バイト数 | 1024*100 |
limits.parts |
番号 | 無制限 | マルチパートのパーツ総数 | 20 |
(3) メモリストレージとディスクストレージ
| ディメンション | メモリストレージ | ディスクストレージ |
|---|---|---|
| 保存場所 | メモリ(バッファ) | ディスク上のファイル |
req.file 物件 |
buffer |
path, filename |
| ユースケース | 小さなファイル、リアルタイム処理(例:画像の圧縮・保存) | 大きなファイル、永続ストレージ |
| パフォーマンス | 高速(ディスクI/Oなし) | やや遅い(ディスクへの書き込みが必要) |
| メモリ関連のリスク | 大容量ファイルの処理時や高同時実行環境下でOOMが発生 | なし |
| 『Lost』を再開する | はい | いいえ |
| ファイル名の制御 | 不要 | カスタム filename コールバックが必要 |
5. 静的ファイルサービスの設定
(1) express.static の詳細
▶ 例:複数のディレクトリを持つ静的サービス + キャッシュ制御
JAVASCRIPT
const express = require('express');
const app = express();
app.use('/static', express.static('public', {
maxAge: '7d',
etag: true,
lastModified: true,
immutable: true,
setHeaders: (res, filePath) => {
if (filePath.endsWith('.html')) {
res.setHeader('Cache-Control', 'no-cache');
}
if (filePath.match(/\.(jpg|png|gif|webp|svg)$/)) {
res.setHeader('Cache-Control', 'public, max-age=2592000, immutable');
}
}
}));
app.use('/uploads', express.static('uploads', {
maxAge: '30d',
dotfiles: 'deny'
}));
(2) 静的サービスに関するセキュリティ上の考慮事項
dotfiles'deny'に設定すると、.envなどの機密ファイルの漏洩を防ぐことができます。.jsファイルが実行されないように、アップロード用ディレクトリとコード用ディレクトリを分けておく- 本番環境では、静的ファイルのホスティングにNginx/CDNを使用します。ExpressはAPI専用に使用されます。
- 帯域幅の使用量を削減するために、適切な
Cache-Control値を設定する
6. 回答の標準形式
(1) {コード、データ、メッセージ} の仕様
▶ 例:レスポンス・ラッパー・ミドルウェア
JAVASCRIPT
const responseHandler = (req, res, next) => {
res.success = (data = null, message = 'ok') => {
res.json({ code: 0, data, message });
};
res.fail = (message = 'Operation Failed', code = -1, data = null) => {
res.json({ code, data, message });
};
res.paginate = (list, total, page, pageSize) => {
res.json({
code: 0,
data: { list, total, page, pageSize, totalPages: Math.ceil(total / pageSize) },
message: 'ok'
});
};
next();
};
app.use(responseHandler);
app.get('/users', (req, res) => {
const users = getUserList();
res.success(users);
});
app.get('/users/:id', (req, res, next) => {
const user = findUser(req.params.id);
if (!user) return res.fail('User does not exist', 404);
res.success(user);
});
(2) 国際化の6つの鉄則
messageへの回答として、中国語をハードコーディングせず、"error.user_not_found"などの i18n キーを使用してください。- サーバーは、
Accept-Languageヘッダーまたは?lang=zhパラメータに基づいて言語を切り替えます - エラーコード
codeは言語に依存しません。フロントエンドはこのコードに基づいて、ローカライズされたテキストを取得します。 - 日付と時刻は常に ISO 8601 形式 (
2025-01-15T08:30:00Z) で返されます。フロントエンドがロケールに応じてフォーマットを行います。 - 数値や通貨はあらかじめ書式設定されていません。元の値に通貨コードが加算されたものが返され、フロントエンドではロケールに応じて表示されます。
- エラー配列内の
msgフィールドも i18n 処理を経由し、中国語のプロンプトが直接返されないことを確認する
7. レート制限(スロットリング)
(1) express-rate-limit の設定
▶ 例:段階的なレート制限戦略
JAVASCRIPT
const rateLimit = require('express-rate-limit');
const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: false,
message: { code: 429, data: null, message: 'Too many requests,Please try again later.' }
});
const apiLimiter = rateLimit({
windowMs: 60 * 1000,
max: 30,
message: { code: 429, data: null, message: 'API Exceeded the call limit' }
});
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
skipSuccessfulRequests: true,
message: { code: 429, data: null, message: 'Too many failed login attempts, please try again in 15 minutes' }
});
app.use(globalLimiter);
app.use('/api/', apiLimiter);
app.use('/auth/login', loginLimiter);
(2) セキュリティミドルウェアの一覧
| ミドルウェア | 目的 | デフォルトの動作 | 主な設定 | npm パッケージ |
|---|---|---|---|---|
helmet |
HTTPセキュリティヘッダー | 15個のセキュリティヘッダーを設定 | contentSecurityPolicy, hsts |
helmet |
express-rate-limit |
レート制限 | — | windowMs, max |
express-rate-limit |
cors |
ドメイン間制御 | すべてのドメイン間リクエストを拒否 | origin, methods, credentials |
cors |
express-validator |
入力検証 | — | body(), query(), param() |
express-validator |
multer |
ファイルのアップロード | — | limits, fileFilter |
multer |
express-mongo-sanitize |
NoSQLインジェクション | $ および . を削除 |
— | express-mongo-sanitize |
xss-clean |
XSS クリーンアップ | HTML エスケープ | — | xss-clean |
hpp |
パラメータ汚染 | 最後の値を採用 | whitelist |
hpp |
compression gzip圧縮 — threshold、levelの圧縮 |
8. 環境設定:dotenv
(1) dotenv の基本的な使い方
▶ 例:環境変数の階層的な読み込み
JAVASCRIPT
const dotenv = require('dotenv');
const path = require('path');
dotenv.config({ path: path.resolve(process.env.NODE_ENV ? `.env.${process.env.NODE_ENV}` : '.env') });
const config = {
port: parseInt(process.env.PORT, 10) || 3000,
env: process.env.NODE_ENV || 'development',
db: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT, 10) || 27017,
name: process.env.DB_NAME || 'myapp_dev'
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '7d'
},
upload: {
maxFileSize: parseInt(process.env.MAX_FILE_SIZE, 10) || 5 * 1024 * 1024,
allowedTypes: (process.env.ALLOWED_TYPES || 'jpg,jpeg,png,gif,webp').split(',')
},
rateLimit: {
windowMs: parseInt(process.env.RATE_WINDOW_MS, 10) || 15 * 60 * 1000,
max: parseInt(process.env.RATE_MAX, 10) || 100
}
};
module.exports = config;
(2) .env ファイルのベストプラクティス
TEXT
# .env ← Default(Development),Do not submit to Git
# .env.production ← Production Environment,Strictly Restrict Access
# .env.test ← Test Environment
PORT=3000
NODE_ENV=development
DB_HOST=localhost
DB_PORT=27017
DB_NAME=myapp_dev
JWT_SECRET=your-super-secret-key-change-in-production
JWT_EXPIRES_IN=7d
MAX_FILE_SIZE=5242880
ALLOWED_TYPES=jpg,jpeg,png,gif,webp
RATE_WINDOW_MS=900000
RATE_MAX=100
TEXT
# .gitignore Must include
.env
.env.*
!.env.example
9. エクスプレスリクエストの処理プロセス全体
(1) マーメイドのフローチャート
flowchart TD
A[Client Request] --> B[helmet Safety Head]
B --> C[cors Cross-domain validation]
C --> D[rate-limit Traffic Flow Inspection]
D -->|429| E[Return Rate-Limiting Response]
D -->|Through| F[express.json Analysis Body]
F --> G[multer File Upload Processing]
G -->|The file is too large/Format error| H[next error]
G -->|Through| I[express-validator Verification]
I -->|Verification Failed| J[Back 400 Validation Error]
I -->|Through| K[Business Routing Processing]
K -->|Business Error| L[next error]
K -->|Success| M[Unified Response Encapsulation success/fail]
M --> N[Back JSON Response]
L --> O[Global Error Handling Middleware]
H --> O
O --> P[Standardized Error Responses code/data/message]
P --> N
style E fill:#f66,stroke:#333,color:#fff
style J fill:#f66,stroke:#333,color:#fff
style P fill:#f66,stroke:#333,color:#fff
style N fill:#6f6,stroke:#333
(2) ミドルウェアのロード順序に関する原則
- セキュリティ対策(ヘルメット、コルセット、レート制限)を一番最初に配置する
- 次に、リクエスト解析クラス(json、urlencoded、cookie)が登場します
- 解析後のファイル処理(multer)
- ビジネスロジックの前に配置するバリデーションクラス(express-validator)
- ビジネスルーティングは一元管理されている
- ビジネスルートの前にレスポンスラッパーを登録する
- エラー処理は常に最後に記述すること
10. 包括的な例:Express API のセキュリティ設定の完了
▶ 例:本番環境向けのExpressサーバー
JAVASCRIPT
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
const multer = require('multer');
const { body, param, validationResult } = require('express-validator');
const compression = require('compression');
const path = require('path');
const config = require('./config');
const app = express();
app.use(helmet());
app.use(cors({ origin: config.corsOrigin, credentials: true }));
app.use(compression({ threshold: 1024 }));
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true }));
const globalLimiter = rateLimit({
windowMs: config.rateLimit.windowMs,
max: config.rateLimit.max,
standardHeaders: true,
legacyHeaders: false,
message: { code: 429, data: null, message: 'error.rate_limited' }
});
app.use(globalLimiter);
const upload = multer({
storage: multer.diskStorage({
destination: 'uploads/',
filename: (req, file, cb) => {
const ext = path.extname(file.originalname);
cb(null, `${Date.now()}-${Math.random().toString(36).slice(2)}${ext}`);
}
}),
fileFilter: (req, file, cb) => {
const ext = path.extname(file.originalname).toLowerCase();
if (config.upload.allowedTypes.some(t => `.${t}` === ext)) {
cb(null, true);
} else {
cb(new Error('error.invalid_file_type'), false);
}
},
limits: { fileSize: config.upload.maxFileSize, files: 5 }
});
const responseHandler = (req, res, next) => {
res.success = (data = null, message = 'ok') => {
res.json({ code: 0, data, message });
};
res.fail = (message = 'error.internal', code = -1, data = null) => {
res.json({ code, data, message });
};
next();
};
app.use(responseHandler);
const validate = (rules) => [
...rules,
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
code: 400,
data: errors.array().map(e => ({ field: e.path, message: e.msg })),
message: 'error.validation_failed'
});
}
next();
}
];
app.post('/api/users',
validate([
body('username').isLength({ min: 3, max: 20 }).withMessage('error.username_length'),
body('email').isEmail().withMessage('error.invalid_email').normalizeEmail(),
body('password').isLength({ min: 8 }).withMessage('error.password_length')
]),
(req, res) => {
const user = createUser(req.body);
res.success(user, 'ok');
}
);
app.post('/api/upload',
upload.array('files', 5),
(req, res) => {
if (!req.files || req.files.length === 0) {
return res.fail('error.no_file_uploaded', 400);
}
const urls = req.files.map(f => `/uploads/${f.filename}`);
res.success(urls, 'ok');
}
);
app.get('/api/users/:id',
validate([param('id').isMongoId().withMessage('error.invalid_id')]),
(req, res) => {
const user = findUser(req.params.id);
if (!user) return res.fail('error.user_not_found', 404);
res.success(user);
}
);
app.use('/uploads', express.static('uploads', { maxAge: '30d', dotfiles: 'deny' }));
app.use((req, res) => {
res.status(404).json({ code: 404, data: null, message: 'error.not_found' });
});
class AppError extends Error {
constructor(message, status) {
super(message);
this.status = status;
this.isOperational = true;
}
}
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(413).json({ code: 413, data: null, message: 'error.file_too_large' });
}
return res.status(400).json({ code: 400, data: null, message: 'error.upload_failed' });
}
const status = err.status || 500;
const message = err.isOperational ? err.message : 'error.internal';
if (config.env === 'development') console.error(err.stack);
res.status(status).json({ code: status, data: null, message });
});
app.listen(config.port, () => {
console.log(`Server running on port ${config.port} [${config.env}]`);
});
11. このレッスンのまとめ
- エラーミドルウェアの4パラメータのシグネチャ
(err, req, res, next)が、Expressによる認識の鍵となる - express-validator は バリデーションチェーン を使用してルールを定義し、
validationResult()がエラーを収集します - multerの
limits+fileFilterは、ディスクが満杯になるのを防ぐための二重の安全策となります - 統一された対応
{code, data, message}一貫性があり、予測可能なフロントエンド処理を確保する - レート制限:段階的なレート制限:グローバル(緩やか)、ログイン済み(厳格)、API(中程度)
- 階層的な読み込みには dotenv を使用する
.env.{NODE_ENV};.envファイルは Git にコミットしない - ミドルウェアの読み込み順序:セキュリティ → 解析 → ファイル → 検証 → ビジネスロジック → エラー処理
❓ よくある質問
Q ミドルウェアとルーティングのどちらが先に実行されますか?
A 登録された順序で実行されます。
app.use に登録されたグローバルミドルウェアは、ルーティングミドルウェアよりも先に実行され、ルーティング内のミドルウェアは、ルーティングで定義された順序で実行されます。Q APIのバージョン管理はどのように実装しますか?
A 一般的な方法として、URLパス(/v1/users)、リクエストヘッダー(Accept: application/vnd.api.v1+json)、クエリパラメータ(?version=1)の3つがあります。
Q エラー処理用ミドルウェアには4つのパラメータが必要ですか?
A はい。Expressはパラメータの数によってエラー処理用ミドルウェアを識別します。パラメータのシグネチャは (err, req, res, next) でなければならず、そうでない場合は通常のミドルウェアとして扱われます。
Q リクエストのレート制限をどのように実装すればよいですか?
A
express-rate-limit ミドルウェアを使用し、windowMs の時間枠と max のリクエスト数を設定し、制限を超えた場合は 429 ステータスコードを返します。Q 静的ファイルの配信パフォーマンスを最適化するにはどうすればよいですか?
A 本番環境では、静的ファイルのホスティングにNginxまたはCDNを使用し、Expressには動的なAPIのみを処理させます。開発環境では、
express.staticが提供するmaxAgeキャッシュを使用できます。- Q: Why must error middleware have exactly 4 parameters? A: Express uses
fn.lengthto check the number of function parameters; only if there are 4 parameters is it recognized as error-handling middleware. Otherwise, it is treated as regular middleware and will not receive theerrobject. - Q: express-validator と Joi の主な違いは何ですか? A: express-validator は、Express ミドルウェア形式のライブラリであり、フィールドごとの検証をチェーン形式で実行でき、ルートと緊密に統合されています。一方、Joi はスタンドアロンのスキーマ検証ライブラリであり、完全なデータ構造を定義しますが、
validate()を手動で呼び出し、その結果を処理する必要があります。 - Q: memoryStorage と diskStorage のどちらを選べばよいですか? A: すぐに処理が必要な小さなファイル(1MB未満、例えばサムネイルを生成してクラウドストレージに保存する場合など)には memoryStorage を使用し、大きなファイルや、メモリオーバーフローを防ぐために永続化する必要があるファイルには diskStorage を使用してください。
- Q: アップロードされるファイルのサイズを制限するにはどうすればよいですか? A: 3段階の保護策があります。multer
limits.fileSizeがアプリケーション層で、express.json({limit:'10kb'})がリクエスト本文で、Nginxclient_max_body_sizeがゲートウェイ層でそれぞれ処理を遮断します。 - Q: express-async-errors とは何ですか? A: 非同期ルートで処理されなかった Promise の拒否を自動的にキャッチし、エラーミドルウェアに転送するために、たった
require('express-async-errors')1 行のコードだけで済むパッケージです。これにより、ルートごとに try/catch ブロックを記述する必要がなくなります。 - Q: 統一レスポンスにおいて、「コード」は成功を示すために0を使用すべきか、それともHTTPステータスコードを使用すべきか? A: ビジネスコードでは、成功を示すために
0を使用することを推奨します(HTTPステータスコードとは切り離して)。一方、HTTPステータスコードについては、REST仕様に従って(200/400/404/500)引き続き返す必要があります。ビジネスエラーは、負の数または特定の正の数を使用してエンコードする必要があります。
📖 まとめ
- 1 ボブの生産危機に関する主要な概念と応用
- 2 エラー処理ミドルウェアの基本概念と使用方法
- リクエストの検証における「express-validator」の3つの基本概念と使い方
- ファイルアップロードにおけるmulterの4つの基本概念と使い方
- 静的ファイルサービス設定の5つの基本概念と使用方法
- 統一応答フォーマットの6つの基本概念と使用方法
- レート制限の7つの基本概念と活用方法
- 環境設定のための dotenv の 8 つの基本概念と使い方
📝 練習問題
- 既存のExpressプロジェクトにグローバルなエラー処理ミドルウェアを追加し、ビジネスエラー(
isOperational)と未知のエラーを区別するようにする。未知のエラーについては、スタックトレースを公開せずに汎用的なメッセージを返す。 express-validatorを使用して、ユーザー登録 API の完全な検証チェーン(ユーザー名、メールアドレス、パスワードの強度、パスワードの確認)を作成し、検証に失敗した場合はフィールドレベルのエラーの配列を返すようにしてください。- プロフィール画像のアップロードを有効にするよう、multerを設定する(1ファイルのみ、2MBまで、JPGまたはPNGのみ)。アップロードに成功した場合はファイルのURLを返し、失敗した場合は具体的な失敗理由を返す。
- 段階的なレート制限を行うため、express-rate-limit を追加します。全体で15分ごとに100リクエスト、1分あたり30回のAPIリクエスト、および15分ごとに5回のログイン失敗を制限します。
- すべての環境変数とそのデフォルト値を一覧にした「
.env.example」という名前のファイルを作成し、「.env」が「.gitignore」に追加されていることを確認してください。



