Extending Error Handling with a Custom Error Generator — Tips & Templates
Why use a Custom Error Generator
- Consistency: produce uniform error objects across services.
- Rich context: attach status, codes, user-facing messages, internal details, and metadata.
- Easier handling: centralized checks (instanceof / error.code) in middleware and alerts.
- Testability: deterministic error shapes simplify unit tests and mocks.
Design principles
- Extend the native Error so instanceof works and stack is preserved.
- Separate operational vs programmer errors (only operational errors are handled and returned to clients).
- Include stable machine-readable codes (e.g., AUTH_INVALID, DBTIMEOUT) plus human messages.
- Avoid leaking internals to users; provide a safe userMessage and a developerMessage/details.
- Make errors serializable (JSON-friendly fields only) for logging and transport.
- Capture origin metadata (service, function, correlationId, request info) where appropriate.
Minimal JS/TS template (generator + base class)
javascript
// BaseError.js class BaseError extends Error { constructor({ code, status = 500, message, userMessage = ‘An error occurred’, details }) { super(message); this.name = this.constructor.name; this.code = code; // machine code this.status = status; // HTTP status or equivalent this.userMessage = userMessage; this.details = details; // internal/debug info Error.captureStackTrace?.(this, this.constructor); } toJSON() { return { name: this.name, code: this.code, status: this.status, userMessage: this.userMessage }; } } module.exports = BaseError;
javascript
// errorGenerator.js const BaseError = require(’./BaseError’); const errorTemplates = { NOT_FOUND: ({ resource }) => new BaseError({ code: ‘NOT_FOUND’, status: 404, message:</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">${</span><span class="token template-string interpolation">resource </span><span class="token template-string interpolation" style="color: rgb(57, 58, 52);">||</span><span class="token template-string interpolation"> </span><span class="token template-string interpolation" style="color: rgb(163, 21, 21);">'Resource'</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">}</span><span class="token template-string" style="color: rgb(163, 21, 21);"> not found</span><span class="token template-string template-punctuation" style="color: rgb(163, 21, 21);">, userMessage: ‘Requested resource not found’, details: { resource } }), VALIDATION: ({ field, reason }) => new BaseError({ code: ‘VALIDATION_ERROR’, status: 400, message:</span><span class="token template-string" style="color: rgb(163, 21, 21);">Invalid </span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">${</span><span class="token template-string interpolation">field</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">}</span><span class="token template-string" style="color: rgb(163, 21, 21);">: </span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">${</span><span class="token template-string interpolation">reason</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">}</span><span class="token template-string template-punctuation" style="color: rgb(163, 21, 21);">, userMessage: ‘Invalid input’, details: { field, reason } }), // add more templates… }; function createError(type, opts = {}) { const factory = errorTemplates[type]; if (!factory) throw new Error(</span><span class="token template-string" style="color: rgb(163, 21, 21);">Unknown error type: </span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">${</span><span class="token template-string interpolation">type</span><span class="token template-string interpolation interpolation-punctuation" style="color: rgb(57, 58, 52);">}</span><span class="token template-string template-punctuation" style="color: rgb(163, 21, 21);">); return factory(opts); } module.exports = { createError, errorTemplates };
Usage examples
- Throw in business logic:
- throw createError(‘NOTFOUND’, { resource: ‘User’ });
- Express error middleware:
javascript
app.use((err, req, res, next) => { if (err instanceof BaseError) { logger.error(err); // structured logging including err.details return res.status(err.status).json({ code: err.code, message: err.userMessage }); } logger.error({ err, uncaught: true }); res.status(500).json({ code: ‘INTERNAL_ERROR’, message: ‘Something went wrong’ }); });
Tips & best practices
- Centralize templates: one source for all error shapes and messages.
- Version error codes if clients depend on them.
- Map internal codes → HTTP statuses in a single place.
- Strip stacks/details for production responses; keep them in logs.
- Add correlation IDs to error objects for tracing across services.
- Test error serialisation and middleware behavior in CI.
- Document codes and userMessages for API consumers.
Quick checklist for production readiness
- Error codes cataloged and documented
- Central generator + typed templates (TS) or runtime validation
- Structured logging with correlation IDs
- Error handling middleware that distinguishes operational vs fatal errors
- Tests for thrown errors, error responses, and logging
If you want, I can: produce a TypeScript version, expand the templates for HTTP APIs, or generate an errors catalog for a specific service—pick one.
Leave a Reply