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

wiremock-ts

v0.8.0

Published

A TypeScript-first HTTP mock server — WireMock, reimagined.

Readme

wiremock-ts

npm version CI license node

A TypeScript-first HTTP mock server — WireMock, reimagined for the Node/TS ecosystem.

Stand up a real HTTP server that returns the responses you stub, match incoming requests on almost anything, and verify what your code actually sent — through a fully-typed fluent API, a REST admin API, or a CLI. The same stubs can also serve in-process by intercepting fetch, with no socket at all.

It runs on Node's built-in http/https. Runtime dependencies are zod (schemas), jsonpath-plus (JSONPath matching), ajv (OpenAPI request validation) and ws (WebSocket mocking).

Contents

Install

npm install --save-dev wiremock-ts

Requires Node 20+. Ships as ESM with type declarations.

Quick start

import { equalToJson, get, ok, okJson, post, urlPathEqualTo, WireMockServer } from "wiremock-ts";

const wm = await new WireMockServer({ port: 8080 }).start();

wm.stubFor(get(urlPathEqualTo("/api/user")).willReturn(okJson({ id: 1, name: "ada" })));

wm.stubFor(
    post(urlPathEqualTo("/orders"))
        .withRequestBody(equalToJson({ sku: "abc" }))
        .willReturn(ok("accepted")),
);

// point your code at wm.baseUrl, then verify what it sent:
wm.verify({ method: "POST", urlPath: "/orders" }, 1); // true if called exactly once

await wm.stop();

Use port: 0 to bind a free ephemeral port (ideal for parallel tests) and read the chosen address from wm.baseUrl. In tests, await using cleans up automatically:

import { get, ok, startMock, urlPathEqualTo } from "wiremock-ts";

it("works", async () => {
    await using wm = await startMock({ port: 0 });
    wm.stubFor(get(urlPathEqualTo("/ping")).willReturn(ok("pong")));
    // ... the server is stopped when the block exits
});

Two ways to serve

The same stubs can be served two ways:

  1. A real HTTP server (start()) — anything that speaks HTTP can hit it.
  2. In-process fetch interception (interceptFetch()) — no socket; fetch calls are answered from the registry. Unmatched requests pass through to the real fetch by default.
const wm = new WireMockServer(); // not started
wm.stubFor(get(urlPathEqualTo("/api/ping")).willReturn(okJson({ pong: true })));
wm.interceptFetch();

await fetch("https://example.test/api/ping"); // served in-process, no network

wm.restoreFetch();

Pass interceptFetch({ passthrough: false }) to return 404 for unmatched requests instead of hitting the network.

HTTPS

Pass a PEM key and certificate to serve over TLS; everything else (stubbing, the admin API, verification) is unchanged, and baseUrl reports an https:// URL.

const wm = await new WireMockServer({
    https: { key: pemKey, cert: pemCert },
}).start();
// wm.baseUrl === "https://127.0.0.1:<port>"

Bring your own certificate (for a fixed hostname) or generate a throwaway self-signed pair for tests.

Request matching

A request matches when every constraint declared on the pattern is satisfied. An empty pattern matches anything.

| Field | Matches on | | ----------------- | --------------------------------------------------- | | method | HTTP method, or "ANY" | | url | exact path and query string | | urlPath | exact path only | | urlPathPattern | regex against the path | | urlPattern | regex against path + query | | queryParameters | per-parameter content pattern | | headers | per-header content pattern (case-insensitive names) | | bodyPatterns | content patterns against the raw body |

URL matchers and content matchers are exposed as builder helpers:

import {
    absent,
    containing,
    equalTo,
    equalToJson,
    get,
    matching,
    matchingJsonPath,
    notMatching,
    urlEqualTo,
    urlMatching,
    urlPathEqualTo,
    urlPathMatching,
} from "wiremock-ts";

wm.stubFor(
    get(urlPathMatching("^/users/\\d+$"))
        .withQueryParam("expand", equalTo("profile"))
        .withHeader("authorization", matching("^Bearer "))
        .willReturn(ok()),
);

Content patterns: equalTo(value, caseInsensitive?), containing, matching / notMatching (regex), equalToJson(value, { ignoreExtraElements?, ignoreArrayOrder? }), matchingJsonPath, and absent.

When several stubs match, the lowest priority wins (set with .atPriority(n)); on a tie the most recently registered stub wins.

Responses

Build responses fluently:

import { aResponse, created, noContent, notFound, ok, okJson, status } from "wiremock-ts";

aResponse()
    .withStatus(202)
    .withHeader("x-trace", "abc")
    .withJsonBody({ queued: true })
    .withFixedDelay(150); // ms

ok(body?), okJson(json), created(body?), noContent(), notFound(body?) and status(code) are shortcuts. A response can carry body, jsonBody, or base64Body, plus headers, a fixed delay, a template flag, a fault, or a proxy target.

Programmatic responses

Pass a function to willReturn to compute the response from the request — fully typed, no string templating required. This is in-process only (used with start() or interceptFetch()).

wm.stubFor(
    get(urlPathMatching("^/echo")).willReturn((req) =>
        okJson({ method: req.method, path: req.urlPath, query: req.query }),
    ),
);

Response templating

For declarative (serialisable) stubs, opt in with .withTransform() and use {{ ... }} tokens in the body and header values:

aResponse()
    .withBody("you requested {{request.path}} at {{now}} (id {{randomUuid}})")
    .withTransform();

Supported tokens: request.method, request.url, request.path, request.body, request.query.<key>, request.headers.<key>, now, and randomUuid. Unknown tokens render as empty.

Stateful scenarios

Model a sequence of responses with a per-scenario state machine. A stub only matches while its scenario is in requiredScenarioState (the implicit start state is "Started"), and serving it transitions to newScenarioState.

wm.stubFor(
    get(urlPathEqualTo("/job"))
        .inScenario("job")
        .whenScenarioStateIs("Started")
        .willSetStateTo("done")
        .willReturn(okJson({ status: "pending" })),
);
wm.stubFor(
    get(urlPathEqualTo("/job"))
        .inScenario("job")
        .whenScenarioStateIs("done")
        .willReturn(okJson({ status: "complete" })),
);

Fault injection

Return a connection-level failure instead of a normal response:

wm.stubFor(get(urlPathEqualTo("/flaky")).willReturn(aResponse().withFault("connection-reset")));

Faults: connection-reset, empty-response, malformed-chunk, random-then-close. Under interceptFetch() a fault surfaces as a rejected fetch (network error).

Proxying

Forward a matched request to a real upstream and return its response:

import { proxiedFrom } from "wiremock-ts";

wm.stubFor(get(urlMatching("^/v1/")).willReturn(proxiedFrom("https://api.example.com")));

Record and playback

Capture live traffic against a real backend, then replay it offline:

wm.startRecording("https://api.example.com");
// ... drive your app; every request is proxied and captured ...
const mappings = wm.stopRecording();

// replay later, with the backend unavailable:
for (const mapping of mappings) wm.stubFor(mapping);

Contract-first (OpenAPI)

Generate a working mock from an OpenAPI 3 document — one stub per operation, with response bodies generated from the schemas ($ref resolution, example/default/enum, and format-aware strings such as uuid, email, date-time).

import openapi from "./openapi.json" with { type: "json" };

wm.loadOpenApi(openapi); // registers a stub for every path + method

Use stubsFromOpenApi(doc) to get the mappings without registering them, or generateSample(schema, doc) to build representative data from a single schema.

Pass { validateRequests: true } to hold callers to the contract: operations with a request body schema validate incoming bodies with ajv and answer 400 (with the violations) when they do not conform, instead of returning the success response.

wm.loadOpenApi(openapi, { validateRequests: true });

// a request whose body omits a required field or uses the wrong type:
// → 400 { "error": "Request body failed schema validation", "errors": [ ... ] }

$refs into #/components/schemas resolve against the document. compileRequestBodyValidators(doc) and validateRequestBody(validator, body) are exported if you want to validate without a server.

GraphQL

Stub a GraphQL endpoint by operation name. GraphQL posts { query, operationName, variables }; graphQlRequest matches on operationName (ignoring the rest of the body), with an optional substring check against the query.

import { graphQlRequest, graphQlRequestedFor, okJson } from "wiremock-ts";

wm.stubFor(graphQlRequest("GetUser").willReturn(okJson({ data: { user: { id: 1 } } })));

// finer control, and a custom path:
wm.stubFor(
    graphQlRequest("Search", { path: "/api/graphql", queryContains: "first: 10" }).willReturn(
        okJson({ data: { search: [] } }),
    ),
);

// verify a GraphQL operation was called:
wm.verify(graphQlRequestedFor("GetUser"));

The endpoint defaults to /graphql; override it with the path option. It returns the same builder as post(...), so all the usual response, scenario and priority methods apply.

WebSockets

Register a WebSocket endpoint at an exact path. Connecting clients receive scripted messages on connect, an echo of what they send, or a computed reply.

// push messages as soon as the client connects:
wm.stubWebSocket("/feed", { onConnect: ["hello", "world"] });

// echo everything back:
wm.stubWebSocket("/echo", { echo: true });

// compute a reply per message (return undefined to stay silent):
wm.stubWebSocket("/rpc", { reply: (msg) => (msg === "ping" ? "pong" : undefined) });

Connections to a path with no stub are refused. WebSocket endpoints are served on the same port (over TLS too when https is set, via wss://).

Verification

Assert what your code sent, either by boolean or by throwing:

import { containing, postRequestedFor, urlPathEqualTo } from "wiremock-ts";

wm.verify(postRequestedFor(urlPathEqualTo("/orders")), 1); // boolean
wm.assertReceived(postRequestedFor(urlPathEqualTo("/orders"))); // throws if never received
wm.countRequests({ method: "GET", urlPath: "/health" }); // number
wm.findRequests(postRequestedFor(urlPathEqualTo("/orders")).withRequestBody(containing("abc")));

getRequestedFor, postRequestedFor, putRequestedFor, deleteRequestedFor, patchRequestedFor and anyRequestedFor build verification patterns and accept .withHeader / .withQueryParam / .withRequestBody.

Remote client SDK

Drive a server running elsewhere (another process, a container, a CI service) over its admin API, using the same DSL builders. WireMockClient mirrors the stubbing and verification surface; its methods are async.

import { connectMock, get, getRequestedFor, okJson, urlPathEqualTo } from "wiremock-ts";

const client = connectMock("http://localhost:8080", {
    adminToken: process.env.WIREMOCK_ADMIN_TOKEN,
});

await client.stubFor(get(urlPathEqualTo("/ping")).willReturn(okJson({ pong: true })));
// ... drive your system-under-test ...
await client.verify(getRequestedFor(urlPathEqualTo("/ping")), 1);
await client.resetAll();

Methods: register / stubFor, listMappings, countRequests, findRequests, verify, assertReceived, resetMappings, resetRequests, resetAll. Programmatic (function) responses run in-process only, so registering one through the client throws.

CLI

wiremock-ts --port 8080 --host 127.0.0.1 --mappings ./mappings

--mappings <dir> loads every *.json file in the directory; each file is either a single stub mapping or { "mappings": [ ... ] }.

Admin API

Served on the same port under /__admin:

| Method & path | Purpose | | ------------------------------- | --------------------------------- | | GET /__admin/health | Liveness | | GET /__admin/mappings | List stubs | | POST /__admin/mappings | Register a stub (201) | | GET /__admin/mappings/{id} | Fetch one stub | | DELETE /__admin/mappings/{id} | Delete one stub | | POST /__admin/mappings/reset | Clear all stubs | | GET /__admin/requests | Request journal | | DELETE /__admin/requests | Clear the journal | | POST /__admin/requests/count | Count requests matching a pattern | | POST /__admin/requests/find | Find requests matching a pattern | | POST /__admin/reset | Reset stubs and journal |

Request bodies are validated with zod; malformed JSON or schema-invalid mappings return 400.

API reference

WireMockServer (and startMock(options), which constructs and starts in one call):

  • Lifecycle: start(), stop(), baseUrl, port, [Symbol.asyncDispose] (options: port, host, adminToken, allowedProxyHosts, https)
  • Stubbing: stubFor(mapping) / register(mapping), listMappings(), loadOpenApi(doc, { validateRequests? }), stubWebSocket(path, options)
  • Verification: verify(pattern, count?), assertReceived(pattern, count?), countRequests(pattern), findRequests(pattern)
  • Reset: resetMappings(), resetRequests(), resetAll()
  • Record: startRecording(targetBaseUrl), stopRecording(), isRecording
  • Interception: interceptFetch(options?), restoreFetch()

pattern accepts a plain RequestPattern or any *RequestedFor(...) builder. Builders, content matchers, response builders, the GraphQL helpers, the OpenAPI helpers, and WireMockClient / connectMock are all exported from the package root.

Scripts

| Script | Description | | ----------------------- | ------------------------------------------ | | npm run dev | Run the CLI under tsx watch | | npm run build | Bundle to dist/ (ESM + .d.ts) via tsup | | npm test | Run the Vitest suite | | npm run test:coverage | Run the suite with a coverage report | | npm run typecheck | tsc --noEmit | | npm run lint | ESLint (flat config) | | npm run format | Prettier (formats and sorts imports) |

Roadmap

  • gRPC mocking (HTTP/2 + protobuf)
  • Response-body schema validation against the contract

Security

wiremock-ts is a development and testing tool. It binds to 127.0.0.1 by default — keep it there unless you have a reason not to, because the admin API and the proxy are powerful and unauthenticated out of the box.

  • The admin API is unauthenticated by default. Anyone who can reach the port can register or delete stubs, reset state, and read the request journal — which holds the headers and bodies your system-under-test sent, including any credentials. Set adminToken to require Authorization: Bearer <token> (or X-Admin-Token: <token>) on every /__admin request:

    const wm = await new WireMockServer({ adminToken: process.env.WIREMOCK_ADMIN_TOKEN }).start();
  • Proxy and record can reach arbitrary URLs (SSRF). proxiedFrom, proxyBaseUrl and startRecording make the server issue outbound requests. Restrict them with allowedProxyHosts — only listed hostnames may be proxied; anything else is refused with 502 (or a thrown error when recording):

    const wm = await new WireMockServer({ allowedProxyHosts: ["api.example.com"] }).start();
  • interceptFetch() patches global fetch — it's for tests, and stop() / restoreFetch() restore the original.

To report a vulnerability, see SECURITY.md.

Contributing

Contributions are welcome — please read CONTRIBUTING.md first. It covers the project's conventions (exact-pinned dependencies, extensionless ESM imports, named exports, the ESLint/Prettier setup) and the typecheck → lint → format:check → test → build gate every change must pass.

Licence

MIT © Matt Hesketh