Skip to content

@jaypie/errors

Prerequisites: npm install jaypie or npm install @jaypie/errors

@jaypie/errors provides typed error classes that map to HTTP status codes and format as JSON:API errors.

Terminal window
npm install jaypie
# or
npm install @jaypie/errors
Class Status Use Case
BadRequestError 400 Invalid input
UnauthorizedError 401 Authentication required
ForbiddenError 403 Permission denied
NotFoundError 404 Resource not found
MethodNotAllowedError 405 Wrong HTTP method
GoneError 410 Resource deleted
TeapotError 418 Easter egg
TooManyRequestsError 429 Rate limited
InternalError 500 Server error
ConfigurationError 500 Missing config
NotImplementedError 400 Feature not built
BadGatewayError 502 Upstream error
UnavailableError 503 Service down
GatewayTimeoutError 504 Upstream timeout
RejectedError 403 Request rejected
Export Purpose
isJaypieError Type guard for Jaypie errors
jaypieErrorFromStatus Create error from status code

Both syntaxes work:

import { BadRequestError, NotFoundError } from "jaypie";
// Function call (preferred)
throw BadRequestError("Missing required field");
// Constructor
throw new BadRequestError("Missing required field");

Every error class takes cause as its second argument:

import { ConfigurationError } from "jaypie";
try {
await getSecret(name);
} catch (error) {
throw new ConfigurationError("Could not get or parse secret", {
cause: error,
});
}

The base JaypieError takes status and title alongside cause. The named classes take cause only, because status and title identify the class.

const error = NotFoundError("User not found");
error.status; // 404
error.title; // "Not Found"
error.name; // "JaypieError"
error.message; // "User not found"
error.detail; // "User not found"
error.isJaypieError; // true

Every Jaypie error carries the name JaypieError. Use instanceof, isJaypieError(error), or error.status to discriminate, not error.name.

const error = BadRequestError("Invalid email");
error.body();

Returns:

{
"errors": [
{
"status": 400,
"title": "Bad Request",
"detail": "Invalid email"
}
]
}

instanceof identifies a specific error class:

import { NotFoundError } from "jaypie";
try {
await getUser(id);
} catch (error) {
if (error instanceof NotFoundError) {
return null;
}
throw error;
}

The check holds across the ESM and CommonJS builds, so an error raised inside a CommonJS package matches in an ESM package, and it holds when two copies of @jaypie/errors are installed. Class identity does not: compare with instanceof, never error.constructor === NotFoundError.

import { isJaypieError, InternalError } from "jaypie";
try {
await riskyOperation();
} catch (error) {
if (isJaypieError(error)) {
return res.status(error.status).json(error.body());
}
throw InternalError();
}
import { jaypieErrorFromStatus } from "jaypie";
const error = jaypieErrorFromStatus(404, "Not found");
// Returns NotFoundError
Scenario Error
Missing required field BadRequestError
Invalid format BadRequestError
No auth token UnauthorizedError
Invalid auth UnauthorizedError
No permission ForbiddenError
Resource missing NotFoundError
Wrong HTTP method MethodNotAllowedError
Resource deleted GoneError
Rate limited TooManyRequestsError
Scenario Error
Missing env var ConfigurationError
External API failed BadGatewayError
External API timeout GatewayTimeoutError
Maintenance mode UnavailableError
Unexpected state InternalError
import { BadRequestError } from "jaypie";
function validateEmail(email) {
if (!email) {
throw BadRequestError("Email is required");
}
if (!email.includes("@")) {
throw BadRequestError("Invalid email format");
}
}
import { NotFoundError } from "jaypie";
async function getUser(id) {
const user = await db.users.findById(id);
if (!user) {
throw NotFoundError("User not found");
}
return user;
}
import { BadGatewayError, log } from "jaypie";
async function callPaymentService(data) {
try {
return await paymentApi.charge(data);
} catch (error) {
log.error("Payment service failed");
log.var({ error: error.message });
throw BadGatewayError();
}
}
import { ConfigurationError } from "jaypie";
function getDbUri() {
const uri = process.env.MONGODB_URI;
if (!uri) {
throw ConfigurationError("MONGODB_URI not configured");
}
return uri;
}
import { matchers } from "@jaypie/testkit";
expect.extend(matchers);
it("throws BadRequestError for invalid input", () => {
expect(() => validateEmail(null)).toThrowBadRequestError();
});
it("throws any Jaypie error", () => {
expect(() => riskyOperation()).toThrowJaypieError();
});
  • toThrowJaypieError()
  • toThrowBadRequestError()
  • toThrowUnauthorizedError()
  • toThrowForbiddenError()
  • toThrowNotFoundError()
  • toThrowInternalError()
  • toThrowConfigurationError()
  • toThrowBadGatewayError()
  • toThrowUnavailableError()