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

@web-ts-toolkit/express-response-handler

v0.68.0

Published

FastAPI-style return-value response handling for Express

Readme

@web-ts-toolkit/express-response-handler

FastAPI-style return-value responses for Express.

Installation

Requires Node.js >=22 and Express ^5 (peer dependency, installed separately).

pnpm add @web-ts-toolkit/express-response-handler @web-ts-toolkit/http-errors express

The quickstart below imports express and @web-ts-toolkit/http-errors directly, so both must be direct dependencies. @web-ts-toolkit/http-errors is also a transitive dependency of this package, but transitive packages are not importable from an isolated consumer (pnpm), so listing it directly is required when route code throws typed errors.

For TypeScript consumers:

pnpm add -D typescript @types/express @types/node

Highlights

  • return plain JSON values instead of calling res.json(...)
  • return explicit HttpResponse wrappers for status control
  • throw typed HTTP errors
  • switch between simple, AIP-193, and RFC 9457-style error payloads
  • if a wrapped handler returns undefined, the library assumes the handler will manage the response itself

Quick Start

import express from 'express';
import apiHandler from '@web-ts-toolkit/express-response-handler';
import { NotFoundError } from '@web-ts-toolkit/http-errors';

const { handleResponse, HttpResponse } = apiHandler;
const app = express();

async function getUser(id: string) {
  return id === 'missing' ? null : { id, name: 'Ada' };
}

async function createJob() {
  return { id: 'job_1' };
}

app.get(
  '/health',
  handleResponse(() => ({ ok: true })),
);

app.get(
  '/users/:id',
  handleResponse(async (req) => {
    const user = await getUser(req.params.id);
    if (!user) throw new NotFoundError('user not found');
    return user;
  }),
);

app.post(
  '/jobs',
  handleResponse(async () => {
    const job = await createJob();
    return HttpResponse.created(job);
  }),
);

Main Exports

Root entrypoint (@web-ts-toolkit/express-response-handler):

  • default handler instance
  • handleResponse(...)
  • HttpResponse
  • createHandler(...)
  • ErrorFormats
  • CSVResponse, Response, and 10 success classes: Accepted, AlreadyReported, Created, IMUsed, MultiStatus, NoContent, NonAuthoritativeInfo, OK, PartialContent, ResetContent

Subpath entrypoints:

  • @web-ts-toolkit/express-response-handler/types — public type exports
  • @web-ts-toolkit/express-response-handler/responses — response wrappers
  • @web-ts-toolkit/express-response-handler/responses/csv — CSVResponse
  • @web-ts-toolkit/express-response-handler/responses/success — Created, Accepted, NoContent, etc.

Subpath import example

import { handleResponse } from '@web-ts-toolkit/express-response-handler';
import { Created, NoContent } from '@web-ts-toolkit/express-response-handler/responses/success';

async function createUser() {
  return { id: 'user_1' };
}

app.post(
  '/users',
  handleResponse(async () => new Created(await createUser())),
);
app.delete(
  '/users/:id',
  handleResponse(async () => new NoContent()),
);

Import styles

The package ships both a default handler instance and named exports:

// default export
import apiHandler from '@web-ts-toolkit/express-response-handler';
const { handleResponse, HttpResponse } = apiHandler;

// named exports
import { handleResponse, HttpResponse } from '@web-ts-toolkit/express-response-handler';

Documentation

Full package documentation: https://web-ts-toolkit.pages.dev/docs/packages/express-response-handler

  • source: website/docs/packages/express-response-handler.md in the repository
  • live docs: https://web-ts-toolkit.pages.dev/docs/packages/express-response-handler

Hooks

Hooks are observational side effects. They receive the value or error being handled, may return void or Promise<void>, and returned values never transform the response payload.

  • preJson runs before a non-undefined success value is serialized. It is skipped when the handler returns undefined or has already taken manual response ownership (for example a synchronous res.* write that committed headers).
  • postJson runs after the HTTP response emits finish for a successful JSON, HttpResponse, or CSV response. It never runs for fallback JSON errors (circular/BigInt serialization, rejected preJson, CSV-before-output failures) or for manual undefined responses.
  • preError runs before an error response is serialized, including success-path fallback errors. It always observes the original failure value.
  • postError runs after the HTTP response emits finish for an error response, including fallback JSON errors. It receives the same failure that was sent (the original, or the preError failure when preError itself fails).

If a handler returns undefined, the library assumes the handler owns the response and runs no success hooks (preJson/postJson) and emits no unsolicited 500. postJson and postError do not run on client close or on partial responses that never successfully finish an error body.

Success-path serialization, preJson, and CSV-before-output failures go through one bounded error lifecycle: one preError, one redacted error body, and one finish-timed postError. A failing preError/provider is never re-entered. When headers are already committed (partial write), the owned failure is delegated to Express error middleware with next(err) and no second body.

Pre-hook failures are routed through the normal error response path. Post-hook failures happen after the response has completed, so they are passed to Express with next(err) for server-side logging/observability without creating a second client response.

The default export is a mutable process-wide singleton. Use createHandler() when you need isolated hook or error-provider state.

Error Security

Unexpected non-HTTP failures are treated as server errors and return status 500 with the generic client message Internal Server Error. The original thrown value is still passed to preError, postError, and Express error middleware if serialization itself fails, so log there instead of exposing raw exception text to clients.

Only finite integer 4xx and 5xx status codes are serialized as HTTP errors. Invalid status values are rejected before response headers are written.

Breaking change: older versions returned generic thrown Error messages as 422 responses. Use typed errors from @web-ts-toolkit/http-errors for intentional client-facing 4xx payloads, or set errorMessageProvider when you deliberately want a sanitized custom generic payload.

CSV Security

CSVResponse writes cell values exactly as supplied. It does not automatically neutralize spreadsheet formulas such as values beginning with =, +, -, or @ because some exports intentionally include formulas.

If user-controlled cells may be opened in spreadsheet software, neutralize them with the processor option before streaming:

const safeCell = (value: unknown) => (typeof value === 'string' && /^[=+\-@]/.test(value) ? `'${value}` : value);

const safeRow = (row: unknown) => {
  if (!row || typeof row !== 'object' || Array.isArray(row)) {
    return safeCell(row);
  }

  return Object.fromEntries(Object.entries(row).map(([key, value]) => [key, safeCell(value)]));
};

return new CSVResponse(rows, {
  filename: 'users.csv',
  processor: safeRow,
});

See the root import measurement and CSV formula-injection policy rationale in ERH-12.md in the repository (dev note, not shipped in the published package).

Direct CSVResponse.streamCsv(res) error ownership (no breaking change): pre-output failures (invalid filename, failed first read, first-row processor throw) with an onBeforeOutputError callback invoke it exactly once and the owner terminates res (the handler owner renders one redacted JSON error); a throwing owner is contained and res is destroyed with the original failure. Without a callback the destination is destroyed with the normalized failure (observable via error + close). Post-output failures always destroy the destination. Non-Error failures are wrapped with the original as cause.