error-handling について
Node.js 開発者向けのエラー処理学習スキルです。カスタム例外、Express ミドルウェア、プロセスレベルの障害に加え、リトライやテスト、トラブルシューティングを扱います。
主な機能
- 01型付きカスタムエラークラス
- 02同期・非同期エラー処理
- 03Express エラーミドルウェア
- 04プロセスレベルのハンドラー
- 05指数バックオフによるリトライ
- 06テストとトラブルシューティング
活用シーン
- 01堅牢な Node.js アプリの構築
- 02Express API のエラー一元管理
- 03DB や外部 API の呼び出し失敗への対応
- 04エラー動作と復旧処理のテスト
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ pluginagentmarketplace/ custom-plugin-nodejs --skill error-handlingインストール後、Claude Code で/error-handlingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/config.yamlschema.jsonreferences/GUIDE.mdPATTERNS.mdscripts/validate.py詳細
Node.js のエラーハンドリングスキル
適切に失敗し、有益なフィードバックを提供する、堅牢な Node.js アプリケーションを構築するためのエラーハンドリングパターンを習得します。
クイックスタート
エラーハンドリングは次の 4 つのレイヤーで行います。
- カスタムエラークラス - 型付けされた、情報量の多いエラー
- Try/Catch パターン - 同期処理と非同期処理のハンドリング
- Express ミドルウェア - API エラーの一元管理
- プロセスハンドラー - キャッチされない例外
主な概念
カスタムエラークラス
// Base application error
class AppError extends Error {
constructor(message, statusCode, code) {
super(message);
this.statusCode = statusCode;
this.code = code;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
// Specific error types
class ValidationError extends AppError {
constructor(message, details = []) {
super(message, 400, 'VALIDATION_ERROR');
this.details = details;
}
}
class NotFoundError extends AppError {
constructor(resource = 'Resource') {
super(`${resource} not found`, 404, 'NOT_FOUND');
}
}
class UnauthorizedError extends AppError {
constructor(message = 'Authentication required') {
super(message, 401, 'UNAUTHORIZED');
}
}
class ForbiddenError extends AppError {
constructor(message = 'Access denied') {
super(message, 403, 'FORBIDDEN');
}
}
非同期エラーハンドリング
// Async wrapper for Express routes
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Usage
router.get('/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) throw new NotFoundError('User');
res.json(user);
}));
Express エラーミドルウェア
const errorHandler = (err, req, res, next) => {
// Log error
logger.error({
message: err.message,
stack: err.stack,
code: err.code,
url: req.originalUrl
});
// Handle operational errors
if (err.isOperational) {
return res.status(err.statusCode).json({
success: false,
error: {
message: err.message,
code: err.code,
...(err.details && { details: err.details })
}
});
}
// Production: hide internal errors
if (process.env.NODE_ENV === 'production') {
return res.status(500).json({
success: false,
error: { message: 'Internal server error', code: 'INTERNAL_ERROR' }
});
}
// Development: show stack
res.status(500).json({
success: false,
error: { message: err.message, stack: err.stack }
});
};
学習ロードマップ
初級(1~2 週間)
- ✅ Error 型を理解する
- ✅ 同期コードでの try/catch
- ✅ Promise の .catch() ハンドリング
- ✅ 基本的な Express エラーミドルウェア
中級(3~4 週間)
- ✅ カスタムエラークラス
- ✅ 非同期エラーラッパー
- ✅ エラーハンドリングの一元管理
- ✅ Winston によるエラーログ記録
上級(5~6 週間)
- ✅ 本番環境でのエラー監視
- ✅ グレースフルシャットダウン
- ✅ サーキットブレーカーパターン
- ✅ エラーリカバリー戦略
プロセスレベルのエラーハンドリング
// Uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
logger.fatal({ error }, 'Uncaught exception');
process.exit(1);
});
// Unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection:', reason);
logger.error({ reason }, 'Unhandled rejection');
});
// Graceful shutdown
process.on('SIGTERM', async () => {
console.log('SIGTERM received. Graceful shutdown...');
server.close(() => console.log('HTTP server closed'));
await mongoose.connection.close();
process.exit(0);
});
指数バックオフによる再試行
async function retryOperation(fn, options = {}) {
const { maxRetries = 3, delay = 1000, backoff = 2 } = options;
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
if (attempt === maxRetries) throw error;
const waitTime = delay * Math.pow(backoff, attempt - 1);
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}
throw lastError;
}
エラーレスポンス形式
// Success
{ "success": true, "data": { ... } }
// Error
{
"success": false,
"error": {
"message": "User not found",
"code": "NOT_FOUND",
"details": [],
"timestamp": "2024-01-15T10:30:00.000Z"
}
}
単体テストのテンプレート
describe('Error Handling', () => {
describe('ValidationError', () => {
it('should create error with correct properties', () => {
const error = new ValidationError('Invalid input', [
{ field: 'email', message: 'Required' }
]);
expect(error.statusCode).toBe(400);
expect(error.code).toBe('VALIDATION_ERROR');
expect(error.isOperational).toBe(true);
});
});
describe('asyncHandler', () => {
it('should catch async errors', async () => {
const mockNext = jest.fn();
const handler = asyncHandler(async () => {
throw new Error('Test error');
});
await handler({}, {}, mockNext);
expect(mockNext).toHaveBeenCalledWith(expect.any(Error));
});
});
});
トラブルシューティング
| 問題 | 原因 | 解決策 |
|---|---|---|
| 未処理の拒否に関する警告 | .catch() がない | プロセスハンドラーとローカルの catch を追加する |
| エラーが気づかれないまま握りつぶされる | 空の catch ブロック | エラーをログに記録するか、再スローする |
| スタックトレースがない | Error.captureStackTrace が呼び出されていない | AppError 基底クラスを使用する |
| エラーによるメモリーリーク | エラー参照を保持している | WeakMap を使用するか、クリーンアップする |
使用する場面
次のような場合は、適切なエラーハンドリングを使用してください。
- 本番環境向けの Node.js アプリケーションを構築する
- Express で REST API を作成する
- データベース操作を処理する
- 外部 API 呼び出しを処理する
- 再試行ロジックを実装する
関連スキル
- Express REST API(API エラーレスポンス)
- 非同期プログラミング(Promise の拒否)
- テストとデバッグ(エラーのテスト)
リソース
コメント
Markdown 対応