Skip to content

hapic

A tiny, fetch-based HTTP client for TypeScript — request & response hooks, transformers, authorization helpers and automatic error throwing. One transport, a family of typed service clients on top.

MIT licensed · Node 16+ · Browser & Workers · ESM + CJS · Zero-config

hapic — request playground
await client.get("users/2");
200 OK
{
  "id": 2,
  "name": "Peter",
  "active": true
}

Every method returns a Response whose data is decoded for you. Non-2xx responses throw a ClientError you can catch — or recover from inside a hook.

One client. Batteries included.

A small, predictable core — and everything you reach for in a real HTTP layer already wired in.

Tiny & fetch-based

A thin wrapper over the platform fetch — no axios-sized footprint. Method shortcuts (get / post / put / patch / delete / head) and a baseURL are all you need to start.

🛑

Request & response hooks

Intercept every request, response, and error. An error hook can return a Response to recover, or new RequestOptions to retry — token refresh in a few lines.

🔄

Transformers

Shape the outgoing body and headers, or normalize incoming data, with per-request or per-client transformer functions that compose in order.

Errors that throw

Any 4xx/5xx becomes a typed ClientError carrying the request, response and status. Duck-typed guards (isClientError*) work across bundled copies.

🌐

Runs everywhere

One code path for Node.js, the browser, and worker runtimes — fetch, Headers, Blob, FormData and proxy support resolve per environment. Ships ESM + CJS.

🧩

A family of clients

Harbor, Loki, OAuth2, Vault and VictoriaLogs clients extend the same Client with typed, domain-oriented APIs — share one transport, drop the boilerplate.

From install to first request

No client builders, no interceptor registries — a function call and a few options.

npm install hapic --save

hooks & recovery

Hooks that can rewrite the outcome

hapic runs your hooks around the whole request lifecycle. An error hook is special: return a Response to swallow the failure, or fresh RequestOptions to retry — the client recurses for you. Token refresh, backoff, and fallbacks become a handful of lines.

  • request — mutate options, inject headers, add transformers before dispatch
  • response — normalize or unwrap payloads on every 2xx/3xx
  • requestError / responseError — recover with a Response, or retry with new options
  • on() / off() — register many hooks, remove one by id or all by name
Read the hooks guide →
auth.ts
// auth.ts
import { createClient, isClientErrorWithStatusCode } from 'hapic';

const client = createClient({ baseURL: 'https://api.example.com/' });

// Recover from a 401 by refreshing the token, then retry
client.on('responseError', async (error) => {
    if (isClientErrorWithStatusCode(error, 401)) {
        const token = await refreshAccessToken();

        // returning RequestOptions re-runs the original request
        return {
            ...error.request,
            headers: {
                ...error.request.headers,
                authorization: `Bearer ${token}`,
            },
        };
    }

    // anything else keeps throwing
    throw error;
});

Released under the MIT License.