Security News
Research
Supply Chain Attack on Rspack npm Packages Injects Cryptojacking Malware
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
A production-grade error creation and serialization library designed for Typescript
A production-grade error creation library designed for Typescript. Useful for direct printing of errors to a client or for internal development / logs.
Error
with additional methods added on.The basic Javascript Error
type is extremely bare bones - you can only specify a message.
In a production-level application, I've experienced the following use-cases:
new-error
was built with these use-cases in mind.
$ npm i new-error --save
The error registry is the fastest way to define and create errors.
// This is a working example
import { ErrorRegistry } from 'new-error'
// Define high level errors
// Do *not* assign a Typescript type to the object
// or IDE autocompletion will not work!
const errors = {
INTERNAL_SERVER_ERROR: {
/**
* The class name of the generated error
*/
className: 'InternalServerError',
/**
* A user-friendly code to show to a client.
*/
code: 'ERR_INT_500',
/**
* (optional) Protocol-specific status code, such as an HTTP status code. Used as the
* default if a Low Level Error status code is not specified or defined.
*/
statusCode: 500
}
}
// Define low-level errors
// Do *not* assign a Typescript type to the object
// or IDE autocompletion will not work!
const errorCodes = {
// 'type' of error
DATABASE_FAILURE: {
/**
* Full description of the error. sprintf() flags can be applied
* to customize it.
* @see https://www.npmjs.com/package/sprintf-js
*/
message: 'There was a database failure, SQL err code %s',
/**
* (optional) A user-friendly code to show to a client.
*/
subCode: 'DB_0001',
/**
* (optional) Protocol-specific status code, such as an HTTP status code.
*/
statusCode: 500
}
}
// Create the error registry by registering your errors and codes
// you will want to memoize this as you will be using the
// reference throughout your application
const errRegistry = new ErrorRegistry(errors, errorCodes)
// Create an instance of InternalServerError
// Typescript autocomplete should show the available definitions as you type the error names
// and type check will ensure that the values are valid
const err = errRegistry.newError('INTERNAL_SERVER_ERROR', 'DATABASE_FAILURE')
.setErrorId('err-1234')
.formatMessage('SQL_1234')
console.log(err.toJSON())
Produces:
(You can omit fields you do not need - see usage section below.)
{
errId: 'err-1234',
name: 'InternalServerError',
code: 'ERR_INT_500',
message: 'There was a database failure, SQL err code SQL_1234',
type: 'DATABASE_FAILURE',
subCode: 'DB_0001',
statusCode: 500,
meta: {},
stack: 'InternalServerError: There was a database failure, SQL err code %s\n' +
' at ErrorRegistry.newError (new-error/src/ErrorRegistry.ts:128:12)\n' +
' at Object.<anonymous> (new-error/src/test.ts:55:25)\n' +
' at Module._compile (internal/modules/cjs/loader.js:1158:30)\n' +
' at Module._compile (new-error/node_modules/source-map-support/source-map-support.js:541:25)\n' +
' at Module.m._compile (/private/var/folders/mx/b54hc2lj3fbfsndkv4xmz8d80000gn/T/ts-node-dev-hook-20649714243977457.js:57:25)\n' +
' at Module._extensions..js (internal/modules/cjs/loader.js:1178:10)\n' +
' at require.extensions.<computed> (/private/var/folders/mx/b54hc2lj3fbfsndkv4xmz8d80000gn/T/ts-node-dev-hook-20649714243977457.js:59:14)\n' +
' at Object.nodeDevHook [as .ts] (new-error/node_modules/ts-node-dev/lib/hook.js:61:7)\n' +
' at Module.load (internal/modules/cjs/loader.js:1002:32)\n' +
' at Function.Module._load (internal/modules/cjs/loader.js:901:14)'
}
You can create concrete error classes by extending the BaseRegistryError
class, which
extends the BaseError
class.
The registry example can be also written as:
import { BaseRegistryError, LowLevelError } from 'new-error'
class InternalServerError extends BaseRegistryError {
constructor (errDef: LowLevelError) {
super({
code: 'ERR_INT_500',
statusCode: 500
}, errDef)
}
}
const err = new InternalServerError({
type: 'DATABASE_FAILURE',
message: 'There was a database failure, SQL err code %s',
subCode: 'DB_0001',
statusCode: 500
})
console.log(err.formatMessage('SQL_1234').toJSON())
If you want a native-style Error
, you can use BaseError
.
The registry example can be written as:
import { BaseError } from 'new-error'
class InternalServerError extends BaseError {}
const err = new InternalServerError('There was a database failure, SQL err code %s')
// calling these methods are optional
.withErrorType('DATABASE_FAILURE')
.withErrorCode('ERR_INT_500')
.withErrorSubCode('DB_0001')
.withStatusCode(500)
console.log(err.formatMessage('SQL_1234').toJSON())
import express from 'express'
import { ErrorRegistry, BaseError } from 'new-error'
const app = express()
const port = 3000
const errors = {
INTERNAL_SERVER_ERROR: {
className: 'InternalServerError',
code: 'ERR_INT_500',
statusCode: 500
}
}
const errorCodes = {
DATABASE_FAILURE: {
message: 'There was a database failure.',
subCode: 'DB_0001',
statusCode: 500
}
}
const errRegistry = new ErrorRegistry(errors, errorCodes)
// middleware definition
app.get('/', async (req, res, next) => {
try {
// simulate a failure
throw new Error('SQL issue')
} catch (e) {
const err = errRegistry.newError('INTERNAL_SERVER_ERROR', 'DATABASE_FAILURE')
err.causedBy(err)
// errors must be passed to next()
// to be caught when using an async middleware
return next(err)
}
})
// catch errors
app.use((err, req, res, next) => {
// error was sent from middleware
if (err) {
// check if the error is a generated one
if (err instanceof BaseError) {
// generate an error id
// you'll want to use a library like 'nanoid' instead
// this is just an example
err.withErrorId(Math.random().toString(36).slice(2))
// log the error
// the "null, 2" options formats the error into a readable structure
console.error(JSON.stringify(err.toJSON(), null, 2))
// get the status code, if the status code is not defined, default to 500
res.status(err.getStatusCode() ?? 500)
// spit out the error to the client
return res.json({
err: err.toJSONSafe()
})
}
// You'll need to modify code below to best fit your use-case
// err.message could potentially expose system internals
return res.json({
err: {
message: err.message
}
})
}
// no error, proceed
next()
})
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))
If you visit http://localhost:3000
, you'll get a 500 status code, and the following response:
{"err": {"errId": "xd0v1szkziq", code":"ERR_INT_500","subCode":"DB_0001","statusCode":500,"meta":{}}}
The ErrorRegistry
is responsible for the registration and creation of errors.
Errors generated by the registry extends BaseError
.
Method: ErrorRegistry#newError(highLevelErrorName, LowLevelErrorName)
This is the method you should generally use as you are forced to use your well-defined high and low level error definitions. This allows for consistency in how errors are defined and thrown.
// Creates an InternalServerError error with a DATABASE_FAILURE code and corresponding
// message and status code
const err = errRegistry.newError('INTERNAL_SERVER_ERROR', 'DATABASE_FAILURE')
Method: ErrorRegistry#newBareError(highLevelErrorName, message)
This method does not include a low level error code, and allows direct specification of an error message.
// Creates an InternalServerError error with a custom message
const err = errRegistry.newBareError('INTERNAL_SERVER_ERROR', 'An internal server error has occured.')
instanceOf
/ comparisonsMethod: ErrorRegistry#instanceOf(classInstance, highLevelErrorName)
Performs an instanceof
operation against a custom error.
// creates an InternalServerError error instance
const err = errRegistry.newError('INTERNAL_SERVER_ERROR', 'DATABASE_FAILURE')
if (errRegistry.instanceOf(err, 'INTERNAL_SERVER_ERROR')) {
// resolves to true since err is an InternalServerError instance
}
instanceof
You can also check if the error is custom-built using this check:
import { BaseError } from 'new-error'
function handleError(err) {
if (err instanceof BaseError) {
// err is a custom error
}
}
Except for the getter and serialization methods, all other methods are chainable.
Generated errors extend the BaseError
class, which supplies the manipulation methods.
BaseError#getErrorId()
BaseError#getErrorName()
BaseError#getCode()
BaseError#getErrorType()
BaseError#getSubCode()
BaseError#getStatusCode()
BaseError#getCausedBy()
BaseError#getMetadata()
BaseError#getSafeMetadata()
If you use the registry, you should not need to us these setters as the registry sets the values already.
BaseError#withErrorType(type: string): this
BaseError#withErrorCode(code: string | number): this
BaseError#withErrorSubCode(code: string | number): this
Method: BaseError#withErrorId(errId: string)
Attaches an id to the error. Useful if you want to display an error id to a client / end-user and want to cross-reference that id in an internal logging system for easier troubleshooting.
For example, you might want to use nanoid
to generate ids for errors.
import { nanoid } from 'nanoid'
err.withErrorId(nanoid())
// In your logging system, log the error, which will include the error id
logger.error(err.toJSON())
// expose the error to the client via err.toJSONSafe() or err.getErrorId(), which
// will also include the error id - an end-user can reference this id to
// support for troubleshooting
Method: BaseError#causedBy(err: any)
You can attach another error to the error.
const externalError = new Error('Some thrown error')
err.causedBy(externalError)
Method: BaseError#formatMessage(...formatParams)
See the sprintf-js
package for usage.
// specify the database specific error code
// Transforms the message to:
// 'There was a database failure, SQL err code %s' ->
// 'There was a database failure, SQL err code SQL_ERR_1234',
err.formatMessage('SQL_ERR_1234')
Method: BaseError#withSafeMetadata(data = {})
Safe metadata would be any kind of data that you would be ok with exposing to a client, like an HTTP response.
err.withSafeMetadata({
errorId: 'err-12345',
moreData: 1234
})
// can be chained to append more data
.withSafeMetadata({
requestId: 'req-12345'
})
This can also be written as:
err.withSafeMetadata({
errorId: 'err-12345',
moreData: 1234
})
// This will append requestId to the metadata
err.withSafeMetadata({
requestId: 'req-12345'
})
Method: BaseError#withMetadata(data = {})
Internal metadata would be any kind of data that you would not be ok with exposing to a client, but would be useful for internal development / logging purposes.
err.withMetadata({
email: 'test@test.com'
})
// can be chained to append more data
.withMetadata({
userId: 'user-abcd'
})
Method: BaseError#toJSONSafe(fieldsToOmit = [])
Generates output that would be safe for client consumption.
name
message
causedBy
type
BaseError#withMetadata()
err.withSafeMetadata({
errorId: 'err-12345',
requestId: 'req-12345'
})
// you can remove additional fields by specifying property names in an array
//.toJSONSafe(['code']) removes the code field from output
.toJSONSafe()
Produces:
{
code: 'ERR_INT_500',
subCode: 'DB_0001',
statusCode: 500,
meta: { errorId: 'err-12345', requestId: 'req-12345' }
}
Method: BaseError#toJSON(fieldsToOmit = [])
Generates output that would be suitable for internal use.
name
type
message
causedBy
BaseError#withMetadata()
and BaseError#withSafeMetadata()
is includederr.withSafeMetadata({
errorId: 'err-12345',
}).withMetadata({
email: 'test@test.com'
})
// you can remove additional fields by specifying property names in an array
//.toJSON(['code', 'statusCode']) removes the code and statusCode field from output
.toJSON()
Produces:
{
name: 'InternalServerError',
code: 'ERR_INT_500',
message: 'There was a database failure, SQL err code %s',
type: 'DATABASE_FAILURE',
subCode: 'DB_0001',
statusCode: 500,
meta: { errorId: 'err-12345', requestId: 'req-12345' },
stack: 'InternalServerError: There was a database failure, SQL err code %s\n' +
' at ErrorRegistry.newError (new-error/src/ErrorRegistry.ts:128:12)\n' +
' at Object.<anonymous> (new-error/src/test.ts:55:25)\n' +
' at Module._compile (internal/modules/cjs/loader.js:1158:30)\n' +
' at Module._compile (new-error/node_modules/source-map-support/source-map-support.js:541:25)\n' +
' at Module.m._compile (/private/var/folders/mx/b54hc2lj3fbfsndkv4xmz8d80000gn/T/ts-node-dev-hook-17091160954051898.js:57:25)\n' +
' at Module._extensions..js (internal/modules/cjs/loader.js:1178:10)\n' +
' at require.extensions.<computed> (/private/var/folders/mx/b54hc2lj3fbfsndkv4xmz8d80000gn/T/ts-node-dev-hook-17091160954051898.js:59:14)\n' +
' at Object.nodeDevHook [as .ts] (new-error/node_modules/ts-node-dev/lib/hook.js:61:7)\n' +
' at Module.load (internal/modules/cjs/loader.js:1002:32)\n' +
' at Function.Module._load (internal/modules/cjs/loader.js:901:14)'
}
1.0.12 - Wed Jun 03 2020 03:54:55
Contributor: Theo Gravity
Fix npm badge from http->https to fix render issues on the npm page
FAQs
A production-grade error creation and serialization library designed for Typescript
The npm package new-error receives a total of 66 weekly downloads. As such, new-error popularity was classified as not popular.
We found that new-error demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.
Security News
Sonar’s acquisition of Tidelift highlights a growing industry shift toward sustainable open source funding, addressing maintainer burnout and critical software dependencies.