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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@wymp/http-errors

v4.0.0

Published

A set of extensions to the standard Error class that add features that make it easy to convert thrown errors into detailed HTTP responses.

Downloads

10

Readme

Http Errors

This is a small library whose primary export is the HttpError class, a derivative of the native Error class. HttpError can be instantiated and thrown like any error, except that it carries additional information that is pertinent to HTTP interactions.

For example:

import * as express from "express";
import { HttpError, isHttpError } from "http-errors";
import { authenticate } from "./Authnz";
import { doThings } from "./myLib";
import { SimpleLoggerConsole } from "@wymp/simple-logger-console";

const app = express();
const log = new SimpleLoggerConsole();

// Normal endpoint
app.get("/my/endpoint", function(req, res, next) {
  try {
    if (!req.headers("Authorization")) {
      throw new HttpError(
        401,
        "You must pass a standard Authorization header to use this endpoint",
        { subcode: "MissingAuthHeader" }
      );
    }

    // May throw additional HTTP errors, such as 400, 401 or 403
    authenticate(req.headers("Authorization"));
    doThings(req);

    // If nothing threw, just return 200 with our data
    return res.status(200).send({ data: "Yay!" });
  } catch (e) {
    // Otherwise, pass the error to next
    next(e);
  }
});

// Global error handler
app.use(function(_e: Error, req, res, next) {
  // If it's not already an HttpError, convert it into an InternalServerError (500)
  const e = HttpError.from(_e);

  // Log the error
  log[e.logLevel](e.stack);

  // Return the response with the correct status and structure
  return res.status(e.status).send({
    ok: false,
    // Note that an HttpError is properly serialized by JSON.stringify, so we don't need to do anything extra here
    error: e
  });
});

API

isHttpError()

A typeguard allowing you to check if a given error is an HttpError. Probably better to just use HttpError.from instead.

HttpError.from

Takes any Error object and converts it to an HttpError. This is most often used in catch blocks to turn native Errors into HttpErrors like so:

try {
  woops this is gonna throw
} catch (_err) {
  const err = HttpError.from(_err);

  // Now we know it's an HttpError and can access its fields
  // ...
}

HttpError.toJSON and (static) HttpError.fromJSON

These methods allow you to easily serialize to JSON and de-serialize back into a native HttpError. To serialize, simply pass the error to JSON.stringify and it will output important fields (although not headers or stack, to avoid accidental leakage). To de-serialize, pass either the string or the already-deserialized object into the HttpError.fromJSON static method.

For example:

const e = new HttpError(404, "Not Found", { obstructions: [{ code: "something", text: "Some explanation" }] });
const json = JSON.stringify(e);
const inflated = HttpError.fromJSON(json);
// e.status === inflated.status
// e.name === inflated.name
// e.obstructions == inflated.obstructions
// etc...

HttpError

Throw this error instead of native errors in order to attach additional data pertinent to your application and to the HTTP context.

Important fields are:

  • status - The status code of the error. (You must pass this into the constructor, and it defaults to 500 when converting regular errors)
  • name - The standardized status text corresponding to the status code. (See also HttpErrorStatuses)
  • subcode - You can use this field to offer a concise, immutable code indicating more specifically what the error pertains to. In the example above, we used MissingAuthHeader as the subcode in our 401. This can help the client understand what needs to change about the request in order for it to succeed.
  • logLevel - This is an internal field that you can use in your system to determine whether or not to log this error.
  • obstructions - An optional collection of more specific data about why the user cannot do what they want to do. See below.
  • headers - Some errors (such as 401) require certain headers to be returned. This allows you to do that.

Obstructions

The concept of obstructions is specific to the HttpError world. An obstruction is defined as follows:

export type ObstructionInterface<Data = undefined> = Data extends undefined
  ? { code: string; text: string }
  : { code: string; text: string; data: Data };

These are meant to be light-weight and data-dense packets that allow you to communicate to consumers about multiple issues that are preventing a given request from completing successfully.

Imagine you're registering a new user. Using an HttpError with obstructions, you can easily stop execution and send back a 400 with useful information from anywhere in your code. In the following example, there are certain things for which we immediately throw and other things where we build up a list of obstructions and then throw.

// library: @my-org/types

// First we'll define our obstructions. This allows front- and back-end code to work with type-safe errors
import { ObstructionInterface } from "@wymp/http-errors";
export type MyObstructions =
  | ObstructionInterface<"NoUserName">
  | ObstructionInterface<"NoEmail">
  | ObstructionInterface<"InvalidEmail", { email: string; pattern: string }>
  | ObstructionInterface<"NoPassword">
  | ObstructionInterface<"NoPasswordConf">
  | ObstructionInterface<"PasswordConfMismatch">;
// app: @my-org/core

import { MyObstructions } from "@my-org/types";

const body = req.body;
if (!body) {
  throw new HttpError(400, "Missing request body. Did you send it?");
}
if (!body.user) {
  throw new HttpError(400, "Missing incoming user object. Did you send it?");
}

const obstructions: Array<MyObstructions> = [];
const ourEmailRegex = /.../;

if (!body.user.name) {
  obstructions.push({
    code: "NoUserName",
    text: "You must send a user name to register."
  });
}

if (!body.user.email) {
  obstructions.push({
    code: "NoEmail",
    text: "You must send an email for the user you're registering."
  });
} else {
  if (!validateEmail(body.user.email, ourEmailRegex)) {
    obstructions.push({
      code: "InvalidEmail",
      text: "Your email doesn't appear to be in valid format.",

      // Note that we can provide data so consumers can be more detailed about
      // how they display the errors
      data: {
        email: body.user.email,
        pattern: ourEmailRegex.toString()
      }
    });
  }
}

if (!body.user.password) {
  obstructions.push({
    code: "NoPassword",
    text: "You haven't provided a password."
  });
} else {
  if (!body.user.passwordConfirmation) {
    obstructions.push({
      code: "NoPasswordConf",
      text: "You haven't provided a password confirmation."
    });
  } else {
    if (body.user.password !== body.user.passwordConfirmation) {
      obstructions.push({
        code: "PasswordConfMismatch",
        text: "Your password confirmation doesn't match the password you entered."
      });
    }
  }
}

if (obstructions.length > 0) {
  throw new HttpError(400, "There were problems registering your user.", { obstructions });
}

// Continue processing....