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:
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:
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:
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:
- Integration-specific handler - Highest priority
- Global handler - From
middleware.config.ts - 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:
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:
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:
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:
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:
- Synchronous only: Error handlers cannot be async or return Promises
- Type safety: The
errorparameter isunknown- use type guards before accessing properties - 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' });