graphql-request client with typed operations, mocked by MSW graphql handlers: working versions (tested 2026-10-15)
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.
passed3 passed · 0 failed2026-10-15 13:22 UTC- Status
- passed
- Last passed
- 2026-10-15 13:22 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 38741369695
The code
Exactly the files that passed. Your agent can pull them with get_recipe("graphql-request-msw") (install), or see them on GitHub.
{
"name": "graphql-request-msw",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"graphql-request": "7.4.0",
"graphql": "16.14.2"
},
"devDependencies": {
"tsx": "4.23.15",
"@types/node": "24.19.2",
"msw": "3.0.3"
}
}
import { GraphQLClient, gql, ClientError } from "graphql-request";
export const client = (url: string, token: string) => new GraphQLClient(url, { headers: { authorization: `Bearer ${token}` } });
const GetRepo = gql`query GetRepo($name: String!) { repo(name: $name) { name stars } }`;
const Star = gql`mutation Star($name: String!) { star(name: $name) { name stars } }`;
type Repo = { name: string; stars: number };
export const getRepo = async (c: GraphQLClient, name: string) => (await c.request<{ repo: Repo | null }>(GetRepo, { name })).repo;
export const star = async (c: GraphQLClient, name: string) => (await c.request<{ star: Repo }>(Star, { name })).star;
export function errorCode(e: unknown) {
return e instanceof ClientError ? (e.response.errors?.[0]?.extensions as any)?.code ?? "UNKNOWN" : "NETWORK";
}
import { test, before, after, afterEach } from "node:test";
import assert from "node:assert/strict";
import { HttpResponse } from "msw";
import { graphql } from "msw/graphql"; // msw 3 moved graphql handlers to a subpath
import { setupServer } from "msw/node";
import { client, errorCode, getRepo, star } from "../src/api.ts";
const URL_ = "https://api.example.com/graphql";
const repos: Record<string, number> = { provenpin: 5 };
const api = graphql.link(URL_); // msw 3: handlers are scoped to an endpoint
const server = setupServer(
api.query("GetRepo", ({ variables, request }) => {
if (request.headers.get("authorization") !== "Bearer t") return HttpResponse.json({ errors: [{ message: "nope", extensions: { code: "UNAUTHENTICATED" } }] });
const stars = repos[variables.name as string];
return HttpResponse.json({ data: { repo: stars === undefined ? null : { name: variables.name, stars } } });
}),
api.mutation("Star", ({ variables }) => HttpResponse.json({ data: { star: { name: variables.name, stars: ++repos[variables.name as string] } } })),
);
before(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
after(() => server.close());
test("query with variables", async () => {
assert.deepEqual(await getRepo(client(URL_, "t"), "provenpin"), { name: "provenpin", stars: 5 });
assert.equal(await getRepo(client(URL_, "t"), "missing"), null);
});
test("mutation", async () => {
assert.equal((await star(client(URL_, "t"), "provenpin")).stars, 6);
});
test("GraphQL errors surface with their code", async () => {
const e = await getRepo(client(URL_, "bad"), "provenpin").catch((x) => x);
assert.equal(errorCode(e), "UNAUTHENTICATED");
});
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
Apollo Server 5 resolvers with per-request DataLoader batching
An Apollo Server 5 schema whose nested resolvers use per-request DataLoader instances to batch and dedupe lookups (no N+1), with GraphQLError extension codes and cache invalidation after a mutation, tested in-process with executeOperation.
GraphQL Yoga 5 schema, context and errors tested via fetch
Build a GraphQL Yoga server with SDL resolvers, per-request context auth and GraphQLError codes, tested in-process with yoga.fetch.
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.
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.
All graphql-request recipesAll graphql recipesAll MSW recipes