error-handling

Handle errors gracefully and consistently across your application.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "error-handling" with this command: npx skills add dadbodgeoff/drift/dadbodgeoff-drift-error-handling

Error Handling

Handle errors gracefully and consistently across your application.

When to Use This Skill

  • API error responses

  • Database errors

  • External service failures

  • Validation errors

  • Authentication/authorization errors

Error Response Format

{ "error": { "code": "RESOURCE_NOT_FOUND", "message": "User not found", "details": { "userId": "123" }, "requestId": "req_abc123" } }

TypeScript Implementation

Custom Error Classes

// errors/app-error.ts export class AppError extends Error { constructor( public code: string, public message: string, public statusCode: number = 500, public details?: Record<string, unknown>, public isOperational: boolean = true ) { super(message); this.name = 'AppError'; Error.captureStackTrace(this, this.constructor); } }

// Common error types export class NotFoundError extends AppError { constructor(resource: string, id?: string) { super( 'RESOURCE_NOT_FOUND', ${resource} not found, 404, id ? { [${resource.toLowerCase()}Id]: id } : undefined ); } }

export class ValidationError extends AppError { constructor(details: Array<{ field: string; message: string }>) { super('VALIDATION_ERROR', 'Validation failed', 400, { errors: details }); } }

export class UnauthorizedError extends AppError { constructor(message = 'Authentication required') { super('UNAUTHORIZED', message, 401); } }

export class ForbiddenError extends AppError { constructor(message = 'Access denied') { super('FORBIDDEN', message, 403); } }

export class ConflictError extends AppError { constructor(message: string, details?: Record<string, unknown>) { super('CONFLICT', message, 409, details); } }

export class RateLimitError extends AppError { constructor(retryAfter: number) { super('RATE_LIMITED', 'Too many requests', 429, { retryAfter }); } }

export class ExternalServiceError extends AppError { constructor(service: string, originalError?: Error) { super( 'EXTERNAL_SERVICE_ERROR', ${service} service unavailable, 503, { service, originalMessage: originalError?.message } ); } }

Error Handler Middleware

// middleware/error-handler.ts import { Request, Response, NextFunction } from 'express'; import { AppError } from '../errors/app-error'; import { logger } from '../utils/logger';

interface ErrorResponse { error: { code: string; message: string; details?: Record<string, unknown>; requestId?: string; }; }

export function errorHandler( err: Error, req: Request, res: Response, next: NextFunction ) { const requestId = req.headers['x-request-id'] as string;

// Handle known operational errors if (err instanceof AppError) { logger.warn('Operational error', { code: err.code, message: err.message, statusCode: err.statusCode, requestId, path: req.path, });

const response: ErrorResponse = {
  error: {
    code: err.code,
    message: err.message,
    details: err.details,
    requestId,
  },
};

return res.status(err.statusCode).json(response);

}

// Handle Prisma errors if (err.name === 'PrismaClientKnownRequestError') { const prismaError = err as any; if (prismaError.code === 'P2002') { return res.status(409).json({ error: { code: 'DUPLICATE_ENTRY', message: 'Resource already exists', details: { fields: prismaError.meta?.target }, requestId, }, }); } if (prismaError.code === 'P2025') { return res.status(404).json({ error: { code: 'RESOURCE_NOT_FOUND', message: 'Resource not found', requestId, }, }); } }

// Handle unknown errors (programming errors) logger.error('Unhandled error', { error: err.message, stack: err.stack, requestId, path: req.path, });

// Don't leak error details in production const message = process.env.NODE_ENV === 'production' ? 'Internal server error' : err.message;

return res.status(500).json({ error: { code: 'INTERNAL_ERROR', message, requestId, }, }); }

Async Handler Wrapper

// utils/async-handler.ts import { Request, Response, NextFunction, RequestHandler } from 'express';

type AsyncRequestHandler = ( req: Request, res: Response, next: NextFunction ) => Promise<any>;

export function asyncHandler(fn: AsyncRequestHandler): RequestHandler { return (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; }

// Usage router.get('/users/:id', asyncHandler(async (req, res) => { const user = await userService.findById(req.params.id); if (!user) { throw new NotFoundError('User', req.params.id); } res.json(user); }));

Service Layer Error Handling

// services/user-service.ts import { NotFoundError, ConflictError } from '../errors/app-error';

class UserService { async findById(id: string): Promise<User> { const user = await db.users.findUnique({ where: { id } }); if (!user) { throw new NotFoundError('User', id); } return user; }

async create(data: CreateUserInput): Promise<User> { const existing = await db.users.findUnique({ where: { email: data.email } }); if (existing) { throw new ConflictError('Email already registered', { email: data.email }); } return db.users.create({ data }); }

async updateEmail(userId: string, newEmail: string): Promise<User> { try { return await db.users.update({ where: { id: userId }, data: { email: newEmail }, }); } catch (error) { if (error.code === 'P2002') { throw new ConflictError('Email already in use'); } throw error; } } }

Python Implementation

errors/app_error.py

from dataclasses import dataclass from typing import Optional, Any

@dataclass class AppError(Exception): code: str message: str status_code: int = 500 details: Optional[dict[str, Any]] = None

class NotFoundError(AppError): def init(self, resource: str, id: str = None): super().init( code="RESOURCE_NOT_FOUND", message=f"{resource} not found", status_code=404, details={f"{resource.lower()}_id": id} if id else None, )

class ValidationError(AppError): def init(self, errors: list[dict]): super().init( code="VALIDATION_ERROR", message="Validation failed", status_code=400, details={"errors": errors}, )

class UnauthorizedError(AppError): def init(self, message: str = "Authentication required"): super().init(code="UNAUTHORIZED", message=message, status_code=401)

class ForbiddenError(AppError): def init(self, message: str = "Access denied"): super().init(code="FORBIDDEN", message=message, status_code=403)

FastAPI Error Handler

middleware/error_handler.py

from fastapi import Request, HTTPException from fastapi.responses import JSONResponse from errors.app_error import AppError

async def app_error_handler(request: Request, exc: AppError): return JSONResponse( status_code=exc.status_code, content={ "error": { "code": exc.code, "message": exc.message, "details": exc.details, "requestId": request.headers.get("x-request-id"), } }, )

Register in app

app.add_exception_handler(AppError, app_error_handler)

Frontend Error Handling

// api-client.ts class ApiError extends Error { constructor( public code: string, public message: string, public statusCode: number, public details?: Record<string, unknown> ) { super(message); } }

async function apiRequest<T>(url: string, options?: RequestInit): Promise<T> { const response = await fetch(url, options);

if (!response.ok) { const body = await response.json(); throw new ApiError( body.error.code, body.error.message, response.status, body.error.details ); }

return response.json(); }

// Usage with error handling try { const user = await apiRequest('/api/users/123'); } catch (error) { if (error instanceof ApiError) { if (error.code === 'RESOURCE_NOT_FOUND') { showNotification('User not found'); } else if (error.code === 'VALIDATION_ERROR') { showFormErrors(error.details.errors); } } }

Best Practices

  • Use error codes - Machine-readable, stable across versions

  • Include request ID - Essential for debugging

  • Log appropriately - Warn for operational, error for bugs

  • Don't leak internals - Hide stack traces in production

  • Be consistent - Same format everywhere

Common Mistakes

  • Returning stack traces to users

  • Generic "Something went wrong" messages

  • Not logging errors

  • Inconsistent error formats

  • Catching and swallowing errors

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

oauth-social-login

No summary provided by upstream source.

Repository SourceNeeds Review
General

sse-streaming

No summary provided by upstream source.

Repository SourceNeeds Review
General

deduplication

No summary provided by upstream source.

Repository SourceNeeds Review