npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

http-fault

v1.0.2

Published

Lightweight, extensible HTTP exceptions with proper status codes and framework-agnostic error handling for Node.js.

Readme

http-fault

A lightweight, framework-agnostic TypeScript library for creating and handling structured HTTP exceptions.

Features

  • Full 4xx/5xx coverage — one class per HTTP status code
  • Structured responses — consistent JSON shape across every error
  • Framework agnostic — works with Express, Fastify, Koa, Hono, raw node:http, or anything built on it
  • Safe by default — 5xx details are hidden from clients unless explicitly exposed
  • Extensible — create domain-specific exceptions by extending any class
  • Zero dependencies — only uses node:http built-in types

Installation

npm install http-fault

File Structure

http.exception.ts        # Abstract base class
http.exceptions.ts       # All 4xx and 5xx exception classes
exception-filter.ts      # Framework-agnostic error handler

Quick Start

import { NotFoundException } from "http-fault";

throw new NotFoundException({ message: "User not found." });

Every error is serialized to a consistent JSON response:

{
  "status": 404,
  "error": "Not Found",
  "message": "User not found.",
  "code": undefined,
  "details": undefined,
  "path": "/users/99",
  "timestamp": "2026-02-28T12:00:00.000Z"
}

HttpException Base Class

All exceptions extend HttpException, which extends Error.

abstract class HttpException extends Error {
  readonly status: number; // HTTP status code
  readonly error: string; // Standard HTTP error name
  readonly message: string; // Human-readable description (inherited from Error)
  readonly code?: string; // Optional machine-readable error code
  readonly details?: unknown; // Optional structured payload
  readonly expose: boolean; // Whether details are safe to send to the client
  readonly cause?: Error; // Optional originating error
}

Constructor Options

All exception constructors accept an optional options object:

| Option | Type | Description | | --------- | --------- | -------------------------------------------------------------------------------------------- | | message | string | Human-readable description. Defaults to the HTTP error name. | | code | string | Machine-readable error code (e.g. "USER_NOT_FOUND"). | | details | unknown | Structured payload (e.g. validation errors). | | expose | boolean | Whether message and details reach the client. Auto-set: true for 4xx, false for 5xx. | | cause | Error | The originating error, for error chaining. |


Built-in Exceptions

4xx Client Errors

| Class | Status | | -------------------------------------- | ------ | | BadRequestException | 400 | | UnauthorizedException | 401 | | PaymentRequiredException | 402 | | ForbiddenException | 403 | | NotFoundException | 404 | | MethodNotAllowedException | 405 | | NotAcceptableException | 406 | | ProxyAuthenticationRequiredException | 407 | | RequestTimeoutException | 408 | | ConflictException | 409 | | GoneException | 410 | | LengthRequiredException | 411 | | PreconditionFailedException | 412 | | ContentTooLargeException | 413 | | URITooLongException | 414 | | UnsupportedMediaTypeException | 415 | | RangeNotSatisfiableException | 416 | | ExpectationFailedException | 417 | | ImATeapotException | 418 | | MisdirectedRequestException | 421 | | UnprocessableEntityException | 422 | | LockedException | 423 | | FailedDependencyException | 424 | | TooEarlyException | 425 | | UpgradeRequiredException | 426 | | PreconditionRequiredException | 428 | | TooManyRequestsException | 429 | | RequestHeaderFieldsTooLargeException | 431 | | UnavailableForLegalReasonsException | 451 |

5xx Server Errors

| Class | Status | | ---------------------------------------- | ------ | | InternalServerErrorException | 500 | | NotImplementedException | 501 | | BadGatewayException | 502 | | ServiceUnavailableException | 503 | | GatewayTimeoutException | 504 | | HTTPVersionNotSupportedException | 505 | | VariantAlsoNegotiatesException | 506 | | InsufficientStorageException | 507 | | LoopDetectedException | 508 | | NotExtendedException | 510 | | NetworkAuthenticationRequiredException | 511 |


Throwing Exceptions

import {
  NotFoundException,
  ForbiddenException,
  UnprocessableEntityException,
  ConflictException,
} from "http-fault";

// Minimal — just a status
throw new NotFoundException();

// With a message
throw new NotFoundException({ message: "User 42 not found." });

// With a machine-readable code
throw new ForbiddenException({
  message: "Only admins can perform this action.",
  code: "INSUFFICIENT_ROLE",
});

// With structured details (great for validation errors)
throw new UnprocessableEntityException({
  message: "Validation failed.",
  code: "VALIDATION_FAILED",
  details: {
    email: ["Must be a valid email address."],
    age: ["Must be at least 18."],
  },
});

// With error chaining
try {
  await db.insert(user);
} catch (cause) {
  throw new ConflictException({
    message: "A user with this email already exists.",
    code: "EMAIL_TAKEN",
    cause,
  });
}

Custom Exceptions

Extend any built-in class to create reusable domain exceptions.

import {
  UnauthorizedException,
  TooManyRequestsException,
  HttpException,
} from "http-fault";

// Fix the code and default message for a specific auth scenario
class TokenExpiredException extends UnauthorizedException {
  constructor(cause?: Error) {
    super({
      message: "Your session has expired. Please sign in again.",
      code: "TOKEN_EXPIRED",
      cause,
    });
  }
}

// Carry typed runtime data in details
class QuotaExceededException extends TooManyRequestsException {
  constructor(used: number, limit: number) {
    super({
      message: `Daily quota exceeded (${used}/${limit}).`,
      code: "QUOTA_EXCEEDED",
      details: { used, limit },
    });
  }
}

// Build directly on HttpException for full control
class MaintenanceModeException extends HttpException {
  constructor(until: string) {
    super(503, "Service Unavailable", {
      message: "The service is under scheduled maintenance.",
      code: "MAINTENANCE_MODE",
      details: { until },
      expose: true, // safe to surface despite being 5xx
    });
  }
}

// Usage
throw new TokenExpiredException();
throw new QuotaExceededException(1000, 1000);
throw new MaintenanceModeException("2026-03-01T06:00:00Z");

ExceptionFilter

The ExceptionFilter converts any thrown value into a structured JSON response. It handles both HttpException instances and unexpected raw errors.

API

// One-off call
ExceptionFilter(err, req, res, options?)

// Pre-configured factory — bind options once, reuse everywhere
const handleError = createExceptionFilter(options?)
handleError(err, req, res)

Options

| Option | Type | Description | | ------------------ | ------------------------- | ----------------------------------------------------------------------------- | | onError | (err, response) => void | Custom logger. Called for all errors if provided, or only 5xx by default. | | resolvePath | (req) => string | Override how the request path is extracted. Defaults to req.url ?? "/". | | forceHideDetails | boolean | Always hide message and details, regardless of expose. Useful in tests. |

Integrations

Raw Node.js

import http from "node:http";
import { ExceptionFilter } from "http-fault";

http
  .createServer(async (req, res) => {
    try {
      await router(req, res);
    } catch (err) {
      ExceptionFilter(err, req, res);
    }
  })
  .listen(3000);

Express

import express from "express";
import { ExceptionFilter } from "http-fault";

const app = express();

app.get("/users/:id", (req, res) => {
  throw new NotFoundException({ message: `User ${req.params.id} not found.` });
});

// Register last
app.use((err, req, res, _next) => ExceptionFilter(err, req, res));

Fastify

import Fastify from "fastify";
import { ExceptionFilter } from "http-fault";

const fastify = Fastify();

fastify.setErrorHandler((err, req, reply) => {
  ExceptionFilter(err, req.raw, reply.raw);
});

Koa

import Koa from "koa";
import { ExceptionFilter } from "http-fault";

const app = new Koa();

app.on("error", (err, ctx) => {
  ExceptionFilter(err, ctx.req, ctx.res);
});

Custom Logger

import pino from "pino";
import { createExceptionFilter } from "http-fault";

const logger = pino();

export const handleError = createExceptionFilter({
  onError: (err, response) => {
    if (response.status >= 500) {
      logger.error({ err, response }, "Unhandled server error");
    } else {
      logger.warn({ response }, "Client error");
    }
  },
});

// app.use((err, req, res, _next) => handleError(err, req, res));

The expose Flag

This flag controls whether message and details are sent to the client.

| Scenario | expose default | What the client sees | | ---------------- | ---------------- | --------------------------------- | | 4xx exception | true | Full message and details | | 5xx exception | false | Only the standard HTTP error name | | Custom exception | Your choice | Depends on expose option |

// Client receives the full message and details (expose: true by default for 4xx)
throw new UnprocessableEntityException({
  message: "Email is invalid.",
  details: { field: "email" },
});
// → { message: "Email is invalid.", details: { field: "email" }, ... }

// Client receives only "Internal Server Error" (expose: false by default for 5xx)
throw new InternalServerErrorException({
  message: "Database connection lost at 192.168.1.5:5432",
});
// → { message: "Internal Server Error", details: undefined, ... }

// Force-expose a 5xx (use with care)
throw new ServiceUnavailableException({
  message: "Back online at 03:00 UTC.",
  expose: true,
});
// → { message: "Back online at 03:00 UTC.", ... }

Error Response Shape

Every error response follows this structure:

{
  status: number       // HTTP status code
  error: string        // Standard HTTP error name
  message: string      // Human-readable description (gated by expose)
  code?: string        // Machine-readable code (gated by expose)
  details?: unknown    // Structured payload (gated by expose)
  path: string         // Request path
  timestamp: string    // ISO 8601 timestamp
}

Testing

serializeException is a pure function — no server required.

import { serializeException, NotFoundException } from "http-fault";

it("returns 404 with message and code", () => {
  const err = new NotFoundException({
    message: "Post not found.",
    code: "POST_NOT_FOUND",
  });

  const result = serializeException(err, "/posts/99");

  expect(result).toMatchObject({
    status: 404,
    error: "Not Found",
    message: "Post not found.",
    code: "POST_NOT_FOUND",
    path: "/posts/99",
  });
});

it("hides details when forceHideDetails is true", () => {
  const err = new NotFoundException({ message: "Hidden." });
  const result = serializeException(err, "/", true);

  expect(result.message).toBe("Not Found"); // message hidden
  expect(result.details).toBeUndefined();
});

it("maps unknown errors to 500", () => {
  const result = serializeException(new Error("boom"), "/");
  expect(result.status).toBe(500);
});

License

MIT