Common utilities for error handling within Backstage with structured error classes and serialization functions
63
Structured error classes that map to common HTTP status codes and business logic scenarios. Each error extends CustomErrorBase and supports cause chaining for comprehensive debugging and error context preservation.
Base class that custom Error classes can inherit from, providing cause chaining and improved error context.
/**
* A base class that custom Error classes can inherit from.
* Provides cause chaining and enhanced error messaging.
*/
class CustomErrorBase extends Error {
/** An inner error that caused this error to be thrown, if any */
readonly cause?: Error | undefined;
/**
* @param message - Optional error message
* @param cause - Optional underlying cause error
*/
constructor(message?: string, cause?: Error | unknown);
}Usage Examples:
import { CustomErrorBase } from "@backstage/errors";
// Create custom error types
class DatabaseError extends CustomErrorBase {
name = 'DatabaseError' as const;
}
// Use with cause chaining
try {
// Some database operation
} catch (err) {
throw new DatabaseError("Failed to save user", err);
}
// Error message includes cause information automatically
// "Failed to save user; caused by ConnectionError: Database unavailable"The given inputs are malformed and cannot be processed. Typically maps to HTTP 400 Bad Request.
/**
* The given inputs are malformed and cannot be processed.
* Commonly used for validation failures and malformed requests.
*/
class InputError extends CustomErrorBase {
name: 'InputError';
}Usage Examples:
import { InputError } from "@backstage/errors";
// Validate user input
function createUser(userData: unknown) {
if (!userData || typeof userData !== 'object') {
throw new InputError("User data must be a valid object");
}
const user = userData as any;
if (!user.email || !user.email.includes('@')) {
throw new InputError("Valid email address is required");
}
}The request requires authentication, which was not properly supplied. Maps to HTTP 401 Unauthorized.
/**
* The request requires authentication, which was not properly supplied.
* Used when credentials are missing, invalid, or expired.
*/
class AuthenticationError extends CustomErrorBase {
name: 'AuthenticationError';
}The authenticated caller is not allowed to perform this request. Maps to HTTP 403 Forbidden.
/**
* The authenticated caller is not allowed to perform this request.
* Used when user lacks permissions for the requested operation.
*/
class NotAllowedError extends CustomErrorBase {
name: 'NotAllowedError';
}Usage Examples:
import { AuthenticationError, NotAllowedError } from "@backstage/errors";
function validateUserAccess(token: string, resource: string) {
if (!token) {
throw new AuthenticationError("Authentication token is required");
}
const user = decodeToken(token);
if (!user) {
throw new AuthenticationError("Invalid or expired token");
}
if (!user.permissions.includes(resource)) {
throw new NotAllowedError(`Access denied to resource: ${resource}`);
}
}The requested resource could not be found. Maps to HTTP 404 Not Found.
/**
* The requested resource could not be found.
* Used when an entity with a given ID does not exist.
*/
class NotFoundError extends CustomErrorBase {
name: 'NotFoundError';
}The request could not complete due to a conflict in the current state of the resource. Maps to HTTP 409 Conflict.
/**
* The request could not complete due to a conflict in the current state of the resource.
* Used when operations conflict with existing data or state.
*/
class ConflictError extends CustomErrorBase {
name: 'ConflictError';
}The requested resource has not changed since last request. Maps to HTTP 304 Not Modified.
/**
* The requested resource has not changed since last request.
* Used for caching scenarios and conditional requests.
*/
class NotModifiedError extends CustomErrorBase {
name: 'NotModifiedError';
}Usage Examples:
import { NotFoundError, ConflictError, NotModifiedError } from "@backstage/errors";
class UserService {
findUser(id: string) {
const user = database.find(id);
if (!user) {
throw new NotFoundError(`User with ID '${id}' not found`);
}
return user;
}
createUser(userData: UserData) {
if (database.exists(userData.email)) {
throw new ConflictError(`User with email '${userData.email}' already exists`);
}
return database.create(userData);
}
updateUser(id: string, data: UserData, ifModifiedSince?: Date) {
const user = this.findUser(id);
if (ifModifiedSince && user.lastModified <= ifModifiedSince) {
throw new NotModifiedError("User has not been modified since last request");
}
return database.update(id, data);
}
}The server does not support the functionality required to fulfill the request. Maps to HTTP 501 Not Implemented.
/**
* The server does not support the functionality required to fulfill the request.
* Used when features are not yet implemented or not supported.
*/
class NotImplementedError extends CustomErrorBase {
name: 'NotImplementedError';
}The server is not ready to handle the request. Maps to HTTP 503 Service Unavailable.
/**
* The server is not ready to handle the request.
* Used when services are temporarily down or overloaded.
*/
class ServiceUnavailableError extends CustomErrorBase {
name: 'ServiceUnavailableError';
}Usage Examples:
import { NotImplementedError, ServiceUnavailableError } from "@backstage/errors";
class ApiHandler {
handleAdvancedFeature() {
throw new NotImplementedError("Advanced feature is planned for v2.0");
}
processRequest() {
if (system.isMaintenanceMode()) {
throw new ServiceUnavailableError("System is under maintenance");
}
if (system.isOverloaded()) {
throw new ServiceUnavailableError("Service temporarily overloaded, please retry");
}
}
}An error that forwards an underlying cause with additional context in the message. The name property is inherited from the cause.
/**
* An error that forwards an underlying cause with additional context in the message.
* The name property is inherited from the cause if possible, otherwise set to 'Error'.
*/
class ForwardedError extends CustomErrorBase {
/**
* @param message - Additional context message
* @param cause - The underlying error being forwarded
*/
constructor(message: string, cause: Error | unknown);
}Usage Examples:
import { ForwardedError } from "@backstage/errors";
async function processUserData(userId: string) {
try {
const userData = await fetchUserData(userId);
return await validateAndTransform(userData);
} catch (err) {
// Forward the error with additional context
throw new ForwardedError(
`Failed to process user data for user ${userId}`,
err
);
}
}
// The forwarded error preserves the original error's name
// If original was ValidationError, the ForwardedError.name will be 'ValidationError'An error thrown as the result of a failed server request, with automatic parsing of error response bodies.
/**
* An error thrown as the result of a failed server request.
* The server is expected to respond on the ErrorResponseBody format.
*/
class ResponseError extends Error {
/** The actual response, as seen by the client (body consumed) */
readonly response: ConsumedResponse;
/** The parsed JSON error body, as sent by the server */
readonly body: ErrorResponseBody;
/** The Error cause, as seen by the remote server */
readonly cause: Error;
readonly statusCode: number;
readonly statusText: string;
/**
* Constructs a ResponseError based on a failed response.
* Assumes response has already been checked to be not ok.
* This function consumes the body of the response.
*/
static fromResponse(
response: ConsumedResponse & { text(): Promise<string> }
): Promise<ResponseError>;
}Usage Examples:
import { ResponseError } from "@backstage/errors";
async function apiCall(url: string) {
const response = await fetch(url);
if (!response.ok) {
throw await ResponseError.fromResponse(response);
}
return response.json();
}
// Handle response errors
try {
const data = await apiCall('/api/users/123');
} catch (err) {
if (err instanceof ResponseError) {
console.log(`API Error: ${err.statusCode} ${err.statusText}`);
console.log(`Server Error: ${err.cause.name} - ${err.cause.message}`);
console.log(`Request URL: ${err.body.request?.url}`);
}
}Install with Tessl CLI
npx tessl i tessl/npm-backstage--errorsevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10