このレッスンのロックを解除するには、まず「レッスン4: リクエストデータの処理とCRUDエンドポイントの構築」を完了してください。前のレッスンに戻る

はじめに

すべてのAPIは、自分では作成していないデータを受信します。ユーザーは不正なJSONを送信し、フィールドが欠落し、数値であるべき場所に文字列を送り、技術的には有効だが論理的に誤ったペイロードを送信します。検証層がなければ、そうした問題は静かにデータベースを破損させたり、意味不明な500エラーを引き起こしたりします。一貫したエラーハンドリング層がなければ、すべてのルートが異なる動作をします —プレーンな文字列を返すもの、HTMLエラーページを返すもの、何も返さないものもあります。

このレッスンでは両方の層を構築します:express-validatorライブラリを使用したリクエスト検証と、すべてのエラーレスポンスに同じ予測可能な形式を与える一元的なエラーハンドラーです。

受信リクエストデータの検証

express-validatorは、よくテストされたvalidator.jsライブラリをラップし、Expressミドルウェアに直接接続します。プロジェクトと一緒にインストールしてください:

bash
npm install express-validator

検証ルールはミドルウェアの配列として定義され、ハンドラーが実行される前にルートにアタッチされます。以下は、有効なメールアドレスと8文字以上のパスワードを必須とするPOST /usersルートです:

javascript
const { body, validationResult } = require('express-validator');
const createUserRules = [
body('email').isEmail().withMessage('Must be a valid email address'),
body('password')
.isLength({ min: 8 })
.withMessage('Password must be at least 8 characters'),
];
router.post('/users', createUserRules, (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
// safe to use req.body here
res.status(201).json({ message: 'User created' });
});

validationResult(req)を呼び出すことで、ルールチェーンが見つけたすべての違反が収集されます。配列が空でない場合、リクエストはHTTP 422 Unprocessable Entityで即座に拒否されます — これはサーバーがリクエストを理解したが、データが無効であるため処理できないという、意味的に正しいステータスです。

リクエストフローを示すフローチャート:受信POSTリクエスト → 検証ミドルウェアチェーン → validationResultチェック → エラーがあれば422レスポンスを返し、エラーがなければルートハンドラーに進む

ルート数が増えるにつれ、検証ルールを別のファイルに抽出して、ルートファイルをビジネスロジックのみに集中させることができます。

一元的なエラーハンドラーの構築

Expressにはエラーハンドリング専用のミドルウェアシグネチャがあります:3つではなく4つのパラメータです。next(err)を呼び出すか、asyncラッパー内でthrowするすべてのミドルウェアまたはルートは、通常のミドルウェアをスキップしてここに到達します。

専用ファイルを作成します:

javascript
// middleware/errorHandler.js
function errorHandler(err, req, res, next) {
const statusCode = err.statusCode || 500;
const message = err.message || 'Internal Server Error';
res.status(statusCode).json({
error: {
message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
},
});
}
module.exports = errorHandler;

メインアプリファイルで、すべてのルートの後に登録してください。順序が重要です — Expressはこのハンドラーが最後に定義されている場合にのみ呼び出します:

javascript
const errorHandler = require('./middleware/errorHandler');
app.use('/api', router);
app.use(errorHandler); // must come after all routes

これで、すべてのルートがErrorオブジェクトにstatusCodeをアタッチしてnextに渡すことで問題を通知できます:

javascript
router.get('/users/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
const err = new Error('User not found');
err.statusCode = 404;
return next(err);
}
res.json(user);
} catch (error) {
next(error); // unexpected errors get 500
}
});

予期しないエラーを含め、すべてのエラーが同じハンドラーを通過し、同じJSON形式を返すようになりました。フロントエンドチームは各エンドポイントのレスポンスを推測する代わりに、単一のエラー解析関数を作成できます。

カスタムAppErrorクラスの作成

手動でアタッチしたstatusCodeプロパティを持つ生のErrorオブジェクトを渡すのは繰り返しが多くなります。小さなカスタムクラスでこれを整理できます:

javascript
// utils/AppError.js
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true; // distinguishes expected errors from bugs
}
}
module.exports = AppError;

isOperationalフラグにより、エラーハンドラーは意図的にスローされたエラーと予期しないランタイムクラッシュを区別して処理できます:

javascript
const AppError = require('../utils/AppError');
// in a route
if (!user) {
return next(new AppError('User not found', 404));
}

エラーハンドラー内でisOperationalをチェックすることで、完全なスタックトレースをログに記録するか、アラートを送信するか、単にメッセージをそのまま返すかを決定できます。

2つのエラーパスを示す図:運用エラー(isOperational true)が安全なJSONレスポンスへ、予期しないエラー(isOperational false)がロギング・アラート通知ステップを経て汎用500レスポンスへ

よくある間違い

エラーハンドラーをルートより前に配置することが最も多い間違いです —まだエラーが渡されていないため、トリガーされません。常に最後に登録してください。

すべてのクライアントエラーにHTTP 400を使用するのは不正確です。不正な構文には400、認証が欠落している場合は401、ユーザーは認証されているが許可されていない場合は403、リソースが存在しない場合は404、構造は有効だが内容がビジネスルールに違反する場合は422を使用してください。

本番環境でスタックトレースを公開することは実際のセキュリティリスクです。上記のerrorHandlerの例の条件付きスプレッド(process.env.NODE_ENV === 'development')により、スタックトレースはローカル環境にのみ表示されます。

非同期ルートハンドラーをtry/catchでラップするのを忘れると、未処理のプロミス拒否がエラーハンドラーを完全にバイパスします。薄いasyncラッパーユーティリティにより、すべての路由でボイラープレートなしにこのパターンを強制できます。

まとめ

express-validatorによる検証は、悪いデータを入り口で阻止し、クライアントに構造化されたフィードバックを返します。一貫したJSON形式を備えた一元的なエラーハンドラーは、APIを消費者にとって予測可能にし、成長に合わせて保守を容易にします。AppErrorクラスは両方の懸念を橋渡しします — コードベースのどこからでも予期された失敗を通知するクリーンな方法を提供し、単一のハンドラーが応答方法を決定できるようにします。

レッスンの練習

レッスンのチェックポイント

学んだ内容を復習し、回答へのフィードバックを受けましょう。

0/1
回答済み · 0/1