Skip to content
courier logoCourierHTTP
A framework-neutral HTTP client with explicit cached reads, prefetching, immutable middleware, typed paths, and structured errors.
Version
v3.0.0
Size
5.4 KB gzip
BrowserNode ≥22SSRDeno
createCourierCourierErrorCourierHttpErrorCourierNetworkErrorCourierTimeoutError View all 10 exports

Why Courier?

Native fetch leaves request policy, error classification, middleware composition, and repeated-read coordination to each application. Courier keeps those concerns in one client, including explicit cached GETs and warm-cache prefetching, while observable query and mutation state remain consumer-owned.

ts
// Before
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const user = await response.json();

// After
const user = await courier.get<User>('/users/{id}', { params: { id: userId } });
FeatureCourierkyofetch
Bundle size5.4 KBSeparate packageSeparate package
Zero runtime dependencies
Native fetch transport
Explicit parsed-value cache and prefetch
Immutable middleware at construction
Structured error taxonomy

Use Courier when one client should own typed HTTP, explicit cached GETs and prefetching, immutable middleware, and structured errors — while a separate state layer owns observable queries and mutations.

Consider ky when you only need a compact fetch wrapper. Consider ofetch when you want a bundled fetch utility with its own retry and caching conventions.

Installation

sh
pnpm add @vielzeug/courier
sh
npm install @vielzeug/courier
sh
yarn add @vielzeug/courier

Quick Start

Create one transport client for an application or request scope, then use method conveniences for standard HTTP methods and request() for custom methods.

ts
import { CourierHttpError, createCourier } from '@vielzeug/courier';

type User = { id: number; name: string };

const courier = createCourier({ baseUrl: 'https://api.example.com' });

try {
  const user = await courier.get<User>('/users/{id}', { params: { id: 42 } });
  console.log(user.name);
} catch (error) {
  if (CourierHttpError.is(error, 404)) console.log('User not found');
  else throw error;
} finally {
  courier.dispose();
}

Features

  • createCourier() — one lifecycle, immutable middleware pipeline, header defaults, and cancellation boundary.
  • request() — one contract for every HTTP method; pass method in the config.
  • Cached get() — opt in with a structured key and optional per-read TTL; successful parsed values share bounded storage and in-flight work.
  • prefetch() — await cache warming while request failures remain observable through tap().
  • invalidateCache() / clearCache() — invalidate structured key prefixes without query state.
  • get() / post() / put() / patch() / delete() — conveniences for standard methods.
  • withBearerAuth() / withRequestId() / withLogging() — composable middleware configured at construction.
  • tap() — signal-owned structured transport observation.
  • Error taxonomy — HTTP, network, timeout, abort, parse, and schema failures are distinct, actionable classes.
  • cancelAll() / dispose() — abort active requests and tear down the transport.

Documentation

See Also

  • Flux — composes async streams; pair with Courier transport calls in the owning state layer.
  • Spell — validates parsed HTTP payloads through Courier's schema option.
  • Postmaster — coordinates durable delivery of Courier requests through a job outbox.