Node Browsers TypeScript Codecov Minified size Mastodon Medium
Handle errors in a simple, stable, consistent way.
Simple patterns to:
- βοΈ Create error classes
- π·οΈ Set error properties
- π Wrap or aggregate errors
- π Separate known and unknown errors
Stability:
- π¨ Normalize invalid errors
- π‘οΈ 100% test coverage
- π€ Strict TypeScript types
modern-errors-cli: Handle errors in CLI modulesmodern-errors-beautiful: Prettify error messages and stacksmodern-errors-process: Handle process errorsmodern-errors-bugs: Print where to report bugsmodern-errors-serialize: Serialize/parse errorsmodern-errors-clean: Clean stack tracesmodern-errors-http: Create HTTP error responsesmodern-errors-winston: Log errors with Winstonmodern-errors-switch: Execute class-specific logic- π Create your own plugin
Create error classes.
import ModernError from 'modern-errors' export const BaseError = ModernError.subclass('BaseError') export const UnknownError = BaseError.subclass('UnknownError') export const InputError = BaseError.subclass('InputError') export const AuthError = BaseError.subclass('AuthError') export const DatabaseError = BaseError.subclass('DatabaseError')
Set error properties.
throw new InputError('Invalid file path', { props: { filePath: '/...' } })
Wrap errors.
try { // ... } catch (cause) { throw new InputError('Could not read the file.', { cause }) }
Normalize errors.
try { throw 'Missing file path.' } catch (error) { // Normalized from a string to a `BaseError` instance throw BaseError.normalize(error) }
Use plugins.
import ModernError from 'modern-errors' import modernErrorsSerialize from 'modern-errors-serialize' export const BaseError = ModernError.subclass('BaseError', { plugins: [modernErrorsSerialize], }) // ... // Serialize error as JSON, then back to identical error instance const error = new InputError('Missing file path.') const errorString = JSON.stringify(error) const identicalError = BaseError.parse(JSON.parse(errorString))
npm install modern-errors
If any plugin is used, it must also be installed.
npm install modern-errors-{pluginName}This package works in both Node.js >=18.18.0 and browsers.
This is an ES module. It must be loaded using
an import or import() statement,
not require(). If TypeScript is used, it must be configured to
output ES modules,
not CommonJS.
import ModernError from 'modern-errors' export const BaseError = ModernError.subclass('BaseError') export const UnknownError = BaseError.subclass('UnknownError') export const InputError = BaseError.subclass('InputError') export const AuthError = BaseError.subclass('AuthError') export const DatabaseError = BaseError.subclass('DatabaseError')
Exporting and documenting all error classes allows consumers to check them. This also enables sharing error classes between modules.
if (error instanceof InputError) { // ... }
ErrorClass.subclass() returns a
subclass.
Parent classes' options are merged with their subclasses.
export const BaseError = ModernError.subclass('BaseError', { props: { isError: true }, }) export const InputError = BaseError.subclass('InputError', { props: { isUserError: true }, }) const error = new InputError('...') console.log(error.isError) // true console.log(error.isUserError) // true console.log(error instanceof BaseError) // true console.log(error instanceof InputError) // true
const InputError = BaseError.subclass('InputError', { props: { isUserError: true }, }) const error = new InputError('...') console.log(error.isUserError) // true
const error = new InputError('...', { props: { isUserError: true } }) console.log(error.isUserError) // true
Error properties that are internal or secret can be prefixed with _. This
makes them
non-enumerable,
which prevents iterating or logging them.
const error = new InputError('...', { props: { userId: 6, _isUserError: true }, }) console.log(error.userId) // 6 console.log(error._isUserError) // true console.log(Object.keys(error)) // ['userId'] console.log(error) // `userId` is logged, but not `_isUserError`
throw new InputError('Missing file path.')
Any error's message, class and
options can be wrapped using the
standard
cause option.
Instead of being set as a cause property, the inner error is directly
merged to the outer error,
including its
message,
stack,
name,
AggregateError.errors
and any additional property.
try { // ... } catch (cause) { throw new InputError('Could not read the file.', { cause }) }
The outer error message is appended, unless it is empty. If the outer error
message ends with : or :\n, it is prepended instead.
const cause = new InputError('File does not exist.') // InputError: File does not exist. throw new InputError('', { cause })
// InputError: File does not exist. // Could not read the file. throw new InputError('Could not read the file.', { cause })
// InputError: Could not read the file: File does not exist. throw new InputError(`Could not read the file:`, { cause })
// InputError: Could not read the file: // File does not exist. throw new InputError(`Could not read the file:\n`, { cause })
The outer error's class replaces the inner one.
try { throw new AuthError('...') } catch (cause) { // Now an InputError throw new InputError('...', { cause }) }
Except when the outer error's class is a parent class, such as
BaseError.
try { throw new AuthError('...') } catch (cause) { // Still an AuthError throw new BaseError('...', { cause }) }
The outer error's props and
plugin options are merged.
try { throw new AuthError('...', innerOptions) } catch (cause) { // `outerOptions` are merged with `innerOptions` throw new BaseError('...', { ...outerOptions, cause }) }
The errors option aggregates multiple errors into one. This
is like
new AggregateError(errors)
except that it works with any error class.
const databaseError = new DatabaseError('...') const authError = new AuthError('...') throw new InputError('...', { errors: [databaseError, authError] }) // InputError: ... { // [errors]: [ // DatabaseError: ... // AuthError: ... // ] // }
Any error can be directly passed to the cause or
errors option, even if it is invalid,
unknown or not
normalized.
try { // ... } catch (cause) { throw new InputError('...', { cause }) }
Manipulating errors that are not
Error instances
or that have
invalid properties
can lead to unexpected bugs.
BaseError.normalize() fixes that.
try { throw 'Missing file path.' } catch (invalidError) { // This fails: `invalidError.message` is `undefined` console.log(invalidError.message.trim()) }
try { throw 'Missing file path.' } catch (invalidError) { const normalizedError = BaseError.normalize(invalidError) // This works: 'Missing file path.' // `normalizedError` is a `BaseError` instance. console.log(normalizedError.message.trim()) }
Known errors should be handled in a try {} catch {} block and
wrapped with a specific class.
That block should only cover the statement that might throw in order to prevent
catching other unrelated errors.
try { return regExp.test(value) } catch (error) { // Now an `InputError` instance throw new InputError('Invalid regular expression:', { cause: error }) }
If an error is not handled as described above, it is
considered unknown. This indicates an unexpected exception, usually a bug.
BaseError.normalize(error, UnknownError)
assigns the UnknownError class to those errors.
export const UnknownError = BaseError.subclass('UnknownError')
try { return regExp.test(value) } catch (error) { // Now an `UnknownError` instance throw BaseError.normalize(error, UnknownError) }
Wrapping a module's main functions with
BaseError.normalize(error, UnknownError)
ensures every error being thrown is valid, applies
plugins, and has a class that is either
known or UnknownError.
export const main = () => { try { // ... } catch (error) { throw BaseError.normalize(error, UnknownError) } }
Plugins extend modern-errors features. All available plugins are
listed here.
To use a plugin, please install it, then pass it to the
plugins option.
npm install modern-errors-{pluginName}import ModernError from 'modern-errors' import modernErrorsBugs from 'modern-errors-bugs' import modernErrorsSerialize from 'modern-errors-serialize' export const BaseError = ModernError.subclass('BaseError', { plugins: [modernErrorsBugs, modernErrorsSerialize], }) // ...
Please see the following documentation to create your own plugin.
Most plugins can be configured with options. The option's name is the same as the plugin.
const options = { // `modern-errors-bugs` options bugs: 'https://github.com/my-name/my-project/issues', // `props` can be configured and modified like plugin options props: { userId: 5 }, }
Plugin options can apply to (in priority order):
- Any error: second argument to
ModernError.subclass()
export const BaseError = ModernError.subclass('BaseError', options)
- Any error of a specific class (and its subclasses): second argument to
ErrorClass.subclass()
export const InputError = BaseError.subclass('InputError', options)
- A specific error: second argument to
new ErrorClass()
throw new InputError('...', options)
- A plugin method call: last argument, passing only that plugin's options
ErrorClass[methodName](...args, options[pluginName])
error[methodName](...args, options[pluginName])
The custom option can be used to provide an error class
with additional methods, constructor, properties or options.
export const InputError = BaseError.subclass('InputError', { // The `class` must extend from the parent error class custom: class extends BaseError { // If a `constructor` is defined, its parameters must be (message, options) // Additional `options` can be defined. constructor(message, options) { message += options?.suffix ?? '' super(message, options) } isUserInput() { // ... } }, }) const error = new InputError('Wrong user name', { suffix: ': example' }) console.log(error.message) // 'Wrong user name: example' console.log(error.isUserInput())
Please see the following documentation for information about TypeScript types.
Top-level ErrorClass.
name: string
options: ClassOptions?
Creates and returns a child ErrorClass.
Type: object
Type: Plugin[]
Type: class extends ErrorClass {}
Custom class to add any methods, constructor or properties.
Any plugin options can also be specified.
message: string
options: InstanceOptions?
Return value: Error
Type: object
Type: any
Inner error being wrapped.
Type: any[]
Array of errors being aggregated.
Any plugin options can also be specified.
error: Error | any
NewErrorClass: subclass of ErrorClass
Return value: Error
Normalizes invalid errors.
If error is an instance of ErrorClass (or one of its subclasses), it is left
as is. Otherwise, it is
converted to NewErrorClass, which defaults to
ErrorClass itself.
This framework brings together a collection of modules which can also be used individually:
error-custom-class: Create one error classerror-class-utils: Utilities to properly create error classeserror-serializer: Convert errors to/from plain objectsnormalize-exception: Normalize exceptions/errorsis-error-instance: Check if a value is anErrorinstancemerge-error-cause: Merge an error with itscauseset-error-class: Properly update an error's classset-error-message: Properly update an error's messagewrap-error-message: Properly wrap an error's messageset-error-props: Properly update an error's propertiesset-error-stack: Properly update an error's stackhandle-cli-error: π£ Error handler for CLI applications π₯beautiful-error: Prettify error messages and stackslog-process-errors: Show some β€ to Node.js process errorserror-http-response: Create HTTP error responseswinston-error-format: Log errors with Winston
For any question, don't hesitate to submit an issue on GitHub.
Everyone is welcome regardless of personal background. We enforce a Code of conduct in order to promote a positive and inclusive environment.
This project was made with β€οΈ. The simplest way to give back is by starring and sharing it online.
If the documentation is unclear or has a typo, please click on the page's Edit
button (pencil icon) and suggest a correction.
If you would like to help us fix a bug or add a new feature, please check our guidelines. Pull requests are welcome!