@jaredwray/mockhttp 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,7 +28,7 @@ A simple HTTP server for mocking responses in tests. Inspired by [httpbin](https
28
28
  - [Bins](/docs/bins/) — capture and inspect incoming HTTP requests (great for webhook debugging)
29
29
  - `@fastify/helmet` built in by default
30
30
  - Built with Node.js, TypeScript, and Fastify
31
- - Deploy via Docker or Node.js
31
+ - Run it in Node.js, or deploy with [Docker](/docs/how-to-deploy/docker/), [Cloudflare Workers](/docs/how-to-deploy/cloudflare/), or [Wasmer Edge](/docs/how-to-deploy/wasmer/)
32
32
  - Free hosted service at [mockhttp.org](https://mockhttp.org), running on Cloudflare
33
33
  - Documentation site and interactive [OpenAPI reference](/api/)
34
34
  - Auto-detect the next port that is not in use
@@ -78,6 +78,8 @@ console.log(response);
78
78
  await mock.close(); // stop the server
79
79
  ```
80
80
 
81
+ Step-by-step deploy guides: [Docker](/docs/how-to-deploy/docker/), [Cloudflare](/docs/how-to-deploy/cloudflare/), and [Wasmer](/docs/how-to-deploy/wasmer/).
82
+
81
83
  ### HTTPS and HTTP/2
82
84
  URL: https://mockhttp.org/docs/https/
83
85
 
@@ -615,13 +617,13 @@ curl -s "http://localhost:3000/bins/$ID/requests" \
615
617
 
616
618
  ### Truncation
617
619
 
618
- Bodies larger than `maxBodySize` (1 MiB by default) are cut off and flagged:
620
+ Bodies larger than `maxBodySize` (128 KiB by default) are cut off and flagged:
619
621
 
620
622
  ```javascript
621
623
  // In test setup
622
- import { MockHttp, BinManager } from '@jaredwray/mockhttp';
624
+ import { MockHttp, MockHttpBins } from '@jaredwray/mockhttp';
623
625
  const mock = new MockHttp();
624
- mock.bins = new BinManager({ maxBodySize: 1024 }); // 1 KiB cap
626
+ mock.bins = new MockHttpBins({ maxBodySize: 1024 }); // 1 KiB cap
625
627
  await mock.start();
626
628
  ```
627
629
 
@@ -661,8 +663,7 @@ curl -s "http://localhost:3000/bins/$ID/requests" | jq '.requests[0].path'
661
663
 
662
664
  ## Programmatic Access
663
665
 
664
- The bin manager is exposed on the `MockHttp` instance as `mock.bins`. This
665
- makes it easy to drive bins from a test suite without going through HTTP:
666
+ Use `mock.bins` to control request bins from a test. You do not need an HTTP call:
666
667
 
667
668
  ```javascript
668
669
  import { MockHttp } from '@jaredwray/mockhttp';
@@ -671,13 +672,13 @@ const mock = new MockHttp({ logging: false });
671
672
  await mock.start();
672
673
 
673
674
  // Create a bin
674
- const bin = mock.bins.createBin();
675
+ const bin = await mock.bins.createBin();
675
676
  console.log(`Webhook target: http://localhost:${mock.port}/b/${bin.id}`);
676
677
 
677
678
  // ... point your code-under-test at that URL ...
678
679
 
679
680
  // Read captured requests (newest first)
680
- const requests = mock.bins.getRequests(bin.id);
681
+ const requests = await mock.bins.getRequests(bin.id);
681
682
  console.log(`Captured ${requests.length} requests`);
682
683
 
683
684
  for (const req of requests) {
@@ -685,8 +686,8 @@ for (const req of requests) {
685
686
  }
686
687
 
687
688
  // Clean up
688
- mock.bins.deleteBin(bin.id);
689
- await mock.close(); // also stops the bin cleanup timer
689
+ await mock.bins.deleteBin(bin.id);
690
+ await mock.close();
690
691
  ```
691
692
 
692
693
  ### Using Bins in a Vitest Test
@@ -707,14 +708,14 @@ afterAll(async () => {
707
708
  });
708
709
 
709
710
  test('my SDK sends the right webhook payload', async () => {
710
- const bin = mock.bins.createBin();
711
+ const bin = await mock.bins.createBin();
711
712
  const webhookUrl = `http://localhost:${mock.port}/b/${bin.id}`;
712
713
 
713
714
  // Drive your SDK at the bin
714
715
  await mySdk.notify(webhookUrl, { event: 'user.created', id: 42 });
715
716
 
716
717
  // Assert on what actually arrived
717
- const [captured] = mock.bins.getRequests(bin.id);
718
+ const [captured] = await mock.bins.getRequests(bin.id);
718
719
  expect(captured.method).toBe('POST');
719
720
  expect(captured.contentType).toBe('application/json');
720
721
  expect(JSON.parse(captured.body!)).toEqual({
@@ -735,18 +736,17 @@ const mock = new MockHttp({
735
736
  });
736
737
  ```
737
738
 
738
- To tune limits, replace the default `BinManager` before starting:
739
+ To change the limits, replace the default `MockHttpBins` before you start the server:
739
740
 
740
741
  ```javascript
741
- import { MockHttp, BinManager } from '@jaredwray/mockhttp';
742
+ import { MockHttp, MockHttpBins } from '@jaredwray/mockhttp';
742
743
 
743
744
  const mock = new MockHttp();
744
- mock.bins = new BinManager({
745
+ mock.bins = new MockHttpBins({
745
746
  defaultTtlMs: 60 * 60 * 1000, // 1 hour (default: 24h)
746
747
  maxRequestsPerBin: 500, // (default: 100)
747
- maxBodySize: 5 * 1024 * 1024, // 5 MiB (default: 1 MiB)
748
+ maxBodySize: 512 * 1024, // 512 KiB (default: 128 KiB)
748
749
  idLength: 16, // (default: 12)
749
- cleanupIntervalMs: 30 * 1000, // sweep every 30s (default: 60s)
750
750
  });
751
751
 
752
752
  await mock.start();
@@ -756,44 +756,40 @@ await mock.start();
756
756
 
757
757
  | Option | Default | Description |
758
758
  |--------|---------|-------------|
759
- | `defaultTtlMs` | `86400000` (24h) | Bin lifetime. Expired bins return 404 and are lazily removed. |
759
+ | `defaultTtlMs` | `86400000` (24h) | Bin lifetime, set as the Keyv TTL. An expired bin returns 404. |
760
760
  | `maxRequestsPerBin` | `100` | When exceeded, oldest captures are dropped (FIFO). |
761
- | `maxBodySize` | `1048576` (1 MiB) | Larger bodies are truncated; `truncated: true` is set on the capture. |
761
+ | `maxBodySize` | `131072` (128 KiB) | Larger bodies are truncated; `truncated: true` is set on the capture. |
762
762
  | `idLength` | `12` | Length of generated bin and request ids. |
763
- | `cleanupIntervalMs` | `60000` (1 min) | How often expired bins are swept. The timer is `unref()`'d so it never keeps the process alive. |
763
+ | `keyv` | `new Keyv()` | Keyv store for bins. The default keeps bins in memory. |
764
+
765
+ ### Storage
764
766
 
765
- ### Pluggable Storage
767
+ `MockHttpBins` stores each bin in [Keyv](https://github.com/jaredwray/keyv) 6. The default is `new Keyv()`. That store keeps the data in memory for this process.
766
768
 
767
- `BinManager` accepts a `store: BinStore` so you can plug in alternative
768
- backends (Redis, SQLite, etc.) without changing the rest of the codebase. The
769
- default `InMemoryBinStore` keeps state in process memory.
769
+ Pass a Keyv 6 instance when you want a different adapter. Install the adapter in your project. This package does not include an adapter. `@keyv/redis` 6 matches this Keyv version.
770
770
 
771
771
  ```typescript
772
- import {
773
- BinManager,
774
- InMemoryBinStore,
775
- type Bin,
776
- type BinStore,
777
- type CapturedRequest,
778
- } from '@jaredwray/mockhttp';
779
-
780
- class RedisBinStore implements BinStore {
781
- createBin(bin: Bin): void { /* SET bin:${bin.id} ... */ }
782
- getBin(id: string): Bin | undefined { /* GET ... */ }
783
- listBins(): Bin[] { /* SCAN ... */ }
784
- deleteBin(id: string): boolean { /* DEL ... */ }
785
- addRequest(binId: string, req: CapturedRequest, max: number): void {
786
- /* LPUSH bin:${binId}:requests + LTRIM to max */
787
- }
788
- getRequests(binId: string): CapturedRequest[] { /* LRANGE ... */ }
789
- getRequest(binId: string, reqId: string): CapturedRequest | undefined { /* ... */ }
790
- clearRequests(binId: string): void { /* DEL bin:${binId}:requests */ }
791
- cleanupExpired(now: number): string[] { /* scan + delete */ return []; }
792
- }
772
+ import Keyv from 'keyv';
773
+ import KeyvRedis from '@keyv/redis'; // pnpm add @keyv/redis
774
+ import { MockHttpBins } from '@jaredwray/mockhttp';
793
775
 
794
- mock.bins = new BinManager({ store: new RedisBinStore() });
776
+ mock.bins = new MockHttpBins({
777
+ keyv: new Keyv({ store: new KeyvRedis('redis://localhost:6379') }),
778
+ });
795
779
  ```
796
780
 
781
+ Each capture writes the full bin again. A large body or a high request limit makes that write large. Set `maxBodySize` and `maxRequestsPerBin` to limit the size. With the defaults, a full bin of binary bodies is about 17 MB.
782
+
783
+ Two processes that share one Keyv can lose a capture. This occurs when both processes write the same bin at the same time.
784
+
785
+ If Keyv does not store a write, the bin route returns `500` with the standard Fastify error body: `statusCode`, `error`, and `message`.
786
+
787
+ A failed read depends on the Keyv `error` listener. Without a listener, Keyv throws the store error and the route returns `500`. With a listener, Keyv does not throw. A failed read then looks like a missing bin, and the route returns `404`. `listBins()` returns only the bins that it read before the error.
788
+
789
+ `listBins()` throws an error when the adapter has no iterator.
790
+
791
+ An expired bin returns 404. The in-memory Keyv store deletes an expired bin only when a read or `listBins()` reaches it. Until then, the bin stays in memory.
792
+
797
793
  ### Configuration
798
794
  URL: https://mockhttp.org/docs/configuration/
799
795
 
@@ -1015,7 +1011,6 @@ new MockHttp(options?)
1015
1011
  - `logging?`: boolean - Enable logging (default: true)
1016
1012
  - `staticFiles?`: boolean - Serve files from the package `public/` directory (default: true)
1017
1013
  - `pluginTimeout?`: number - Fastify plugin boot timeout in milliseconds; `0` disables it (default: Fastify's timeout)
1018
- - `startBins?`: boolean - Start the request-bin cleanup timer during `initialize()` (default: true)
1019
1014
  - `httpBin?`: HttpBinOptions - Configure which httpbin routes to enable
1020
1015
  - `httpMethods?`: boolean - Enable HTTP method routes (default: true)
1021
1016
  - `redirects?`: boolean - Enable redirect routes (default: true)
@@ -1044,7 +1039,6 @@ new MockHttp(options?)
1044
1039
  - `logging`: boolean - Get/set logging enabled state
1045
1040
  - `staticFiles`: boolean - Get/set whether files from `public/` are served
1046
1041
  - `pluginTimeout`: number | undefined - Get/set Fastify plugin boot timeout
1047
- - `startBins`: boolean - Get/set whether bin cleanup starts during `initialize()`
1048
1042
  - `rateLimit`: RateLimitPluginOptions | undefined - Get/set rate limiting options
1049
1043
  - `httpBin`: HttpBinOptions - Get/set httpbin route options
1050
1044
  - `https`: HttpsOptions | undefined - Get/set HTTPS configuration
@@ -1053,7 +1047,7 @@ new MockHttp(options?)
1053
1047
  - `http1`: boolean - Get/set HTTP/1.1 fallback for HTTP/2 with HTTPS
1054
1048
  - `server`: FastifyInstance - Get/set the Fastify server instance
1055
1049
  - `taps`: TapManager - Get/set the TapManager instance
1056
- - `bins`: BinManager - Get/set the BinManager instance for request bins
1050
+ - `bins`: MockHttpBins - Get or set the request bins
1057
1051
 
1058
1052
  ### Methods
1059
1053
 
@@ -1133,57 +1127,55 @@ Register the request bin routes — management at `/bins` and capture at `/b/:id
1133
1127
 
1134
1128
  ## Bins (Request Capture)
1135
1129
 
1136
- Access the BinManager via `mockHttp.bins` to manage request bins programmatically. See [Request Bins](#request-bins) for usage examples.
1130
+ Use `mockHttp.bins` to control request bins. See [Request Bins](#request-bins) for usage examples.
1137
1131
 
1138
1132
  ### `bins.createBin()`
1139
1133
 
1140
- Create a new bin with the configured TTL.
1134
+ Create a new bin with the configured TTL. Throws when Keyv does not store the bin.
1141
1135
 
1142
- **Returns:** `Bin` — `{ id, createdAt, expiresAt, requestCount }`
1136
+ **Returns:** `Promise<Bin>` — `{ id, createdAt, expiresAt, requestCount }`
1143
1137
 
1144
1138
  ### `bins.getBin(id)`
1145
1139
 
1146
- Look up a bin by id. Returns `undefined` if the bin does not exist or has expired (and lazily removes the expired entry).
1140
+ Look up a bin by id. Returns `undefined` if the bin does not exist or Keyv has expired it. When the Keyv instance has an `error` listener, a failed read also returns `undefined`.
1147
1141
 
1148
- **Returns:** `Bin | undefined`
1142
+ **Returns:** `Promise<Bin | undefined>`
1149
1143
 
1150
1144
  ### `bins.listBins()`
1151
1145
 
1152
- List all non-expired bins.
1146
+ List the bins that Keyv still holds.
1153
1147
 
1154
- **Returns:** `Bin[]`
1148
+ **Returns:** `Promise<Bin[]>`
1155
1149
 
1156
1150
  ### `bins.deleteBin(id)`
1157
1151
 
1158
- Delete a bin and all its captured requests.
1152
+ Delete a bin and all its captured requests. The delete waits for captures already queued for that bin.
1159
1153
 
1160
- **Returns:** `boolean` — `true` if the bin existed
1154
+ **Returns:** `Promise<boolean>` — `true` when the bin existed
1161
1155
 
1162
1156
  ### `bins.getRequests(binId)`
1163
1157
 
1164
1158
  List captured requests for a bin, newest first.
1165
1159
 
1166
- **Returns:** `CapturedRequest[]`
1160
+ **Returns:** `Promise<CapturedRequest[]>`
1167
1161
 
1168
1162
  ### `bins.getRequest(binId, reqId)`
1169
1163
 
1170
1164
  Look up a single captured request.
1171
1165
 
1172
- **Returns:** `CapturedRequest | undefined`
1166
+ **Returns:** `Promise<CapturedRequest | undefined>`
1173
1167
 
1174
1168
  ### `bins.clearRequests(binId)`
1175
1169
 
1176
- Clear all captured requests in a bin (the bin itself is preserved).
1170
+ Clear all captured requests in a bin. The bin stays. Throws when Keyv does not store the change.
1177
1171
 
1178
- ### `bins.recordRequest(binId, raw)`
1179
-
1180
- Manually record a captured request. Returns `undefined` if the bin does not exist or is expired. Normally invoked by the capture route, but available for programmatic use.
1172
+ **Returns:** `Promise<boolean>` — `false` when the bin does not exist
1181
1173
 
1182
- **Returns:** `CapturedRequest | undefined`
1174
+ ### `bins.recordRequest(binId, raw)`
1183
1175
 
1184
- ### `bins.start()` / `bins.stop()`
1176
+ Manually record a captured request. Returns `undefined` if the bin does not exist. Throws when Keyv does not store the capture. Normally invoked by the capture route, but available for programmatic use.
1185
1177
 
1186
- Start or stop the periodic cleanup of expired bins. `MockHttp.start()` and `MockHttp.close()` call these automatically. The interval is `unref()`'d so it never keeps the Node process alive.
1178
+ **Returns:** `Promise<CapturedRequest | undefined>`
1187
1179
 
1188
1180
  ### `bins.maxBodySize` / `bins.maxRequestsPerBin` / `bins.defaultTtlMs`
1189
1181
 
@@ -1241,15 +1233,216 @@ A getter that returns whether there are any active injections.
1241
1233
 
1242
1234
  **Returns:** boolean - `true` if there are active injections, `false` otherwise
1243
1235
 
1244
- ### Hosted Service
1236
+ ### MockHttp.org Live
1245
1237
  URL: https://mockhttp.org/docs/hosted/
1246
1238
 
1247
1239
  # About mockhttp.org
1248
1240
 
1249
- [mockhttp.org](https://mockhttp.org) is a free hosted instance of this codebase for testing. It runs on a [Cloudflare Worker](https://developers.cloudflare.com/workers/) (no containers): documentation is served from Workers Assets, and mock APIs such as `/get` and `/post` run in the Worker. The service is globally available and rate-limited (1000 requests per minute per IP) to prevent abuse.
1241
+ [mockhttp.org](https://mockhttp.org) is a free hosted instance of this codebase for testing. It runs on a [Cloudflare Worker](/docs/how-to-deploy/cloudflare/) (no containers): documentation is served from Workers Assets, and mock APIs such as `/get` and `/post` run in the Worker. The service is globally available and rate-limited (1000 requests per minute per IP) to prevent abuse. Request bins on this service are stored in MongoDB Atlas, in the `bins` collection, so they are shared across isolates.
1242
+
1243
+ All [mockhttp.org](https://mockhttp.org) traffic stays on Cloudflare. A separate test app runs the Node server on [Wasmer Edge](/docs/how-to-deploy/wasmer/) at [mockhttp.wasmer.app](https://mockhttp.wasmer.app).
1250
1244
 
1251
1245
  Getting Started is served at `/` and `/docs`, other guides at `/docs/...`, and the interactive HTTP API reference at `/api`. Mock endpoints such as `/get` and `/post` are unchanged.
1252
1246
 
1247
+ ### Cloudflare
1248
+ URL: https://mockhttp.org/docs/how-to-deploy/cloudflare/
1249
+ Description: Deploy MockHTTP as a Cloudflare Worker. Workers Assets serve the docs site, and the Worker handles mock API routes.
1250
+
1251
+ # Deploy on Cloudflare
1252
+
1253
+ MockHTTP runs on Cloudflare as a Worker, not a container. [mockhttp.org](https://mockhttp.org) is this deployment: [Workers Assets](https://developers.cloudflare.com/workers/static-assets/) serve the documentation site, and the Worker handles mock APIs such as `/get` and `/post`.
1254
+
1255
+ The Worker entry is [`worker/index.ts`](https://github.com/jaredwray/mockhttp/blob/main/worker/index.ts). It listens with the Node.js compatibility layer and forwards each request into the Fastify app. Configuration lives in [`wrangler.jsonc`](https://github.com/jaredwray/mockhttp/blob/main/wrangler.jsonc).
1256
+
1257
+ ## What the Worker does differently
1258
+
1259
+ The Worker starts the same MockHTTP app as Node.js, with a few options set for the Workers runtime:
1260
+
1261
+ | Option | Worker value | Why |
1262
+ | --- | --- | --- |
1263
+ | `rateLimit` | `false` | [`@fastify/rate-limit`](https://github.com/fastify/fastify-rate-limit) is off. A Cloudflare rate limiting binding allows 1000 requests per 60 seconds per IP. |
1264
+ | `siteDistPath` | unused path | Fastify does not serve the docs. Workers Assets serve `site/dist`. |
1265
+ | `staticFiles` | `false` | Logos and other `public/` files are copied into `site/dist` and read through the `ASSETS` binding. |
1266
+ | `pluginTimeout` | `0` | Disables Fastify's plugin boot timeout. Those timers are unreliable on Workers. |
1267
+ | `logging` | `false` | Request logging is off. |
1268
+ | `autoDetectPort` | `false` | The app always listens on port 3000 inside the isolate. |
1269
+
1270
+ The rate limiter key is the `cf-connecting-ip` header, then the first address in `x-forwarded-for`, then `unknown`. A limited request returns `429` with `retry-after: 60`. That limiter is shared across isolates. Bins are not. The default store is in memory inside the isolate that handled the request, so a bin created on one request can be missing when another isolate or location handles the next one.
1271
+
1272
+ Taps are a library API (`mock.taps.inject`). They have no HTTP routes, so a deployed Worker has no way to inject one.
1273
+
1274
+ Assets are checked before the Worker. A file under `site/dist` (the docs site, favicon, logos) is served as a static asset. A path that is not a file, such as `/get` or `/post`, is handled by the Worker.
1275
+
1276
+ ## Deploy your own Worker
1277
+
1278
+ You need a Cloudflare account and permission to deploy Workers. This repository pins Wrangler to `4.127.0`.
1279
+
1280
+ Change `name` and `routes` in `wrangler.jsonc` before you deploy a fork. The route in this repo attaches the custom domain `mockhttp.org`. `workers_dev` is enabled, so a `*.workers.dev` hostname is available as well. Adjust the `RATE_LIMITER` binding if you want a different limit than 1000 requests per 60 seconds.
1281
+
1282
+ Also change `siteUrl` in `site/docula.config.ts`. The sitemap is a static file, and this repo sets it to `https://mockhttp.org`. The `migrations` block records a container class that this Worker no longer uses. A brand-new Worker can omit that block.
1283
+
1284
+ Wrangler bundles `worker/index.ts` from source, so you do not need `pnpm build` for this deploy. `pnpm website:build` produces `site/dist`.
1285
+
1286
+ ```bash
1287
+ pnpm install
1288
+ pnpm website:build
1289
+ pnpm prepare:worker-assets
1290
+ npx wrangler@4.127.0 deploy
1291
+ ```
1292
+
1293
+ `pnpm prepare:worker-assets` copies `public/` into `site/dist` so favicon and logo files ship with the docs site. Wrangler reads `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` from the environment when you are not logged in locally.
1294
+
1295
+ After deploy, open the workers.dev hostname or your custom domain and request `/get`. The docs are at `/docs`, and the OpenAPI reference is at `/api`.
1296
+
1297
+ ## Deploy from GitHub Actions
1298
+
1299
+ [`.github/workflows/deploy-site.yaml`](https://github.com/jaredwray/mockhttp/blob/main/.github/workflows/deploy-site.yaml) deploys this Worker on the `released` event (a published release, not a prerelease) and when the workflow is started by hand. The `production` environment provides `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID`. The deploy job installs production dependencies, downloads the built site, copies `public/` into `site/dist`, installs Wrangler `4.127.0`, and runs `wrangler deploy`.
1300
+
1301
+ That workflow does not deploy the [Wasmer](/docs/how-to-deploy/wasmer/) app. Docker image publishing is a separate workflow, documented in [Docker](/docs/how-to-deploy/docker/).
1302
+
1303
+ ### Docker
1304
+ URL: https://mockhttp.org/docs/how-to-deploy/docker/
1305
+ Description: Run MockHTTP from the jaredwray/mockhttp image, with Docker Compose, or by building the image from this repository.
1306
+
1307
+ # Deploy with Docker
1308
+
1309
+ The published image is [`jaredwray/mockhttp`](https://hub.docker.com/r/jaredwray/mockhttp) on Docker Hub. It runs the Node.js server and the documentation site in one container. The `latest` tag is `linux/amd64` only. Apple Silicon runs it under emulation. On arm64 Linux, build the image from this repository instead.
1310
+
1311
+ ## Run the image
1312
+
1313
+ ```bash
1314
+ docker run -d -p 3000:3000 jaredwray/mockhttp
1315
+ ```
1316
+
1317
+ The server listens on port 3000. Open `http://localhost:3000/get` for a mock response, `http://localhost:3000/docs` for these guides, and `http://localhost:3000/api` for the OpenAPI reference.
1318
+
1319
+ `pnpm docker:run` is `docker run -p 3000:3000 jaredwray/mockhttp`. It stays in the foreground. Docker pulls the Hub image when it is not already local.
1320
+
1321
+ Rate limiting is on: 1000 requests per minute. The allow list is only `127.0.0.1` and `::1`. A published port delivers traffic from the Docker bridge address, not from loopback inside the container, so those requests count against the limit. None of the environment variables below turn it off. Change or disable it with the library [`rateLimit` option](/docs/configuration/). A test suite that sends more than 1000 requests a minute to the published port will get `429` responses.
1322
+
1323
+ ## Docker Compose
1324
+
1325
+ ```yaml
1326
+ services:
1327
+ mockhttp:
1328
+ image: jaredwray/mockhttp:latest
1329
+ ports:
1330
+ - "3000:3000"
1331
+ ```
1332
+
1333
+ The Compose file in this repository publishes port 3001 and sets `PORT` so the process listens on that same port:
1334
+
1335
+ ```yaml
1336
+ services:
1337
+ mockhttp:
1338
+ image: jaredwray/mockhttp:latest
1339
+ ports:
1340
+ - "3001:3001"
1341
+ environment:
1342
+ - PORT=3001
1343
+ ```
1344
+
1345
+ `pnpm docker:compose:up` and `pnpm docker:compose:down` call the `docker-compose` binary. Current Docker installs often provide `docker compose` instead, which you can run against the same file.
1346
+
1347
+ ## Environment variables
1348
+
1349
+ The container starts `node dist/index.mjs`. That process reads these variables:
1350
+
1351
+ | Variable | Default | Effect |
1352
+ | --- | --- | --- |
1353
+ | `PORT` | `3000` | Port the server listens on. Match it to the published container port. |
1354
+ | `HOST` | `0.0.0.0` | Bind address. |
1355
+ | `LOGGING` | enabled | Set to `false` to turn logging off. |
1356
+ | `HTTP2` | off | Set to `true` to enable HTTP/2 cleartext (h2c). The image does not load a TLS certificate. |
1357
+ | `AUTO_DETECT_PORT` | `true` | Set to `false` to keep `PORT` even when that port is already in use. |
1358
+
1359
+ HTTPS certificates are configured in the [library](/docs/https/), not through container environment variables. Put a TLS proxy in front of the container when you need HTTPS.
1360
+
1361
+ ## Build the image
1362
+
1363
+ The [`Dockerfile`](https://github.com/jaredwray/mockhttp/blob/main/Dockerfile) copies `node_modules`, `package.json`, `public/`, `dist/`, and `site/dist/` from the build context. Build the package and the docs site before `docker build`.
1364
+
1365
+ ```bash
1366
+ pnpm install
1367
+ pnpm build
1368
+ pnpm website:build
1369
+ docker build -t jaredwray/mockhttp .
1370
+ docker run -d -p 3000:3000 jaredwray/mockhttp
1371
+ ```
1372
+
1373
+ `pnpm docker:build` is the `docker build` step. The image is based on Node.js 24 Alpine, runs as the `node` user, exposes port 3000, and starts `node dist/index.mjs`.
1374
+
1375
+ ## Publish to Docker Hub
1376
+
1377
+ [`.github/workflows/docker-publish.yaml`](https://github.com/jaredwray/mockhttp/blob/main/.github/workflows/docker-publish.yaml) runs on the `released` event (a published release, not a prerelease) and when the workflow is started by hand. It builds `jaredwray/mockhttp:<version>` and `jaredwray/mockhttp:latest` with `docker build` on `ubuntu-latest`, pushes both tags, and updates the Docker Hub description from `DOCKER.md`. That build is `linux/amd64`.
1378
+
1379
+ The same Node server can run on [Wasmer Edge](/docs/how-to-deploy/wasmer/). The public site at [mockhttp.org](https://mockhttp.org) is the [Cloudflare Worker](/docs/how-to-deploy/cloudflare/), not this image.
1380
+
1381
+ ### Wasmer
1382
+ URL: https://mockhttp.org/docs/how-to-deploy/wasmer/
1383
+ Description: Deploy the MockHTTP Node.js server to Wasmer Edge with app.yaml, Anybuild, and wasmer deploy.
1384
+
1385
+ # Deploy on Wasmer
1386
+
1387
+ Wasmer Edge runs the same Node.js server as [Docker](/docs/how-to-deploy/docker/). The test app for this repository is [mockhttp.wasmer.app](https://mockhttp.wasmer.app).
1388
+
1389
+ This deploy does not publish the [Cloudflare Worker](/docs/how-to-deploy/cloudflare/). [mockhttp.org](https://mockhttp.org) stays on Cloudflare.
1390
+
1391
+ ## App configuration
1392
+
1393
+ [`app.yaml`](https://github.com/jaredwray/mockhttp/blob/main/app.yaml) describes the Edge app:
1394
+
1395
+ ```yaml
1396
+ kind: wasmer.io/App.v0
1397
+ name: mockhttp
1398
+ owner: jaredwray
1399
+ package: .
1400
+ env:
1401
+ HOST: "0.0.0.0"
1402
+ LOGGING: "false"
1403
+ AUTO_DETECT_PORT: "false"
1404
+ ```
1405
+
1406
+ `package: .` deploys the repository directory. `HOST` binds the server on all interfaces. Logging is off. Wasmer injects `PORT`. `AUTO_DETECT_PORT=false` keeps that port instead of scanning for another one.
1407
+
1408
+ Change `name` and `owner` before you deploy your own app. The environment variables are the same ones the Docker image reads (`PORT`, `HOST`, `LOGGING`, `HTTP2`, and `AUTO_DETECT_PORT`). See [Docker](/docs/how-to-deploy/docker/) for what each one does.
1409
+
1410
+ ## Build with Anybuild
1411
+
1412
+ [`Anybuild`](https://github.com/jaredwray/mockhttp/blob/main/Anybuild) tells Wasmer how to build the package on a remote builder:
1413
+
1414
+ - Node.js 24 and pnpm `12.12.1`
1415
+ - Build command: `pnpm run build && pnpm website:build`
1416
+ - Start command: `node dist/index.mjs`
1417
+
1418
+ The build command produces `dist/` and `site/dist/` while Docula is still installed. Wasmer then prunes dev dependencies. The docs site has to exist before that prune, because Docula is a dev dependency.
1419
+
1420
+ ## Deploy from the CLI
1421
+
1422
+ Install the [Wasmer CLI](https://docs.wasmer.io/install) and log in, then from the repository root:
1423
+
1424
+ ```bash
1425
+ wasmer deploy --build-remote
1426
+ ```
1427
+
1428
+ `--build-remote` uploads the source and runs the Anybuild file on Wasmer's builders. When the deploy finishes, the CLI prints the app URL.
1429
+
1430
+ ## Deploy from GitHub Actions
1431
+
1432
+ [`.github/workflows/deploy-wasmer.yaml`](https://github.com/jaredwray/mockhttp/blob/main/.github/workflows/deploy-wasmer.yaml) runs on the `released` event (a published release, not a prerelease) and when the workflow is started by hand. The `wasmer` environment provides `WASMER_TOKEN`. The deploy step runs:
1433
+
1434
+ ```bash
1435
+ wasmer deploy --non-interactive --build-remote
1436
+ ```
1437
+
1438
+ ## Runtime behavior
1439
+
1440
+ Bins use `new Keyv()` by default. The store keeps each bin in memory on the instance that created it. Give `MockHttpBins` a shared Keyv when bins must be visible on more than one instance. Two processes can lose a capture when they write the same bin at the same time. Taps are a library API (`mock.taps.inject`) and have no HTTP routes, so a deployed app cannot inject one.
1441
+
1442
+ `@fastify/rate-limit` is on at 1000 requests per minute. It keys on the address that connects to the process. The server does not trust proxy headers, and the allow list is only `127.0.0.1` and `::1`. On the test app, `/ip` reports `127.0.0.100`, which is the edge proxy rather than the caller, and the response includes `x-ratelimit-limit: 1000`. Clients that share that proxy address share one limit on that instance. This is not the Cloudflare limiter used by [mockhttp.org](https://mockhttp.org).
1443
+
1444
+ The process listens with HTTP. Wasmer terminates TLS for the `*.wasmer.app` hostname.
1445
+
1253
1446
  ## API Reference
1254
1447
  URL: https://mockhttp.org/api
1255
1448
 
@@ -1260,7 +1453,7 @@ OpenAPI Spec Source: /home/runner/work/mockhttp/mockhttp/site/api/swagger.json
1260
1453
  "info": {
1261
1454
  "title": "Mock HTTP API",
1262
1455
  "description": "\nA simple HTTP server that can be used to mock HTTP responses for testing purposes. Inspired by [httpbin](https://httpbin.org/) and built using `nodejs` and `fastify` with the idea of running it via https://mockhttp.org, via docker `jaredwray/mockhttp`, or nodejs `npm install @jaredwray/mockhttp`.\n\n* [GitHub Repository](https://github.com/jaredwray/mockhttp)\n* [Docker Image](https://hub.docker.com/r/jaredwray/mockhttp)\n* [NPM Package](https://www.npmjs.com/package/@jaredwray/mockhttp)\n\n# About mockhttp.org\n\n[mockhttp.org](https://mockhttp.org) is a free hosted instance of this codebase for testing. It runs on a Cloudflare Worker (no containers): documentation is served from Workers Assets, and mock APIs such as `/get` and `/post` run in the Worker. The service is globally available and rate-limited (1000 requests per minute per IP) to prevent abuse.\n",
1263
- "version": "2.0.0"
1456
+ "version": "3.0.0"
1264
1457
  },
1265
1458
  "components": {
1266
1459
  "schemas": {}
@@ -1394,7 +1587,11 @@ OpenAPI Spec Source: /home/runner/work/mockhttp/mockhttp/site/api/swagger.json
1394
1587
  "type": "string"
1395
1588
  },
1396
1589
  {
1397
- "type": "null"
1590
+ "type": "object",
1591
+ "nullable": true,
1592
+ "enum": [
1593
+ null
1594
+ ]
1398
1595
  }
1399
1596
  ]
1400
1597
  }
@@ -5636,10 +5833,8 @@ OpenAPI Spec Source: /home/runner/work/mockhttp/mockhttp/site/api/swagger.json
5636
5833
  "type": "integer"
5637
5834
  },
5638
5835
  "body": {
5639
- "type": [
5640
- "null",
5641
- "string"
5642
- ]
5836
+ "type": "string",
5837
+ "nullable": true
5643
5838
  },
5644
5839
  "bodyEncoding": {
5645
5840
  "type": "string",
@@ -5806,10 +6001,8 @@ OpenAPI Spec Source: /home/runner/work/mockhttp/mockhttp/site/api/swagger.json
5806
6001
  "type": "integer"
5807
6002
  },
5808
6003
  "body": {
5809
- "type": [
5810
- "null",
5811
- "string"
5812
- ]
6004
+ "type": "string",
6005
+ "nullable": true
5813
6006
  },
5814
6007
  "bodyEncoding": {
5815
6008
  "type": "string",
@@ -6746,6 +6939,124 @@ OpenAPI Spec Source: /home/runner/work/mockhttp/mockhttp/site/api/swagger.json
6746
6939
  ## Changelog
6747
6940
  URL: https://mockhttp.org/changelog
6748
6941
 
6942
+ ### v3.0.0
6943
+ URL: https://mockhttp.org/changelog/v3-0-0
6944
+ Date: October 20, 2026
6945
+ Tag: Release
6946
+
6947
+ ## @jaredwray/mockhttp@3.0.0 — 2026-10-19
6948
+
6949
+ Request bins now use Keyv via MockHttpBins, and the Worker can store them in MongoDB Atlas.
6950
+
6951
+ ### ⚠ BREAKING CHANGES
6952
+ - Replace `BinManager`, `BinStore`, and `InMemoryBinStore` with async `MockHttpBins`. `mock.bins.start()` and `mock.bins.stop()` are removed. The default `maxBodySize` is 128 KiB (was 1 MiB). Node engines are now `>=22.19.0` (was `>=22.18.0`). (`b1ddcc0`, #246)
6953
+ Migration: assign `mock.bins = new MockHttpBins({ keyv })` before `start()` (default store is `new Keyv()`) and `await` the bin methods.
6954
+
6955
+ ```ts
6956
+ import Keyv from "keyv";
6957
+ import { MockHttp, MockHttpBins } from "@jaredwray/mockhttp";
6958
+
6959
+ const mock = new MockHttp();
6960
+ mock.bins = new MockHttpBins({ keyv: new Keyv(), maxBodySize: 128 * 1024 });
6961
+ await mock.start();
6962
+ const bin = await mock.bins.createBin();
6963
+ ```
6964
+
6965
+ ### Features
6966
+ - Store Worker request bins in MongoDB Atlas when `MONGODB_URI` is set (`266157b`, #247)
6967
+
6968
+ ```bash
6969
+ # Cloudflare Worker secret. Bins go in the Atlas `bins` collection.
6970
+ # Leave MONGODB_URI unset to keep the in-memory Keyv.
6971
+ wrangler secret put MONGODB_URI
6972
+ ```
6973
+
6974
+ ### Bug Fixes
6975
+ - security - upgrade fastify proxy-addr, fast-uri, find-my-way, undici (`9c52869`, #233)
6976
+ - security - upgrade handlebars, source-map-js (`28aa662`, #236)
6977
+
6978
+ ### Internal
6979
+ - sync defense-in-depth files from agentic (`7fb3ce4`, #234)
6980
+ - upgrade code quality dependencies (`a57b990`, #235)
6981
+ - upgrade TypeScript and build tooling (`b11fa0f`, #237)
6982
+ - upgrade monorepo tooling (`58e8596`, #238)
6983
+ - upgrade GitHub Actions (`58bc448`, #239)
6984
+ - upgrade Dev Container images (`2047c3c`, #240)
6985
+ - upgrade Fastify dependencies (`3e93bea`, #241)
6986
+ - upgrade hookified (`d938e6e`, #242)
6987
+ - upgrade pino and pino-pretty (`c2a94ad`, #243)
6988
+ - upgrade Docker Node.js runtime image (`cd5decf`, #244)
6989
+ - pin the Compose image (`74ad4ea`, #245)
6990
+
6991
+ ### Contributors
6992
+ - @jaredwray (11)
6993
+ - @cursoragent (5)
6994
+
6995
+ ### Full List of Changes
6996
+ - fix: security - upgrade fastify proxy-addr, fast-uri, find-my-way, undici by @jaredwray in #233
6997
+ - mono - chore: sync defense-in-depth files from agentic by @jaredwray in #234
6998
+ - mono - chore: upgrade code quality dependencies by @jaredwray in #235
6999
+ - fix: security - upgrade handlebars, source-map-js by @jaredwray in #236
7000
+ - mono - chore: upgrade TypeScript and build tooling by @jaredwray in #237
7001
+ - mono - chore: upgrade monorepo tooling by @jaredwray in #238
7002
+ - mono - chore: upgrade GitHub Actions by @jaredwray in #239
7003
+ - mono - chore: upgrade Dev Container images by @jaredwray in #240
7004
+ - mono - chore: upgrade Fastify dependencies by @jaredwray in #241
7005
+ - mono - chore: upgrade hookified by @jaredwray in #242
7006
+ - mono - chore: upgrade pino and pino-pretty by @jaredwray in #243
7007
+ - mono - chore: upgrade Docker Node.js runtime image by @jaredwray in #244
7008
+ - mono - chore: pin the Compose image by @jaredwray in #245
7009
+ - Store request bins in Keyv by @jaredwray in #246
7010
+ - Store Worker request bins in MongoDB Atlas by @jaredwray in #247
7011
+
7012
+ **Full diff:** [https://github.com/jaredwray/mockhttp/compare/v2.1.0...v3.0.0](https://github.com/jaredwray/mockhttp/compare/v2.1.0...v3.0.0)
7013
+
7014
+ ### v2.1.0
7015
+ URL: https://mockhttp.org/changelog/v2-1-0
7016
+ Date: October 3, 2026
7017
+ Tag: Release
7018
+
7019
+ ## @jaredwray/mockhttp@2.1.0 — 2026-10-03
7020
+
7021
+ Add AUTO_DETECT_PORT=false so the process keeps PORT, and include the docs site in the npm package.
7022
+
7023
+ ### Features
7024
+ - Deploy the Node server to Wasmer Edge (#225)
7025
+
7026
+ ```bash
7027
+ PORT=3000 AUTO_DETECT_PORT=false node dist/index.mjs
7028
+ ```
7029
+
7030
+ ### Bug Fixes
7031
+ - include Docula site in the npm tarball (#222)
7032
+ - deploy Wasmer with a remote build (#226)
7033
+ - deploy Wasmer through the Anybuild file (#228)
7034
+
7035
+ ### Documentation
7036
+ - add Drydock review badge to README (#224)
7037
+ - rename Hosted Service page to MockHttp.org Live (#230)
7038
+ - add How to Deploy guides for Cloudflare, Docker, and Wasmer (#229)
7039
+
7040
+ ### Internal
7041
+ - root - defense - close catalog gaps from current skill (#221)
7042
+ - Publish the GitHub Actions build to Wasmer (#227)
7043
+
7044
+ ### Contributors
7045
+ - @jaredwray (owner)
7046
+
7047
+ ### Full List of Changes
7048
+ - root - chore: defense - close catalog gaps from current skill by @jaredwray in #221
7049
+ - fix: include Docula site in the npm tarball by @jaredwray in #222
7050
+ - docs: add Drydock review badge to README by @jaredwray in #224
7051
+ - Deploy the Node server to Wasmer Edge by @jaredwray in #225
7052
+ - Fix the Wasmer deploy by using a remote build by @jaredwray in #226
7053
+ - Publish the GitHub Actions build to Wasmer by @jaredwray in #227
7054
+ - Deploy Wasmer with the Anybuild file by @jaredwray in #228
7055
+ - docs: rename Hosted Service to MockHttp.org Live by @jaredwray in #230
7056
+ - docs: add How to Deploy guides for Cloudflare, Docker, and Wasmer by @jaredwray in #229
7057
+
7058
+ **Full diff:** [https://github.com/jaredwray/mockhttp/compare/v2.0.0...v2.1.0](https://github.com/jaredwray/mockhttp/compare/v2.0.0...v2.1.0)
7059
+
6749
7060
  ### v2.0.0
6750
7061
  URL: https://mockhttp.org/changelog/v2-0-0
6751
7062
  Date: September 18, 2026
@@ -7377,39 +7688,3 @@ Tag: Release
7377
7688
 
7378
7689
 
7379
7690
  **Full Changelog**: [https://github.com/jaredwray/mockhttp/compare/v0.2.0...v0.2.1](https://github.com/jaredwray/mockhttp/compare/v0.2.0...v0.2.1)
7380
-
7381
- ### v0.2.0
7382
- URL: https://mockhttp.org/changelog/v0-2-0
7383
- Date: December 13, 2024
7384
- Tag: Release
7385
-
7386
- ## What's Changed
7387
- * response inspection by @jaredwray in [https://github.com/jaredwray/mockhttp/pull/14](https://github.com/jaredwray/mockhttp/pull/14)
7388
-
7389
-
7390
- **Full Changelog**: [https://github.com/jaredwray/mockhttp/compare/v0.1.0...v0.2.0](https://github.com/jaredwray/mockhttp/compare/v0.1.0...v0.2.0)
7391
-
7392
- ### v0.1.0
7393
- URL: https://mockhttp.org/changelog/v0-1-0
7394
- Date: December 12, 2024
7395
- Tag: Release
7396
-
7397
- ## What's Changed
7398
- * adding in contributing and code of conduct by @jaredwray in [https://github.com/jaredwray/mockhttp/pull/1](https://github.com/jaredwray/mockhttp/pull/1)
7399
- * migrating to correct folder format by @jaredwray in [https://github.com/jaredwray/mockhttp/pull/2](https://github.com/jaredwray/mockhttp/pull/2)
7400
- * adding in logo and favicon by @jaredwray in [https://github.com/jaredwray/mockhttp/pull/3](https://github.com/jaredwray/mockhttp/pull/3)
7401
- * adding in headers and user-agent endpoints by @jaredwray in [https://github.com/jaredwray/mockhttp/pull/4](https://github.com/jaredwray/mockhttp/pull/4)
7402
- * moving to scalar by @jaredwray in [https://github.com/jaredwray/mockhttp/pull/5](https://github.com/jaredwray/mockhttp/pull/5)
7403
- * adding in status codes by @jaredwray in [https://github.com/jaredwray/mockhttp/pull/6](https://github.com/jaredwray/mockhttp/pull/6)
7404
- * adding in unit tests and workflows by @jaredwray in [https://github.com/jaredwray/mockhttp/pull/7](https://github.com/jaredwray/mockhttp/pull/7)
7405
- * fixing the title that had an extra space by @jaredwray in [https://github.com/jaredwray/mockhttp/pull/8](https://github.com/jaredwray/mockhttp/pull/8)
7406
- * moving to scalar locally instead of via CDN by @jaredwray in [https://github.com/jaredwray/mockhttp/pull/9](https://github.com/jaredwray/mockhttp/pull/9)
7407
- * adding in dynamic version from package.json by @jaredwray in [https://github.com/jaredwray/mockhttp/pull/10](https://github.com/jaredwray/mockhttp/pull/10)
7408
- * adding in fastify helmet by @jaredwray in [https://github.com/jaredwray/mockhttp/pull/11](https://github.com/jaredwray/mockhttp/pull/11)
7409
- * upgrading xo to 0.60.0 by @jaredwray in [https://github.com/jaredwray/mockhttp/pull/12](https://github.com/jaredwray/mockhttp/pull/12)
7410
- * updating build to tsup and upgrading vitest by @jaredwray in [https://github.com/jaredwray/mockhttp/pull/13](https://github.com/jaredwray/mockhttp/pull/13)
7411
-
7412
- ## New Contributors
7413
- * @jaredwray made their first contribution in [https://github.com/jaredwray/mockhttp/pull/1](https://github.com/jaredwray/mockhttp/pull/1)
7414
-
7415
- **Full Changelog**: [https://github.com/jaredwray/mockhttp/commits/v0.1.0](https://github.com/jaredwray/mockhttp/commits/v0.1.0)