$npx -y skills add girijashankarj/cursor-handbook --skill error-handler-generatorGenerate typed error classes, error handling middleware, and HTTP error mapping following project conventions. Use when the user asks to set up error handling, create error classes, or implement error middleware.
| 1 | # Skill: Error Handler Generator |
| 2 | |
| 3 | Create a comprehensive error handling system with typed error classes, centralized middleware, and consistent API error responses. |
| 4 | |
| 5 | ## Trigger |
| 6 | When the user asks to create error handling, set up error classes, implement error middleware, or standardize error responses. |
| 7 | |
| 8 | ## Prerequisites |
| 9 | - [ ] Framework identified (Express, Fastify, Koa, etc.) |
| 10 | - [ ] Response envelope format known (`{ error: { code, message }, meta }`) |
| 11 | - [ ] Error categories for the domain identified |
| 12 | |
| 13 | ## Steps |
| 14 | |
| 15 | ### Step 1: Define Error Hierarchy |
| 16 | |
| 17 | ```typescript |
| 18 | // errors/base.ts |
| 19 | export class AppError extends Error { |
| 20 | public readonly statusCode: number; |
| 21 | public readonly code: string; |
| 22 | public readonly isOperational: boolean; |
| 23 | public readonly details?: Record<string, unknown>; |
| 24 | |
| 25 | constructor(params: { |
| 26 | message: string; |
| 27 | statusCode: number; |
| 28 | code: string; |
| 29 | isOperational?: boolean; |
| 30 | details?: Record<string, unknown>; |
| 31 | }) { |
| 32 | super(params.message); |
| 33 | this.name = this.constructor.name; |
| 34 | this.statusCode = params.statusCode; |
| 35 | this.code = params.code; |
| 36 | this.isOperational = params.isOperational ?? true; |
| 37 | this.details = params.details; |
| 38 | Error.captureStackTrace(this, this.constructor); |
| 39 | } |
| 40 | } |
| 41 | ``` |
| 42 | |
| 43 | ### Step 2: Create Specific Error Classes |
| 44 | |
| 45 | ```typescript |
| 46 | // errors/index.ts |
| 47 | export class ValidationError extends AppError { |
| 48 | constructor(message: string, details?: Record<string, unknown>) { |
| 49 | super({ message, statusCode: 400, code: 'VALIDATION_ERROR', details }); |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | export class AuthenticationError extends AppError { |
| 54 | constructor(message = 'Authentication required') { |
| 55 | super({ message, statusCode: 401, code: 'AUTHENTICATION_ERROR' }); |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | export class AuthorizationError extends AppError { |
| 60 | constructor(message = 'Insufficient permissions') { |
| 61 | super({ message, statusCode: 403, code: 'AUTHORIZATION_ERROR' }); |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | export class NotFoundError extends AppError { |
| 66 | constructor(resource: string, id?: string) { |
| 67 | const msg = id ? `${resource} with id '${id}' not found` : `${resource} not found`; |
| 68 | super({ message: msg, statusCode: 404, code: 'NOT_FOUND' }); |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | export class ConflictError extends AppError { |
| 73 | constructor(message: string) { |
| 74 | super({ message, statusCode: 409, code: 'CONFLICT' }); |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | export class RateLimitError extends AppError { |
| 79 | constructor(retryAfter?: number) { |
| 80 | super({ |
| 81 | message: 'Rate limit exceeded', |
| 82 | statusCode: 429, |
| 83 | code: 'RATE_LIMIT_EXCEEDED', |
| 84 | details: retryAfter ? { retryAfterSeconds: retryAfter } : undefined, |
| 85 | }); |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | export class ExternalServiceError extends AppError { |
| 90 | constructor(serviceName: string, message?: string) { |
| 91 | super({ |
| 92 | message: message || `External service error: ${serviceName}`, |
| 93 | statusCode: 502, |
| 94 | code: 'EXTERNAL_SERVICE_ERROR', |
| 95 | details: { service: serviceName }, |
| 96 | }); |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | export class InternalError extends AppError { |
| 101 | constructor(message = 'An unexpected error occurred') { |
| 102 | super({ message, statusCode: 500, code: 'INTERNAL_ERROR', isOperational: false }); |
| 103 | } |
| 104 | } |
| 105 | ``` |
| 106 | |
| 107 | ### Step 3: Create Error Middleware |
| 108 | |
| 109 | ```typescript |
| 110 | // middleware/error-handler.ts |
| 111 | import { Request, Response, NextFunction } from 'express'; |
| 112 | import { AppError } from '../errors/base'; |
| 113 | import { logger } from '../utils/logger'; |
| 114 | |
| 115 | export function errorHandler( |
| 116 | err: Error, |
| 117 | req: Request, |
| 118 | res: Response, |
| 119 | _next: NextFunction |
| 120 | ): void { |
| 121 | const correlationId = req.headers['x-correlation-id'] as string || 'unknown'; |
| 122 | |
| 123 | if (err instanceof AppError) { |
| 124 | logger.warn({ |
| 125 | correlationId, |
| 126 | error: err.code, |
| 127 | message: err.message, |
| 128 | statusCode: err.statusCode, |
| 129 | path: req.path, |
| 130 | method: req.method, |
| 131 | }); |
| 132 | |
| 133 | res.status(err.statusCode).json({ |
| 134 | error: { |
| 135 | code: err.code, |
| 136 | message: err.message, |
| 137 | ...(err.details && { details: err.details }), |
| 138 | }, |
| 139 | meta: { |
| 140 | correlationId, |
| 141 | timestamp: new Date().toISOString(), |
| 142 | }, |
| 143 | }); |
| 144 | return; |
| 145 | } |
| 146 | |
| 147 | // Unexpected errors — don't expose details |
| 148 | logger.error({ |
| 149 | correlationId, |
| 150 | error: 'INTERNAL_ERROR', |
| 151 | message: err.message, |
| 152 | stack: err.stack, |
| 153 | path: req.path, |
| 154 | method: req.method, |
| 155 | }); |
| 156 | |
| 157 | res.status(500).json({ |
| 158 | error: { |
| 159 | code: 'INTERNAL_ERROR', |
| 160 | message: 'An unexpected error occurred', |
| 161 | }, |
| 162 | meta: { |
| 163 | correlationId, |
| 164 | timestamp: new Date().toISOString(), |
| 165 | }, |
| 166 | }); |
| 167 | } |
| 168 | ``` |
| 169 | |
| 170 | ### Step 4: Create Async Handler Wrapper |
| 171 | |
| 172 | ```typescript |
| 173 | // middleware/async-handler.ts |
| 174 | import { Request, Response, NextFunction, RequestHandler } from 'express'; |
| 175 | |
| 176 | export const asyncHandler = ( |
| 177 | fn: (req: Request, res: Response, next: NextFunction) => Promise<void> |
| 178 | ): RequestHandler => { |
| 179 | return (req, res, next) => { |