Handle errors in a simple, stable, consistent way
Handle errors in a simple, stable, consistent way
Simple patterns to:
Stability:
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 logicCreate 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-errorsIf 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) // trueconst InputError = BaseError.subclass('InputError', {
props: { isUserError: true },
})
const error = new InputError('...')
console.log(error.isUserError) // trueconst error = new InputError('...', { props: { isUserError: true } })
console.log(error.isUserError) // trueError 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 optio
No open issues yet, or sync has not completed.