ky HTTP client with hooks and retries, tested with MSW + Vitest: working versions (tested 2026-10-15)
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.
passed3 passed · 0 failed2026-10-15 13:30 UTC- Status
- passed
- Last passed
- 2026-10-15 13:30 UTC
- Tests
- 3 / 3
- 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 38742087302
The code
Exactly the files that passed. Your agent can pull them with get_recipe("ky-msw-vitest") (install), or see them on GitHub.
{
"name": "ky-msw-vitest",
"private": true,
"type": "module",
"scripts": {
"test": "vitest run --reporter=default --reporter=json --outputFile=test-report.json"
},
"dependencies": {
"ky": "2.2.0"
},
"devDependencies": {
"@types/node": "24.19.2",
"vitest": "5.0.4",
"msw": "3.0.3"
}
}
import ky from "ky";
export function createApi(prefixUrl: string, getToken: () => string) {
return ky.create({
prefix: prefixUrl,
timeout: 5000,
retry: { limit: 2, methods: ["get"], statusCodes: [500, 502, 503, 504], backoffLimit: 0 },
hooks: {
beforeRequest: [({ request }) => { request.headers.set("authorization", `Bearer ${getToken()}`); }],
},
});
}
export type Repo = { id: number; name: string; stars: number };
export const getRepo = (api: ReturnType<typeof createApi>, name: string) => api.get(`repos/${name}`).json<Repo>();
import { describe, it, expect, beforeAll, afterAll, afterEach } from "vitest";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { HTTPError } from "ky";
import { createApi, getRepo } from "../src/api";
const BASE = "https://api.example.com";
let calls = 0;
const server = setupServer();
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => { server.resetHandlers(); calls = 0; });
afterAll(() => server.close());
const api = createApi(BASE, () => "tok");
describe("ky client", () => {
it("sends auth and parses json", async () => {
server.use(http.get(`${BASE}/repos/:name`, ({ params, request }) => {
expect(request.headers.get("authorization")).toBe("Bearer tok");
return HttpResponse.json({ id: 1, name: params.name, stars: 5 });
}));
expect(await getRepo(api, "pp")).toEqual({ id: 1, name: "pp", stars: 5 });
});
it("retries 503 then succeeds", async () => {
server.use(http.get(`${BASE}/repos/:name`, () => (++calls < 3 ? new HttpResponse(null, { status: 503 }) : HttpResponse.json({ id: 2, name: "x", stars: 0 }))));
expect((await getRepo(api, "x")).id).toBe(2);
expect(calls).toBe(3);
});
it("throws HTTPError on 404 without retrying", async () => {
server.use(http.get(`${BASE}/repos/:name`, () => { calls++; return new HttpResponse(null, { status: 404 }); }));
await expect(getRepo(api, "nope")).rejects.toBeInstanceOf(HTTPError);
expect(calls).toBe(1);
});
});
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
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.
Astro 7 component rendering tests with the Container API (Vitest)
Render Astro components to HTML in Vitest with the Container API and getViteConfig, checking props, slots and conditional markup.
AWS SDK v3 DynamoDB document client and SQS events, tested with Vitest matchers
A single-table DynamoDB order service on @aws-sdk/lib-dynamodb with conditional puts, begins_with queries and optimistic locking, publishing SQS events, tested offline with aws-sdk-client-mock and aws-sdk-client-mock-vitest matchers.
Effect 4 Schema classes, typed errors and retries, tested with @effect/vitest
Effect 4 Schema.Class models with checks, brands, FiniteFromString and fromJsonString for decode and encode, TaggedError failures, and Effect.retry with exponential backoff verified on the TestClock using @effect/vitest.