Kodokon kodokon.com

入力の検証とエラーミドルウェア

APIの境界でデータを検証し、すべてのエラー処理を四つの引数を持つ一つのミドルウェアに集約しましょう。

9 分 · 3 問

このレッスンを Kodokon で開く

入ってくるあらゆるデータは、そうでないと証明されるまで敵対的です。JSONのボディ、URLのパラメーター、ヘッダー、すべてです。検証はアプリケーションの境界、つまりコントローラーの手前に属します。そうすれば内側のレイヤーは、保証されたデータの上で動けます。次に、二種類のエラーを区別しましょう。運用上のエラー(無効な入力、存在しないリソース、競合)は、想定内であり4xxに翻訳されます。そしてプログラミングのエラー(バグ)は、クライアント側には不透明な500を、サーバー側には完全なスタックトレースを生み出さなければなりません。

JAVASCRIPT
export class HttpError extends Error {
  constructor(status, message, details = undefined) {
    super(message);
    this.status = status;
    this.details = details;
  }
}
自らのHTTPステータスを携えるエラー。基本の構成要素です。

検証ミドルウェアは、コントローラーの手前でリクエストを横取りします。下の手書きのバージョンは原理を示すものです。本番では、宣言的なスキーマ(zodが標準になりました)が、加えて型推論と構造化されたメッセージを提供します。トレードオフは、一つの依存とわずかな実行時コストで、その代わりに集約された、組み合わせ可能で、決して忘れようのないルールが手に入ることです。

JAVASCRIPT
import { HttpError } from './http-error.js';

export function validateUser(req, res, next) {
  const { email, name } = req.body ?? {};
  const errors = [];
  if (typeof email !== 'string' || !email.includes('@')) {
    errors.push('email: invalid format');
  }
  if (typeof name !== 'string' || name.trim().length < 2) {
    errors.push('name: 2 characters minimum');
  }
  if (errors.length > 0) {
    return next(new HttpError(400, 'Invalid body', errors));
  }
  next();
}
router.post('/', validateUser, controller.create)でつなぎ込む。

Expressはエラーミドルウェアを、その四つの引数のシグネチャ(err, req, res, next)によって見分けます。すべてのルートのあとにapp.use(errorHandler)で最後に登録されると、それはAPIの唯一のエラーの出口になります。均質なレスポンス形式、一箇所でのロギング、内部情報の漏洩なし、です。一方でコントローラーは、ただnext(err)を呼ぶだけです。

JAVASCRIPT
export function errorHandler(err, req, res, next) {
  if (res.headersSent) {
    return next(err);
  }
  const status = err.status ?? 500;
  const body = { error: err.message };
  if (err.details) {
    body.details = err.details;
  }
  if (status >= 500) {
    console.error(err);
    body.error = 'Internal server error';
  }
  res.status(status).json(body);
}
4xxはクライアントに対して透明、500は隠されるがログには残る。

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. Expressはエラーミドルウェアを、普通のミドルウェアとどうやって見分けますか?
    • errorという語を含まなければならない、その名前によって
    • 四つの引数のシグネチャ(err, req, res, next)によって
    • チェーンの先頭という、その位置によって
    • app.setErrorHandlerへの明示的な呼び出しによって
  2. 予期しないプログラミングエラーに対する正しいHTTPレスポンスは何ですか?
    • 診断を助けるために、メッセージとスタックを添えた500
    • リクエストが失敗したことを知らせる400
    • 汎用的なメッセージを添えた500で、完全なエラーはサーバー側にログされる
  3. Express 4で、非同期ハンドラーがtry/catchなしで拒否されるとどうなりますか?
    • それでもエラーはエラーミドルウェアに届く
    • リクエストは宙づりのままになる。拒否は決してnextに転送されない
    • Expressが自動的に400を返す
    • Nodeのプロセスが即座に止まる