Make application errors easier to handle
npm install @guilhermemj/app-error
Simply throw an AppError
like you would with any normal Error
. You can, however, pass a second argument with some options to customize error context and meta info.
var AppError = require("@guilhermemj/app-error");
// Basic usage
throw new AppError("Ops, something went wrong!");
throw new AppError("Resource not found!", {
httpStatusCode: 404,
code: "ERR_NOT_FOUND",
});
// Full customization
throw new AppError("Resource version differs from database", {
httpStatusCode: 409,
code: "ERR_RESOURCE_CONFLICT",
level: "warn",
meta: {
user: "@johndoe",
resourceId: "123456",
informedData: {/* ... */},
expectedData: {/* ... */},
},
});
Where AppError
really shines is when catching and handling thrown errors. Besides the mentioned options, each error has an unique id
and date
object making it very easy to log errors and normalize API responses.
Here, we simulate an express controller response with a totally arbitrary logger function to show the difference between handling an AppError
and other common errors:
var AppError = require("@guilhermemj/app-error");
try {
// Do some code...
} catch (error) {
if (error instanceof AppError) {
logger[error.level](error.message, error.code, {
// `meta` may have important info about error context
meta: error.meta,
date: error.date,
uuid: error.id,
});
res.status(error.httpStatusCode).json({
message: error.message,
code: error.code,
uuid: error.id,
});
} else {
// Something REALLY bad happened! But we only have a message to log :(
logger.error(error.message, "ERR_INTERNAL_ERROR", { date: new Date() });
res.status(500).json({
message: error.message,
code: "ERR_INTERNAL_ERROR",
});
}
}
Please note how we are able to log much richier information with AppError
and how easy it is to identify it's instances.
In order to improve code DRYness it's recommended to create some presets for common errors in your application. Typescript users have the AppErrorOptions
interface available for this purpose.
To avoid destructuring and other workarounds when using presets, AppError
constructor accepts a third parameter to be used as the meta
object option.
import AppError, { AppErrorOptions } from "@guilhermemj/app-error";
const ERR_INVALID_CONFIG: AppErrorOptions = {
httpStatusCode: 400,
code: "ERR_INVALID_CONFIG",
level: "debug",
};
if (!isValidEmail(payload.email)) {
throw new AppError(`"${payload.email}" is not a valid email`, ERR_INVALID_CONFIG, { payload, invalidField: "email" });
}
if (!payload.password) {
throw new AppError("Password is required", ERR_INVALID_CONFIG, { payload, invalidField: "password" });
}
- Type:
number
- Default:
500
Status code to be used on API responses. (List of existing codes)
- Type:
string
- Default:
"ERR_UNCAUGHT_EXCEPTION"
Error code. Can be used to identify errors of the same type.
- Type:
"error" | "warn" | "info" | "debug"
- Default:
"error"
Error severity level. Useful for logging.
- Type:
Object
- Default:
{}
Extra info about the error to be retrieved later.
It's very common to enrich logs and API responses with some execution context. This is where you should store it.