Modern Errors

Handle errors in a simple, stable, consistent way

README

modern-errors logo

Node
Browsers
TypeScript
Codecov
Minified size
Mastodon
Medium

Handle errors in a simple, stable, consistent way.

Features


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

Plugins


- [modern-errors-cli](https://github.com/ehmicky/modern-errors-cli): Handle
  errors in CLI modules
- [modern-errors-process](https://github.com/ehmicky/modern-errors-process):
  Handle process errors
- [modern-errors-bugs](https://github.com/ehmicky/modern-errors-bugs): Print
  where to report bugs
- [modern-errors-serialize](https://github.com/ehmicky/modern-errors-serialize):
  Serialize/parse errors
- [modern-errors-clean](https://github.com/ehmicky/modern-errors-clean): Clean
  stack traces
- [modern-errors-http](https://github.com/ehmicky/modern-errors-http): Create
  HTTP error responses
- [modern-errors-winston](https://github.com/ehmicky/modern-errors-winston):
  Log errors with Winston
- [modern-errors-switch](https://github.com/ehmicky/modern-errors-switch):
  Execute class-specific logic
- 🔌 Create your own plugin

Example


Create error classes.

  1. ```js
  2. import ModernError from 'modern-errors'

  3. export const BaseError = ModernError.subclass('BaseError')

  4. export const UnknownError = BaseError.subclass('UnknownError')
  5. export const InputError = BaseError.subclass('InputError')
  6. export const AuthError = BaseError.subclass('AuthError')
  7. export const DatabaseError = BaseError.subclass('DatabaseError')
  8. ```

Set error properties.

  1. ```js
  2. throw new InputError('Invalid file path', { props: { filePath: '/...' } })
  3. ```

Wrap errors.

  1. ```js
  2. try {
  3.   // ...
  4. } catch (cause) {
  5.   throw new InputError('Could not read the file.', { cause })
  6. }
  7. ```

Normalize errors.


  1. ```js
  2. try {
  3.   throw 'Missing file path.'
  4. } catch (error) {
  5.   // Normalized from a string to a `BaseError` instance
  6.   throw BaseError.normalize(error)
  7. }
  8. ```

Use plugins.

  1. ```js
  2. import ModernError from 'modern-errors'
  3. import modernErrorsSerialize from 'modern-errors-serialize'

  4. export const BaseError = ModernError.subclass('BaseError', {
  5.   plugins: [modernErrorsSerialize],
  6. })

  7. // ...

  8. // Serialize error as JSON, then back to identical error instance
  9. const error = new InputError('Missing file path.')
  10. const errorString = JSON.stringify(error)
  11. const identicalError = BaseError.parse(JSON.parse(errorString))
  12. ```

Install


  1. ```bash
  2. npm install modern-errors
  3. ```

If any plugin is used, it must also be installed.

  1. ```bash
  2. npm install modern-errors-{pluginName}
  3. ```

This package works in both Node.js >=14.18.0 and

This is an ES module. It must be loaded using
[an import or import() statement](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c),
not require(). If TypeScript is used, it must be configured to
not CommonJS.

Usage


⛑️ Error classes


Create error classes


  1. ```js
  2. import ModernError from 'modern-errors'

  3. export const BaseError = ModernError.subclass('BaseError')

  4. export const UnknownError = BaseError.subclass('UnknownError')
  5. export const InputError = BaseError.subclass('InputError')
  6. export const AuthError = BaseError.subclass('AuthError')
  7. export const DatabaseError = BaseError.subclass('DatabaseError')
  8. ```

Export error classes


Exporting and documenting all error classes allows consumers to check them. This
also enables sharing error classes between modules.

Check error classes


  1. ```js
  2. if (error instanceof InputError) {
  3.   // ...
  4. }
  5. ```

Error subclasses


[ErrorClass.subclass()](#errorclasssubclassname-options) returns a
Parent classes' options are merged with their subclasses.

  1. ```js
  2. export const BaseError = ModernError.subclass('BaseError', {
  3.   props: { isError: true },
  4. })
  5. export const InputError = BaseError.subclass('InputError', {
  6.   props: { isUserError: true },
  7. })

  8. const error = new InputError('...')
  9. console.log(error.isError) // true
  10. console.log(error.isUserError) // true
  11. console.log(error instanceof BaseError) // true
  12. console.log(error instanceof InputError) // true
  13. ```

🏷️ Error properties


Error class properties


  1. ```js
  2. const InputError = BaseError.subclass('InputError', {
  3.   props: { isUserError: true },
  4. })
  5. const error = new InputError('...')
  6. console.log(error.isUserError) // true
  7. ```

Error instance properties


  1. ```js
  2. const error = new InputError('...', { props: { isUserError: true } })
  3. console.log(error.isUserError) // true
  4. ```

Internal error properties


Error properties that are internal or secret can be prefixed with _. This
makes them
which prevents iterating or logging them.


  1. ```js
  2. const error = new InputError('...', {
  3.   props: { userId: 6, _isUserError: true },
  4. })
  5. console.log(error.userId) // 6
  6. console.log(error._isUserError) // true
  7. console.log(Object.keys(error)) // ['userId']
  8. console.log(error) // `userId` is logged, but not `_isUserError`
  9. ```

🎀 Wrap errors


Throw errors


  1. ```js
  2. throw new InputError('Missing file path.')
  3. ```

Wrap inner error


Any error's message, class and
options can be wrapped using the
[cause option](#optionscause).

Instead of being set as a cause property, the inner error is directly
merged to the outer error,
including its
[message](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/message),
[stack](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack),
[name](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/name),
[AggregateError.errors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError)

  1. ```js
  2. try {
  3.   // ...
  4. } catch (cause) {
  5.   throw new InputError('Could not read the file.', { cause })
  6. }
  7. ```

Wrap error message


The outer error message is appended, unless it is empty. If the outer error
message ends with : or :\n, it is prepended instead.

  1. ```js
  2. const cause = new InputError('File does not exist.')
  3. // InputError: File does not exist.
  4. throw new InputError('', { cause })
  5. ```

  1. ```js
  2. // InputError: File does not exist.
  3. // Could not read the file.
  4. throw new InputError('Could not read the file.', { cause })
  5. ```

  1. ```js
  2. // InputError: Could not read the file: File does not exist.
  3. throw new InputError(`Could not read the file:`, { cause })
  4. ```

  1. ```js
  2. // InputError: Could not read the file:
  3. // File does not exist.
  4. throw new InputError(`Could not read the file:\n`, { cause })
  5. ```

Wrap error class


The outer error's class replaces the inner one.

  1. ```js
  2. try {
  3.   throw new AuthError('...')
  4. } catch (cause) {
  5.   // Now an InputError
  6.   throw new InputError('...', { cause })
  7. }
  8. ```

Except when the outer error's class is a parent class, such as
[BaseError](#create-error-classes).

  1. ```js
  2. try {
  3.   throw new AuthError('...')
  4. } catch (cause) {
  5.   // Still an AuthError
  6.   throw new BaseError('...', { cause })
  7. }
  8. ```

Wrap error options


The outer error's [props](#%EF%B8%8F-error-properties) and
plugin options are merged.

  1. ```js
  2. try {
  3.   throw new AuthError('...', innerOptions)
  4. } catch (cause) {
  5.   // `outerOptions` are merged with `innerOptions`
  6.   throw new BaseError('...', { ...outerOptions, cause })
  7. }
  8. ```

Aggregate errors


The [errors option](#optionserrors) aggregates multiple errors into one. This
is like
[new AggregateError(errors)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError/AggregateError)
except that it works with any error class.

  1. ```js
  2. const databaseError = new DatabaseError('...')
  3. const authError = new AuthError('...')
  4. throw new InputError('...', { errors: [databaseError, authError] })
  5. // InputError: ... {
  6. //   [errors]: [
  7. //     DatabaseError: ...
  8. //     AuthError: ...
  9. //   ]
  10. // }
  11. ```

🚨 Normalize errors


Wrapped errors


Any error can be directly passed to the [cause](#wrap-inner-error) or
[errors](#aggregate-errors) option, even if it is invalid,
unknown or not

  1. ```js
  2. try {
  3.   // ...
  4. } catch (cause) {
  5.   throw new InputError('...', { cause })
  6. }
  7. ```

Invalid errors


Manipulating errors that are not
[Error instances](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
or that have
can lead to unexpected bugs.
[BaseError.normalize()](#errorclassnormalizeerror-newerrorclass) fixes that.


  1. ```js
  2. try {
  3.   throw 'Missing file path.'
  4. } catch (invalidError) {
  5.   // This fails: `invalidError.message` is `undefined`
  6.   console.log(invalidError.message.trim())
  7. }
  8. ```


  1. ```js
  2. try {
  3.   throw 'Missing file path.'
  4. } catch (invalidError) {
  5.   const normalizedError = BaseError.normalize(invalidError)
  6.   // This works: 'Missing file path.'
  7.   // `normalizedError` is a `BaseError` instance.
  8.   console.log(normalizedError.message.trim())
  9. }
  10. ```

🐞 Unknown errors


Handling known errors


Known errors should be handled in a try {} catch {} block and
That block should only cover the statement that might throw in order to prevent
catching other unrelated errors.


  1. ```js
  2. try {
  3.   return regExp.test(value)
  4. } catch (error) {
  5.   // Now an `InputError` instance
  6.   throw new InputError('Invalid regular expression:', { cause: error })
  7. }
  8. ```

Normalizing unknown errors


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)](#errorclassnormalizeerror-newerrorclass)
assigns the UnknownError class to those errors.

  1. ```js
  2. export const UnknownError = BaseError.subclass('UnknownError')
  3. ```


  1. ```js
  2. try {
  3.   return regExp.test(value)
  4. } catch (error) {
  5.   // Now an `UnknownError` instance
  6.   throw BaseError.normalize(error, UnknownError)
  7. }
  8. ```

Top-level error handler


Wrapping a module's main functions with
[BaseError.normalize(error, UnknownError)](#errorclassnormalizeerror-newerrorclass)
ensures every error being thrown is valid, applies
plugins, and has a class that is either
_known_ or [UnknownError](#-unknown-errors).

  1. ```js
  2. export const main = () => {
  3.   try {
  4.     // ...
  5.   } catch (error) {
  6.     throw BaseError.normalize(error, UnknownError)
  7.   }
  8. }
  9. ```

🔌 Plugins


List of plugins


Plugins extend modern-errors features. All available plugins are

Adding plugins


To use a plugin, please install it, then pass it to the
[plugins option](#optionsplugins).

  1. ```bash
  2. npm install modern-errors-{pluginName}
  3. ```


  1. ```js
  2. import ModernError from 'modern-errors'

  3. import modernErrorsBugs from 'modern-errors-bugs'
  4. import modernErrorsSerialize from 'modern-errors-serialize'

  5. export const BaseError = ModernError.subclass('BaseError', {
  6.   plugins: [modernErrorsBugs, modernErrorsSerialize],
  7. })
  8. // ...
  9. ```

Custom plugins


Please see the following documentation to create your own
plugin.

Plugin options


Most plugins can be configured with options. The option's name is the same as
the plugin.

  1. ```js
  2. const options = {
  3.   // `modern-errors-bugs` options
  4.   bugs: 'https://github.com/my-name/my-project/issues',
  5.   // `props` can be configured and modified like plugin options
  6.   props: { userId: 5 },
  7. }
  8. ```

Plugin options can apply to (in priority order):

- Any error: second argument to [ModernError.subclass()](#options-1)

  1. ```js
  2. export const BaseError = ModernError.subclass('BaseError', options)
  3. ```

- Any error of a specific class (and its subclasses): second argument to
  [ErrorClass.subclass()](#options-1)

  1. ```js
  2. export const InputError = BaseError.subclass('InputError', options)
  3. ```

- A specific error: second argument to [new ErrorClass()](#options-3)

  1. ```js
  2. throw new InputError('...', options)
  3. ```

- A plugin method call: last argument, passing only that plugin's options

  1. ```js
  2. ErrorClass[methodName](...args, options[pluginName])
  3. ```

  1. ```js
  2. error[methodName](...args, options[pluginName])
  3. ```

🔧 Custom logic


The [custom option](#optionscustom) can be used to provide an error class
with additional methods, constructor or properties.

<!-- eslint-disable no-param-reassign, fp/no-mutation,
     class-methods-use-this -->

  1. ```js
  2. export const InputError = BaseError.subclass('InputError', {
  3.   // The `class` must extend from the parent error class
  4.   custom: class extends BaseError {
  5.     // If a `constructor` is defined, its parameters must be (message, options)
  6.     constructor(message, options) {
  7.       message += message.endsWith('.') ? '' : '.'
  8.       super(message, options)
  9.     }

  10.     isUserInput() {
  11.       // ...
  12.     }
  13.   },
  14. })

  15. const error = new InputError('Wrong user name')
  16. console.log(error.message) // 'Wrong user name.'
  17. console.log(error.isUserInput())
  18. ```

🤓 TypeScript


Please see the following documentation for information
about TypeScript types.

API


ModernError


Top-level ErrorClass.

ErrorClass.subclass(name, options?)


name: string\
options: [ClassOptions?](#options)

Creates and returns a child ErrorClass.

options


options.props


_Type_: object


options.plugins


_Type_: [Plugin[]](#-plugins)

options.custom


_Type_: class extends ErrorClass {}

Custom class to add any methods,constructor or properties.

options.\*


Any plugin options can also be specified.

new ErrorClass(message, options?)


message: string\
options: [InstanceOptions?](#options-2)\
_Return value_: Error

options


options.props


_Type_: object


options.cause


_Type_: [any](#wrapped-errors)

Inner error being wrapped.

options.errors


_Type_: any[]

Array of errors being aggregated.

options.\*


Any plugin options can also be specified.

ErrorClass.normalize(error, NewErrorClass?)


error: Error | any\
NewErrorClass: subclass of ErrorClass\
_Return value_: Error

Normalizes invalid errors.

If the error's class is a subclass of ErrorClass, it is left as is.
Otherwise, it is [converted to NewErrorClass](#normalizing-unknown-errors),
which defaults to ErrorClass itself.

Modules


This framework brings together a collection of modules which can also be used
individually:

- [error-custom-class](https://github.com/ehmicky/error-custom-class): Create
  one error class
- [error-class-utils](https://github.com/ehmicky/error-class-utils): Utilities
  to properly create error classes
- [error-serializer](https://github.com/ehmicky/error-serializer): Convert
  errors to/from plain objects
- [normalize-exception](https://github.com/ehmicky/normalize-exception):
  Normalize exceptions/errors
- [is-error-instance](https://github.com/ehmicky/is-error-instance): Check if
  a value is an Error instance
- [merge-error-cause](https://github.com/ehmicky/merge-error-cause): Merge an
  error with its cause
- [set-error-class](https://github.com/ehmicky/set-error-class): Properly
  update an error's class
- [set-error-message](https://github.com/ehmicky/set-error-message): Properly
  update an error's message
- [wrap-error-message](https://github.com/ehmicky/wrap-error-message):
  Properly wrap an error's message
- [set-error-props](https://github.com/ehmicky/set-error-props): Properly
  update an error's properties
- [set-error-stack](https://github.com/ehmicky/set-error-stack): Properly
  update an error's stack
- [handle-cli-error](https://github.com/ehmicky/handle-cli-error): 💣 Error
  handler for CLI applications 💥
- [log-process-errors](https://github.com/ehmicky/log-process-errors): Show
  some ❤ to Node.js process errors
- [error-http-response](https://github.com/ehmicky/error-http-response):
  Create HTTP error responses
- [winston-error-format](https://github.com/ehmicky/winston-error-format): Log
  errors with Winston

Support


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.

Contributing


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!


ehmicky
ehmicky

💻 🎨 🤔 📖
Patrik Tomášik
Patrik Tomášik

🤔