yanko-belov

error-responses

5
0
# Install this skill:
npx skills add yanko-belov/code-craft --skill "error-responses"

Install specific skill from multi-skill repository

# Description

Use when returning errors from APIs. Use when exposing internal errors. Use when error responses lack structure.

# SKILL.md


name: error-responses
description: Use when returning errors from APIs. Use when exposing internal errors. Use when error responses lack structure.


Error Responses

Overview

Never expose internal errors. Return structured, safe error responses.

Raw error messages leak implementation details, aid attackers, and confuse users. Errors should be safe, consistent, and actionable.

When to Use

  • Implementing error handling in APIs
  • Returning error responses to clients
  • Catching exceptions in controllers
  • Asked to "just return the error message"

The Iron Rule

NEVER expose raw error messages or stack traces to clients.

No exceptions:
- Not for "it helps debugging"
- Not for "internal API only"
- Not for "we're in development"
- Not for "the frontend needs details"

Detection: Leak Smell

If errors expose internals, STOP:

// ❌ VIOLATION: Exposing internals
app.get('/users/:id', async (req, res) => {
  try {
    const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
    res.json(user);
  } catch (error) {
    res.status(500).json({ message: error.message });  // Leaks!
  }
});

What could leak:
- "relation \"users\" does not exist" - Database schema
- "connect ECONNREFUSED 10.0.1.5:5432" - Internal IPs
- Stack traces with file paths
- SQL queries with table names

The Correct Pattern: Safe Error Responses

// ✅ CORRECT: Structured, safe errors

// Custom error classes
class AppError extends Error {
  constructor(
    public statusCode: number,
    public code: string,
    message: string
  ) {
    super(message);
  }
}

class NotFoundError extends AppError {
  constructor(resource: string) {
    super(404, 'NOT_FOUND', `${resource} not found`);
  }
}

class ValidationError extends AppError {
  constructor(public details: Record<string, string[]>) {
    super(400, 'VALIDATION_ERROR', 'Validation failed');
  }
}

// Error handler middleware
app.use((error: Error, req: Request, res: Response, next: NextFunction) => {
  // Log full error internally
  console.error('Error:', {
    message: error.message,
    stack: error.stack,
    path: req.path,
    method: req.method,
  });

  // Return safe response
  if (error instanceof ValidationError) {
    return res.status(400).json({
      error: {
        code: error.code,
        message: error.message,
        details: error.details,
      }
    });
  }

  if (error instanceof AppError) {
    return res.status(error.statusCode).json({
      error: {
        code: error.code,
        message: error.message,
      }
    });
  }

  // Unknown errors - never expose
  res.status(500).json({
    error: {
      code: 'INTERNAL_ERROR',
      message: 'An unexpected error occurred',
    }
  });
});

// Usage in routes
app.get('/users/:id', async (req, res, next) => {
  try {
    const user = await userService.findById(req.params.id);
    if (!user) throw new NotFoundError('User');
    res.json(user);
  } catch (error) {
    next(error);  // Pass to error handler
  }
});

Error Response Structure

Consistent structure for all errors:

interface ErrorResponse {
  error: {
    code: string;        // Machine-readable: 'VALIDATION_ERROR'
    message: string;     // Human-readable: 'Validation failed'
    details?: unknown;   // Additional info (validation errors, etc.)
    requestId?: string;  // For support/debugging
  }
}

// Examples:
// 400 Bad Request
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": {
      "email": ["Invalid email format"],
      "age": ["Must be at least 18"]
    }
  }
}

// 404 Not Found
{
  "error": {
    "code": "NOT_FOUND",
    "message": "User not found"
  }
}

// 500 Internal Error
{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred",
    "requestId": "req_abc123"
  }
}

HTTP Status Codes

Code When to Use
400 Bad request, validation errors
401 Not authenticated
403 Authenticated but not authorized
404 Resource not found
409 Conflict (duplicate, state issue)
422 Unprocessable entity
429 Rate limited
500 Server error (hide details!)
502 Upstream service failed
503 Service unavailable

Pressure Resistance Protocol

1. "It Helps Debugging"

Pressure: "Developers need to see the full error"

Response: Log full errors server-side. Return request IDs for correlation.

Action: { requestId: "abc123" } - developers can look up logs.

2. "Internal API Only"

Pressure: "Only our services call this"

Response: Internal services get compromised. Logs get leaked. Protect everything.

Action: Same safe error handling everywhere.

3. "Development Mode"

Pressure: "Show details in dev, hide in prod"

Response: Dev code becomes prod code. Habits matter.

Action: Same handling in all environments. Use logging for debugging.

Red Flags - STOP and Reconsider

  • res.json({ message: error.message })
  • Stack traces in responses
  • SQL queries in error messages
  • Internal IPs or paths exposed
  • Different error formats per endpoint

All of these mean: Implement proper error handling.

Quick Reference

Exposed (Bad) Safe (Good)
Database error messages "An error occurred"
Stack traces Request ID for log lookup
Internal paths Generic error code
SQL queries "Validation failed"
Variable dumps Structured error object

Common Rationalizations (All Invalid)

Excuse Reality
"Helps debugging" Log server-side, return request ID.
"Internal API" Internal gets compromised too.
"Development mode" Same handling everywhere.
"Frontend needs details" Return safe, structured details.
"It's faster" Error handling is cheap. Breaches aren't.

The Bottom Line

Log everything internally. Expose nothing externally.

Return consistent, structured errors with machine-readable codes and human-readable messages. Never leak stack traces, queries, or internal details. Use request IDs for debugging.

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.