npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@douglasragio/cap-error-handler-beta

v1.0.1

Published

Standardized, reusable error handling module for SAP CAP with integrated documentation and SAP Fiori Design Guidelines-compliant HTML rendering

Readme

@douglasragio/cap-error-handler

A reusable, standardized error handling module for SAP CAP applications that provides centralized error governance, automatic documentation generation, and SAP Fiori Design Guidelines-compliant HTML rendering.

Table of Contents

Features

Immutable Error Model - Standardized, OData-compliant error response structure
Centralized Error Registry - Define all errors once, reuse across multiple projects
Automatic Documentation - Generate documentation URLs and endpoints automatically
SAP Fiori UI - Responsive, accessible HTML rendering following SAP Fiori Design Guidelines
SAP CAP Integration - Seamless bootstrap and error middleware integration
Dual Response Formats - JSON and HTML responses based on Accept header
Message Interpolation - Dynamic context values in error messages
Stateless & Thread-Safe - Production-ready for high-concurrency environments
Comprehensive Validation - Fail-fast error configuration validation

Installation

npm install @douglasragio/cap-error-handler

Quick Start

1. Define Error Configurations

Create an errors.config.ts file with your error definitions:

import { ErrorConfigInput } from '@douglasragio/cap-error-handler';

export const errorConfig: ErrorConfigInput[] = [
  {
    id: 'USER_NOT_FOUND',
    httpStatus: 404,
    message: 'User {userId} not found in the system',
    target: 'Users',
    severity: 3, // Error level
    doc_title: 'User Not Found',
    doc_description: 'The requested user could not be found in the system.',
    doc_causes: [
      'User ID is incorrect or malformed',
      'User has been deleted',
      'User belongs to a different tenant'
    ],
    doc_solutions: [
      'Verify the user ID before making the request',
      'Check user existence before performing operations',
      'Contact your system administrator for access issues'
    ],
    details: [
      {
        message: 'User ID {userId} was not found in the database',
        target: 'Users.id',
        severity: 2 // Warning level for detail
      }
    ]
  },
  {
    id: 'INVALID_EMAIL',
    httpStatus: 400,
    message: 'Invalid email format: {email}',
    target: 'Users.email',
    severity: 2,
    doc_title: 'Invalid Email Format',
    doc_description: 'The provided email address does not match the required format.',
    doc_causes: [
      'Email is missing @ symbol',
      'Email contains invalid characters',
      'Email exceeds maximum length'
    ],
    doc_solutions: [
      'Provide a valid email address (e.g., [email protected])',
      'Remove special characters except . - and _',
      'Keep email under 254 characters'
    ]
  }
];

2. Initialize During CAP Bootstrap

In srv/server.ts or equivalent:

import { setupErrorHandler } from '@douglasragio/cap-error-handler';
import { errorConfig } from './errors.config';

cds.on('bootstrap', (cds) => {
  // Initialize error handler with configuration
  setupErrorHandler(cds, {
    baseUrl: 'https://api.example.com/errors', // Public documentation base URL
    errors: errorConfig
  });
  
  console.log('✓ Error handler initialized');
});

3. Throw Errors from Handlers

In your service handlers:

import { getErrorHandler } from '@douglasragio/cap-error-handler';

class UserService extends cds.ApplicationService {
  async on('READ', 'Users', async (req) => {
    const errorHandler = getErrorHandler(cds);
    const userId = req.data.ID;

    const user = await SELECT.one.from('Users').where({ ID: userId });
    
    if (!user) {
      // Throw standardized error with context
      errorHandler.throw('USER_NOT_FOUND', { userId });
    }

    return user;
  });

  async on('CREATE', 'Users', async (req) => {
    const errorHandler = getErrorHandler(cds);
    const email = req.data.email;

    if (!isValidEmail(email)) {
      errorHandler.throw('INVALID_EMAIL', { email });
    }

    return await INSERT.into('Users').entries(req.data);
  });
}

function isValidEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

4. Access Error Documentation

Users can access error documentation at:

  • HTML Documentation: https://api.example.com/errors/user-not-found
  • JSON Documentation: https://api.example.com/errors/user-not-found (with Accept: application/json)

Core Concepts

Error Model Contract

The error response structure is immutable and follows OData conventions:

{
  error: {
    message: "User 123 not found",
    code: "https://api.example.com/errors/user-not-found",
    target: "Users",
    "@Common.numericSeverity": 3,
    details: [
      {
        code: "https://api.example.com/errors/user-not-found",
        message: "User ID 123 was not found in the database",
        target: "Users.id",
        "@Common.numericSeverity": 2
      }
    ]
  }
}

Numeric Severity Levels

| Level | Label | Usage | |-------|-----------|-----------------------------------| | 0 | Success | Operation completed successfully | | 1 | Info | Informational messages | | 2 | Warning | Warning conditions | | 3 | Error | Error conditions | | 4 | Critical | Critical system failures |

Error ID Format

Error IDs must follow UPPER_SNAKE_CASE pattern:

  • ✅ Valid: USER_NOT_FOUND, INVALID_EMAIL, DB_CONNECTION_FAILED
  • ❌ Invalid: userNotFound, Invalid-Email, db_connection_failed

Error IDs are encoded to URL-safe format for documentation endpoints:

  • USER_NOT_FOUND/errors/user-not-found
  • INVALID_EMAIL/errors/invalid-email

API Documentation

setupErrorHandler(cds, config)

Initialize error handler with CAP integration.

Parameters:

  • cds (Object): SAP CAP cds instance
  • config (ErrorHandlerConfig): Configuration object
    • baseUrl (string): Public base URL for error documentation
    • errors (ErrorConfigInput[]): Array of error configurations

Returns: ErrorHandler instance

Example:

const errorHandler = setupErrorHandler(cds, {
  baseUrl: 'https://api.example.com/errors',
  errors: errorConfig
});

getErrorHandler(cds)

Retrieve the initialized ErrorHandler instance.

Parameters:

  • cds (Object): SAP CAP cds instance

Returns: ErrorHandler | null

Example:

const errorHandler = getErrorHandler(cds);
if (errorHandler) {
  errorHandler.throw('USER_NOT_FOUND', { userId: '123' });
}

errorHandler.throw(errorId, context)

Throw a standardized error.

Parameters:

  • errorId (string): Registered error identifier (e.g., 'USER_NOT_FOUND')
  • context (ErrorContext, optional): Object with values for message interpolation

Throws: StandardError with standardized response structure

Example:

errorHandler.throw('USER_NOT_FOUND', { 
  userId: user.ID,
  userName: user.name 
});

errorHandler.getError(errorId)

Retrieve error configuration by ID.

Parameters:

  • errorId (string): Error identifier

Returns: RegisteredError | null

Example:

const errorConfig = errorHandler.getError('USER_NOT_FOUND');
console.log(errorConfig?.httpStatus); // 404

errorHandler.hasError(errorId)

Check if error is registered.

Parameters:

  • errorId (string): Error identifier

Returns: boolean

Example:

if (errorHandler.hasError('USER_NOT_FOUND')) {
  errorHandler.throw('USER_NOT_FOUND', {});
}

Configuration Guide

ErrorConfigInput Structure

Each error must define:

{
  // Unique identifier (UPPER_SNAKE_CASE)
  id: string;
  
  // HTTP status code
  httpStatus: number; // 100-599
  
  // Message template with optional {placeholder} syntax
  message: string;
  
  // Functional target (e.g., entity name, field name)
  target: string;
  
  // Numeric severity (0-4)
  severity: NumericSeverity;
  
  // Documentation: Title
  doc_title: string;
  
  // Documentation: Full description
  doc_description: string;
  
  // Documentation: List of causes
  doc_causes: string[];
  
  // Documentation: List of solutions
  doc_solutions: string[];
  
  // Optional: Detail items for specific error aspects
  details?: Array<{
    message: string;      // Detail message template
    target: string;       // Specific target for this detail
    severity: NumericSeverity;
  }>;
}

Message Interpolation

Use {placeholder} syntax for dynamic values:

{
  id: 'USER_NOT_FOUND',
  message: 'User {userId} not found in {department}',
  // When thrown with context:
  // errorHandler.throw('USER_NOT_FOUND', { userId: '123', department: 'Sales' })
  // Result: "User 123 not found in Sales"
}

Unreplaced placeholders are left as-is:

errorHandler.throw('USER_NOT_FOUND', { userId: '123' })
// Result: "User 123 not found in {department}"

SAP CAP Integration

Automatic Endpoint Registration

The error handler automatically registers a documentation endpoint:

GET /errors/{error-id}

Supported Accept headers:

  • text/html → Returns HTML documentation page
  • application/json → Returns JSON documentation
  • Default (no header) → Returns HTML documentation

Example requests:

# Get HTML documentation
curl https://api.example.com/errors/user-not-found

# Get JSON documentation
curl -H "Accept: application/json" https://api.example.com/errors/user-not-found

# Unknown error
curl https://api.example.com/errors/unknown-error
# Returns 404 HTML page

Error Middleware

The error handler registers a CAP error middleware that:

  1. Catches StandardError instances thrown by the handler
  2. Converts them to standardized HTTP responses
  3. Sets appropriate HTTP status codes
  4. Includes all error details and documentation links

Integration Points

The module integrates with:

  • Bootstrap: Initializes during CAP startup
  • Routing: Registers documentation endpoints
  • Error Handling: Middleware for StandardError conversion
  • Context: Stores handler in Symbol for retrieval

Best Practices

  1. Initialize early - Call setupErrorHandler() in bootstrap phase
  2. Centralize configuration - Keep all errors in one configuration file
  3. Use context - Provide relevant context when throwing errors
  4. Validate early - Configuration is validated during initialization
  5. Document cause - Always include possible causes for troubleshooting

Error Documentation Endpoints

HTML Response (Default)

Request:

GET https://api.example.com/errors/user-not-found

Response:

Content-Type: text/html; charset=utf-8
Status: 200

[SAP Fiori-styled HTML page with:
- Error title and severity indicator
- Full description
- Possible causes (bulleted list)
- Recommended solutions (bulleted list)
- Technical information (status code, target, severity)
- Error details if applicable]

JSON Response

Request:

GET https://api.example.com/errors/user-not-found
Accept: application/json

Response:

{
  "id": "USER_NOT_FOUND",
  "title": "User Not Found",
  "description": "The requested user could not be found in the system.",
  "causes": [
    "User ID is incorrect",
    "User has been deleted",
    "User belongs to another tenant"
  ],
  "solutions": [
    "Verify the user ID",
    "Check user existence",
    "Contact administrator"
  ],
  "technical": {
    "httpStatus": 404,
    "target": "Users",
    "severity": 3,
    "severityLabel": "Error",
    "code": "https://api.example.com/errors/user-not-found"
  },
  "details": [
    {
      "message": "User ID {userId} was not found in the database",
      "target": "Users.id",
      "severity": 2
    }
  ]
}

Not Found Response

Request:

GET https://api.example.com/errors/unknown-error

Response:

Content-Type: text/html; charset=utf-8
Status: 404

[Fiori-styled 404 HTML page indicating error not found]

Examples

Example 1: Basic Error Throwing

class ProductService extends cds.ApplicationService {
  async on('READ', 'Products', async (req) => {
    const errorHandler = getErrorHandler(cds);
    const productId = req.data.ID;

    const product = await SELECT.one.from('Products').where({ ID: productId });
    
    if (!product) {
      errorHandler.throw('PRODUCT_NOT_FOUND', { productId });
    }

    return product;
  });
}

Error Response:

{
  "error": {
    "message": "Product P001 not found",
    "code": "https://api.example.com/errors/product-not-found",
    "target": "Products",
    "@Common.numericSeverity": 3,
    "details": []
  }
}

Example 2: Error with Details

const errorConfig: ErrorConfigInput[] = [
  {
    id: 'VALIDATION_FAILED',
    httpStatus: 400,
    message: 'Validation failed for entity {entity}',
    target: 'Validation',
    severity: 2,
    doc_title: 'Validation Failed',
    doc_description: 'One or more validation rules were violated.',
    doc_causes: ['Required field is empty', 'Field format is invalid'],
    doc_solutions: ['Check field values', 'Review validation rules'],
    details: [
      {
        message: 'Required field {fieldName} is empty',
        target: 'Validation.{fieldName}',
        severity: 3
      },
      {
        message: 'Field {fieldName} exceeds maximum length',
        target: 'Validation.{fieldName}',
        severity: 2
      }
    ]
  }
];

Error thrown:

errorHandler.throw('VALIDATION_FAILED', { 
  entity: 'Users',
  fieldName: 'email'
});

Response:

{
  "error": {
    "message": "Validation failed for entity Users",
    "code": "https://api.example.com/errors/validation-failed",
    "target": "Validation",
    "@Common.numericSeverity": 2,
    "details": [
      {
        "code": "https://api.example.com/errors/validation-failed",
        "message": "Required field email is empty",
        "target": "Validation.email",
        "@Common.numericSeverity": 3
      },
      {
        "code": "https://api.example.com/errors/validation-failed",
        "message": "Field email exceeds maximum length",
        "target": "Validation.email",
        "@Common.numericSeverity": 2
      }
    ]
  }
}

Example 3: Complex Configuration

export const errorConfig: ErrorConfigInput[] = [
  {
    id: 'DATABASE_UNAVAILABLE',
    httpStatus: 503,
    message: 'Database service is currently unavailable',
    target: 'Database',
    severity: 4, // Critical
    doc_title: 'Database Unavailable',
    doc_description: 'The database service is temporarily unavailable. Please retry your request.',
    doc_causes: [
      'Database server is down for maintenance',
      'Network connectivity issue',
      'Database connection pool exhausted',
      'Database authentication failed'
    ],
    doc_solutions: [
      'Wait for maintenance window to complete',
      'Check network connectivity',
      'Reduce concurrent connections',
      'Verify database credentials',
      'Contact database administrator'
    ]
  }
];

Best Practices

1. Error ID Naming

Follow consistent naming conventions:

// Good: Clear, descriptive, grouped by domain
USER_NOT_FOUND
USER_ALREADY_EXISTS
PERMISSION_DENIED
DATABASE_CONNECTION_FAILED
INVALID_REQUEST_FORMAT

// Avoid: Vague, technical jargon
ERROR_1
BAD_DATA
FAIL
UNKNOWN_ISSUE

2. Documentation Quality

Write documentation that helps troubleshooting:

// Good: Actionable, specific
{
  doc_description: 'The user account specified in the request does not exist in the system.',
  doc_causes: [
    'User ID was typed incorrectly',
    'User account was deleted within the last 30 days',
    'User belongs to a different tenant in multi-tenant setup'
  ],
  doc_solutions: [
    'Verify the user ID matches the intended user',
    'Check user list to confirm account exists',
    'Ensure you are accessing the correct tenant'
  ]
}

// Avoid: Vague, unhelpful
{
  doc_description: 'User not found.',
  doc_causes: ['User does not exist'],
  doc_solutions: ['Try again']
}

3. Context Interpolation

Provide relevant context values:

// Good: Rich context for troubleshooting
errorHandler.throw('PAYMENT_FAILED', {
  orderId: order.ID,
  amount: order.totalAmount,
  paymentMethod: order.paymentMethod,
  errorCode: paymentGateway.errorCode
});

// Avoid: Insufficient context
errorHandler.throw('PAYMENT_FAILED', {
  error: 'failed'
});

4. HTTP Status Codes

Use appropriate HTTP status codes:

400 // Bad Request - Client error (invalid input)
401 // Unauthorized - Missing authentication
403 // Forbidden - Insufficient permissions
404 // Not Found - Resource doesn't exist
409 // Conflict - State conflict (already exists)
500 // Internal Server Error - Server error
503 // Service Unavailable - Temporary unavailability

5. Severity Assignment

Assign severity based on impact:

0 // Success - Operation completed
1 // Info - Informational message
2 // Warning - Non-critical issue (proceed with caution)
3 // Error - Operation failed (must be resolved)
4 // Critical - System failure (requires immediate action)

Versioning

This module follows Semantic Versioning (semver):

  • MAJOR: Breaking changes to error model or HTML rendering
  • MINOR: New features (new errors, new fields that don't break existing contracts)
  • PATCH: Bug fixes, internal improvements

Breaking changes include:

  • Adding/removing/renaming fields in the error model
  • Changing field types in the error model
  • Changing HTML structure or styling significantly
  • Changing endpoint paths

Non-breaking changes include:

  • Adding new error definitions
  • Enhancing HTML styles without breaking layout
  • Improving documentation generation

License

Apache License 2.0


Support & Contributing

For issues, feature requests, or contributions, please refer to the project repository.

For more information: