Vue Storefront is now Alokai! Learn More
Custom Error Handlers

Custom Error Handlers

The Alokai middleware provides a flexible error handling system that lets you customize how errors are handled in your integrations. You can configure error handlers at the global level for consistent behavior across all integrations, or per-integration for specialized handling.

Why Use Custom Error Handlers?

  • Better Debugging: Connect to monitoring services like Sentry or DataDog
  • User-Friendly Messages: Return helpful error messages instead of generic ones
  • Specialized Handling: Treat critical integrations (like payments) differently
  • Security: Control what error details are exposed to clients

Quick Start

Global Error Handler

Available from @alokai/connect version 1.2.0. For earlier versions, use Integration-Specific Handler only.

Add a global error handler in middleware.config.ts:

middleware.config.ts
import type { MiddlewareConfig } from '@alokai/connect/middleware';
import { config as commerceConfig } from '@/integrations/sapcc';

export const config = {
  errorHandler: (error, req, res) => {
    res.status(500).send({ error: 'Something went wrong' });
  },
  integrations: {
    commerce: commerceConfig,
  }
} satisfies MiddlewareConfig;

This handler catches all errors from integration endpoints (/commerce/*, etc.).

Integration-Specific Handler

For integrations that need special error handling, add an errorHandler to the integration config:

integrations/payment/config.ts
import { getLogger, type Integration } from '@alokai/connect/middleware';
import type { MiddlewareConfig } from './types';

export const config = {
  location: './lib/integrations/payment/index.server.js',
  errorHandler: (error, req, res) => {
    const logger = getLogger(res);
    logger.alert('CRITICAL: Payment error');
    
    res.status(503).send({ 
      error: 'Payment service temporarily unavailable',
    });
  },
  configuration: { /* ... */ }
} satisfies Integration<MiddlewareConfig>;

Then use it in your middleware configuration:

middleware.config.ts
import type { MiddlewareConfig } from '@alokai/connect/middleware';
import { config as commerceConfig } from '@/integrations/sapcc';
import { config as paymentConfig } from '@/integrations/payment';

export const config = {
  integrations: {
    commerce: commerceConfig,
    payment: paymentConfig, // Uses its own error handler
  }
} satisfies MiddlewareConfig;

Error Handler Priority

When an error occurs, Alokai checks for error handlers in this order:

  1. Integration-specific handler - Highest priority
  2. Global handler - From middleware.config.ts
  3. Built-in default - Alokai's fallback handler

When to Use Which

Use global for consistent error handling across all integrations. Use integration-specific when one integration needs special treatment (e.g., payment errors requiring alerts).

Automatic Error Logging

Built-in Logging

Alokai automatically logs all errors before your custom handler runs. You can add extra logging in your handler if needed.

For more information, see the Logging guide.

Error Handler Scope

Error handlers only apply to integration endpoints (/:integrationName/*):

✅ Caught:

  • /commerce/getProduct - Integration methods
  • /commerce/unified/getProductDetails - Extension methods

❌ Not caught:

  • /healthz - Custom routes outside integrations
  • Routes added via extendApp

Common Use Cases

Adding Error Monitoring

Connect to Sentry while keeping default error responses:

middleware.config.ts
import { defaultErrorHandler, type MiddlewareConfig } from '@alokai/connect/middleware';
import * as Sentry from '@sentry/node';

export const config = {
  errorHandler: (error, req, res) => {
    Sentry.captureException(error, {
      user: { id: req.headers['x-user-id'] as string }
    });
    defaultErrorHandler(error, req, res);
  },
  integrations: { /* ... */ }
} satisfies MiddlewareConfig;

Adding Custom Context

Log additional context beyond what's automatically included:

middleware.config.ts
import { getLogger, type MiddlewareConfig } from '@alokai/connect/middleware';

export const config = {
  errorHandler: (error, req, res) => {
    const logger = getLogger(res);
    // Middleware already logs functionName, integrationName, etc.
    // Add extra business context here
    logger.error('Payment failed', { 
      orderId: req.body?.orderId,
      userId: req.headers['x-user-id']
    });
    
    res.status(500).send({ error: 'Internal server error' });
  },
  integrations: { /* ... */ }
} satisfies MiddlewareConfig;

Different Handling by Error Type

Respond differently based on error status:

middleware.config.ts
import { getLogger, type MiddlewareConfig } from '@alokai/connect/middleware';

export const config = {
  errorHandler: (error, req, res) => {
    if (error && typeof error === 'object' && 'statusCode' in error) {
      const statusCode = (error as any).statusCode;
      
      if (statusCode === 401 || statusCode === 403) {
        res.status(statusCode).send({ 
          error: 'Authentication required',
          redirectTo: '/login'
        });
        return;
      }
    }
    
    res.status(500).send({ error: 'Internal server error' });
  },
  integrations: { /* ... */ }
} satisfies MiddlewareConfig;

Using the Default Error Handler

Keep the default response format while adding custom logic:

middleware.config.ts
import { defaultErrorHandler, getLogger, type MiddlewareConfig } from '@alokai/connect/middleware';

export const config = {
  errorHandler: (error, req, res) => {
    const logger = getLogger(res);
    logger.info('Additional context', { userId: req.headers['x-user-id'] });
    
    defaultErrorHandler(error, req, res);
  },
  integrations: { /* ... */ }
} satisfies MiddlewareConfig;

The default handler:

  • Returns appropriate status codes (400s for client errors, 500s for server errors)
  • Shows error messages for 4xx errors
  • Returns generic messages for 5xx errors (avoids leaking server details)

Important Notes

Must Send Response

Your error handler MUST send a response using res.status().send(). If you don't, the request will hang.

Key points:

  1. Synchronous only: Error handlers cannot be async or return Promises
  2. Type safety: The error parameter is unknown - use type guards before accessing properties
  3. One response: Once you send a response, no other handlers will run

Best Practices

✅ Use the logger

const logger = getLogger(res);
logger.error('Error details', { error });

✅ Don't expose sensitive information

// ❌ BAD
res.status(500).send({ error: error.message, stack: error.stack });

// ✅ GOOD
res.status(500).send({ error: 'An error occurred' });

✅ Use type guards

// ❌ BAD
res.status(error.statusCode).send({ error: error.message });

// ✅ GOOD
const status = (error && typeof error === 'object' && 'statusCode' in error)
  ? (error as any).statusCode
  : 500;
res.status(status).send({ error: 'Error occurred' });