ofetch HTTP client mocked with MSW: working versions (tested 2026-10-15)
Call APIs with ofetch against an MSW 2 HTTP worker in Node, covering JSON responses, query params and error status mapping offline.
passed2 passed · 0 failed2026-10-15 15:01 UTC- Status
- passed
- Last passed
- 2026-10-15 15:01 UTC
- Tests
- 2 / 2
- Language
- TypeScript
- Runtime
- node v22.23.3
npm install --no-audit --no-fund --ignore-scripts=false npm test
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38755782594
The code
Exactly the files that passed. Your agent can pull them with get_recipe("ofetch-msw-client") (install), or see them on GitHub.
{
"name": "ofetch-msw-client",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"ofetch": "1.5.1",
"msw": "3.0.3"
},
"devDependencies": {
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
import { ofetch, type FetchOptions } from "ofetch";
export type Item = { id: number; name: string };
export function createClient(baseURL: string, fetchImpl?: typeof fetch) {
const api = ofetch.create({ baseURL, fetch: fetchImpl as typeof fetch });
return {
list: (q?: string) => api<Item[]>("/items", { query: q ? { q } : undefined }),
get: (id: number) => api<Item>(`/items/${id}`),
create: (name: string) => api<Item>("/items", { method: "POST", body: { name } }),
};
}
export type Client = ReturnType<typeof createClient>;
import { test, before, after } from "node:test";
import assert from "node:assert/strict";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { createClient } from "../src/client.ts";
const items = [{ id: 1, name: "bolt" }];
const server = setupServer(
http.get("http://local/items", ({ request }) => {
const q = new URL(request.url).searchParams.get("q");
return HttpResponse.json(q ? items.filter((i) => i.name.includes(q)) : items);
}),
http.get("http://local/items/:id", ({ params }) => {
const item = items.find((i) => i.id === Number(params.id));
if (!item) return HttpResponse.json({ error: "missing" }, { status: 404 });
return HttpResponse.json(item);
}),
http.post("http://local/items", async ({ request }) => {
const body = await request.json() as { name: string };
const item = { id: items.length + 1, name: body.name };
items.push(item);
return HttpResponse.json(item, { status: 201 });
}),
);
before(() => server.listen({ onUnhandledRequest: "error" }));
after(() => server.close());
test("lists, filters, creates", async () => {
const client = createClient("http://local");
assert.equal((await client.list()).length, 1);
assert.equal((await client.list("bo"))[0].name, "bolt");
const created = await client.create("nut");
assert.equal(created.name, "nut");
assert.equal((await client.get(created.id)).name, "nut");
});
test("maps 404 to FetchError", async () => {
const client = createClient("http://local");
await assert.rejects(() => client.get(999), (e: any) => e.status === 404 || e.response?.status === 404 || String(e).includes("404"));
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
graphql-request client with typed operations, mocked by MSW graphql handlers
Call a GraphQL API with graphql-request (variables, headers, error handling) and test offline with MSW's graphql.link(url).query/mutation handlers (msw 3 API). graphql stays on 16.x: graphql-request 7 peers on graphql 14 - 16.
ky HTTP client with hooks and retries, tested with MSW + Vitest
A ky 2 API client (prefix option, state-object hooks) with auth hooks, retry on 5xx and typed JSON, tested offline with MSW's Node server in Vitest.
openapi-fetch typed client with MSW
Call a typed OpenAPI paths client with openapi-fetch against MSW handlers, covering GET/POST and error status offline.
Redux Toolkit + RTK Query with tag invalidation and optimistic updates, tested with MSW
An RTK Query API slice with auth headers from Redux state, tag-based cache invalidation and an optimistic update with rollback, tested offline in Vitest against MSW 3 request handlers without rendering React.