Error Handling
Zeltは、HonoのHTTPExceptionをベースにしたシンプルなエラーハンドリング機構を提供します。
Error Response Format
全てのエラーは一貫したJSON形式で返されます:
{
"code": "ERROR_CODE",
"message": "Error description"
}
Built-in Error Types
VALIDATION_FAILED
リクエストボディのバリデーションが失敗したときに返されます(ステータス400):
{
"code": "VALIDATION_FAILED",
"issues": [
{
"kind": "validation",
"type": "email",
"message": "Invalid email",
"path": ["email"]
}
]
}
INTERNAL_ERROR
未処理のエラーが発生したときに返されます(ステータス500):
{
"code": "INTERNAL_ERROR",
"message": "internal server error"
}
開発モード(NODE_ENV=development)では、デバッグのために実際のエラーメッセージが含まれます。
Throwing HTTPExceptions
HonoのHTTPExceptionを使い、ステータスコードとメッセージまたはカスタムレスポンスを指定してHTTPエラーを投げます。
Custom Message
基本的なテキストレスポンスの場合は、エラーのmessageを設定するだけです:
import { HTTPException } from '@zeltjs/core';
throw new HTTPException(401, { message: 'Unauthorized' });
Custom Response
JSONレスポンスやレスポンスヘッダーの設定にはresオプションを使います。
import { HTTPException } from '@zeltjs/core';
const errorResponse = Response.json(
{ code: 'USER_NOT_FOUND', message: 'User not found' },
{ status: 404 }
);
throw new HTTPException(404, { res: errorResponse });
カスタムヘッダー付きの場合:
const errorResponse = new Response('Unauthorized', {
status: 401,
headers: {
'WWW-Authenticate': 'Bearer error="invalid_token"',
},
});
throw new HTTPException(401, { res: errorResponse });
Cause
デバッグのために元のエラーを付加するにはcauseオプションを使います:
async use(c: RequestContext, next: Next) {
try {
await authorize(c);
} catch (cause) {
throw new HTTPException(401, { message: 'Authorization failed', cause });
}
await next();
return undefined;
}
}
Custom Error Codes
APIの一貫性を保つため、再利用可能なエラーレスポンスを定義します:
import { HTTPException } from '@zeltjs/core';
const notFoundResponse = Response.json(
{ code: 'USER_NOT_FOUND', message: 'User not found' },
{ status: 404 }
);
const forbiddenResponse = Response.json(
{ code: 'FORBIDDEN', message: 'Access denied' },
{ status: 403 }
);
// 使用例
throw new HTTPException(404, { res: notFoundResponse });
throw new HTTPException(403, { res: forbiddenResponse });
または、ファクトリ関数を作成します:
const createErrorResponse = (
status: number,
code: string,
message: string
): Response => {
return Response.json({ code, message }, { status });
};
// 使用例
const response = createErrorResponse(404, 'USER_NOT_FOUND', 'User not found');
throw new HTTPException(404, { res: response });
Error Types for OpenAPI
組み込みのエラー型を使って、OpenAPI仕様書にエラーレスポンスをドキュメント化します:
import type { ErrorBody, ValidationErrorBody } from '@zeltjs/core';
これらの型はエラーレスポンスの構造を定義します:
ErrorBody— 全てのエラー型のUnion(VALIDATION_FAILED | INTERNAL_ERROR)ValidationErrorBody— バリデーションエラー型のみ
Error Handling Flow
Custom Error Handlers
より複雑なエラーハンドリングロジックには、@ErrorHandlerデコレータを使って再利用可能なエラーハンドラクラスを作成します。
Creating an Error Handler
import { ErrorHandler, RequestContext } from '@zeltjs/core';
@ErrorHandler
class DatabaseErrorHandler {
onError(error: Error, c: RequestContext): Response | undefined {
if (error.name === 'PrismaClientKnownRequestError') {
return Response.json(
{ code: 'DATABASE_ERROR', message: 'Database operation failed' },
{ status: 409 }
);
}
return undefined;
}
}
onErrorメソッドは次を受け取ります:
error— 投げられたエラーc— Honoのリクエストcontext
エラーを処理する場合はResponseを返し、次のハンドラへ渡す場合はundefinedを返します。
Registering Error Handlers
errorHandlersオプションを通じて、controllers付きのhttp(...)featureへエラーハンドラを渡します:
const app = createApp([http({
controllers: [UserController],
errorHandlers: [DatabaseErrorHandler, ValidationErrorHandler],
})]);
Handler Chain
エラーハンドラは、http({ errorHandlers: [...] })で登録された順序で実行されます:
- 最初のハンドラの
onErrorが呼ばれる undefinedが返された場合、次のハンドラが呼ばれる- 全てのハンドラが
undefinedを返した場合、デフォルトのエラーハンドラが実行される
@ErrorHandler
class FirstHandler {
onError(error: Error, c: RequestContext) {
if (error instanceof CustomError) {
return Response.json({ code: 'CUSTOM' }, { status: 400 });
}
return undefined;
}
}
@ErrorHandler
class FallbackHandler {
onError(error: Error, c: RequestContext) {
console.error('Unhandled error:', error);
return undefined;
}
}
createApp([http({
controllers: [MyController],
errorHandlers: [FirstHandler, FallbackHandler],
})]);
Dependency Injection
エラーハンドラは依存性注入をサポートしています。serviceへアクセスするにはコンストラクタ注入を使います:
@ErrorHandler
class LoggingErrorHandler {
constructor(private logger = inject(LoggerService)) {}
onError(error: Error, c: RequestContext) {
this.logger.error('Request failed', { error, path: c.req.path });
return undefined;
}
}
Framework Error Classes
Zeltはフレームワークレベルのエラーのための構造化されたエラークラスを提供します。これらのクラスは一貫した命名規則(Zelt*Error)に従い、デバッグ用の型付きcontextを含みます:
| Error Class | Description |
|---|---|
ZeltDecoratorUsageError | デコレータの不正な使用(例: staticメソッドへの適用) |
ZeltLifecycleStateError | 不正なライフサイクル状態(例: shutdown後のメソッド呼び出し) |
ZeltContextNotAvailableError | 実行context外でのprimitive呼び出し |
ZeltAppConfigurationError | 不正なアプリ設定 |
ZeltRouteConfigurationError | 不正なルート設定 |
ZeltMiddlewareExecutionError | middlewareの実行エラー(例: next()の複数回呼び出し) |
ZeltNotImplementedError | メソッドが未実装 |
ZeltSchemaValidationError | 不正なschema定義 |
Usage
import { ZeltAppConfigurationError } from '@zeltjs/core';
try {
// ...
} catch (error) {
if (error instanceof ZeltAppConfigurationError) {
console.log(error.context.reason); // 'no_http_or_commands' | 'duplicate_command'
}
}
Error Context
各エラークラスは、構造化された情報を持つcontextプロパティを含みます:
// @noErrors
// Reason: 型のみの例でランタイムコードがないため
// ZeltDecoratorUsageErrorのcontext
type DecoratorUsageErrorContext = {
decoratorName: string;
reason: 'static_method' | 'missing_decorator';
targetName?: string;
}
// ZeltLifecycleStateErrorのcontext
type LifecycleStateErrorContext = {
operation: string;
currentState: 'disposed' | 'ready' | 'not_ready';
}
Best Practices
- 説明的なエラーコードを使う —
NOT_FOUNDよりUSER_NOT_FOUNDを優先する - 実用的なメッセージを含める — APIの利用者が何が起きたかを理解できるようにする
- 内部の詳細を露出させない — 本番環境ではスタックトレースや内部エラーメッセージを含めない
- エラーレスポンスをドキュメント化する — OpenAPI schemaを使って全ての起こりうるエラーコードをドキュメント化する
- エラーハンドラを具体性の高い順に並べる — 汎用的なハンドラより具体的なハンドラを先に置く
- フレームワークのエラーを使う —
Zelt*Errorクラスを捕捉して、フレームワーク固有の問題を処理する