@mounaji_npm/error-handler
@mounaji_npm/error-handler
Robust, framework-agnostic error handling for the Mounaji micro-module stack. One taxonomy, one handler — normalize any throw into a typed error, log it with structured + redacted context, shoot an event onto your EventBus, and respond with a client-safe payload.
- Typed taxonomy —
AppError+ValidationError,NotFoundError,ForbiddenError,ConflictError,RateLimitError,ExternalServiceError, … Each carriescode,status,severity,expose,retryable,context, and a nativecausechain. normalizeError()turns any thrown value (Error, string, HTTP-ish object, ZodError, Postgres/Supabase SQLSTATE, network errors) into anAppError, so downstream code handles a single shape. Extensible via custommappers.- Structured logging — built-in zero-dep JSON logger (or inject your own), level auto-derived from severity.
- Secret redaction — deep, cycle-safe masking of
password,token,authorization,apiKey, … before anything is logged or emitted. - Event shooting — emits a redacted record onto any EventBus-like
{ emit(type, payload) }(e.g.@mounaji_npm/event-core), severity mapped into the notifications pipeline. - Adapters — Express middleware, Next.js App Router wrapper, Node
uncaughtException/unhandledRejectionhooks. - Browser-safe core — the main entry has no Node built-ins, so error classes/serializers are shared between client and server.
Install
npm i @mounaji_npm/error-handler
# optional, for event shooting:
npm i @mounaji_npm/event-core
1. Create the handler (once per service)
// lib/errors.js
import { createErrorHandler } from '@mounaji_npm/error-handler';
import { EventBus } from '@mounaji_npm/event-core';
export const errors = createErrorHandler({
serviceName: 'api',
events: EventBus, // optional → emits 'system.error'
exposeStackInResponse: process.env.NODE_ENV !== 'production',
redactKeys: ['x-internal-key'], // extra keys to mask
onError: (appErr, record) => { // optional extra sink
// Sentry.captureException(appErr, { extra: record });
},
});
2. Throw typed errors in your code
import { ValidationError, NotFoundError } from '@mounaji_npm/error-handler';
if (!body.email) throw new ValidationError('email is required', { details: [{ path: 'email' }] });
const user = await db.get(id);
if (!user) throw new NotFoundError('User not found', { context: { id } });
3. Wire the adapter
Express (@mounaji_npm/error-handler/express):
import { createExpressErrorMiddleware, notFoundMiddleware, asyncRoute } from '@mounaji_npm/error-handler/express';
app.get('/users/:id', asyncRoute(async (req, res) => { /* throw freely */ }));
app.use(notFoundMiddleware()); // 404 for unmatched routes
app.use(createExpressErrorMiddleware(errors)); // MUST be last
Next.js App Router (@mounaji_npm/error-handler/next):
// app/api/things/route.js
import { withErrorHandling } from '@mounaji_npm/error-handler/next';
import { errors } from '@/lib/errors';
export const POST = withErrorHandling(errors, async (request) => {
const body = await request.json();
if (!body.name) throw new ValidationError('name is required');
return Response.json({ ok: true });
});
Node process net (@mounaji_npm/error-handler/node):
import { installProcessHandlers } from '@mounaji_npm/error-handler/node';
installProcessHandlers(errors, { exitOnUncaught: true }); // log + emit, then let the supervisor restart
Response shape
expose: true errors reveal their message (and details); everything else gets
a generic message per status so internals never leak:
// 422 ValidationError (expose) // 500 wrapped internal error (hidden)
{ "error": { "code": "VALIDATION_ERROR", { "error": { "code": "INTERNAL_ERROR",
"message": "email is required", "message": "An internal error occurred. Please try again later.",
"status": 422, "retryable": false, "status": 500, "retryable": false,
"details": [{ "path": "email" }] } } "requestId": "…" } }
Event record
events.emit('system.error', record) where record is the redacted
serializeForLog output plus { event, service, eventSeverity }. Subscribe with
EventBus.on('system.error', …) to route into notifications, dashboards, or
alerting. eventSeverity is mapped to event-core's scale (error/critical →
danger).
Architecture
errors.js AppError taxonomy + normalizeError (+ built-in library mappers)
codes.js ERROR_CODES · SEVERITY · status→generic message map
redact.js deep secret redaction
logger.js zero-dep structured logger (pluggable)
serialize.js serializeForLog (full, redacted) · serializeForClient (safe)
handler.js createErrorHandler → capture / toResponse / wrap
adapters/ express.js · next.js · process.js
Framework-agnostic and source-only (no build step), like @mounaji_npm/event-core.