GraphQL Yoga 5 schema, context and errors tested via fetch: working versions (tested 2026-10-15)
Build a GraphQL Yoga server with SDL resolvers, per-request context auth and GraphQLError codes, tested in-process with yoga.fetch.
passed4 passed · 0 failed2026-10-15 11:54 UTC- Status
- passed
- Last passed
- 2026-10-15 11:54 UTC
- Tests
- 4 / 4
- Language
- TypeScript
- Runtime
- node v22.23.3
pinned versions4
graphql-yoga@5.24.5graphql@17.0.2tsx@4.23.15@types/node@24.19.2
run it
npm install --no-audit --no-fund --ignore-scripts=false npm test
test environment
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38730348195
The code
Exactly the files that passed. Your agent can pull them with get_recipe("graphql-yoga-schema-context") (install), or see them on GitHub.
package.json
{
"name": "graphql-yoga-schema-context",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"graphql-yoga": "5.24.5",
"graphql": "17.0.2"
},
"devDependencies": {
"tsx": "4.23.15",
"@types/node": "24.19.2"
}
}
src/yoga.ts
import { createSchema, createYoga } from "graphql-yoga";
import { GraphQLError } from "graphql";
type Ctx = { userId: string | null };
const books = [{ id: "1", title: "SICP", ownerId: "ada" }];
export const yoga = createYoga<{}, Ctx>({
context: ({ request }) => ({ userId: request.headers.get("x-user-id") }),
schema: createSchema<Ctx>({
typeDefs: /* GraphQL */ `
type Book { id: ID!, title: String! }
type Query { books: [Book!]!, myBooks: [Book!]! }
type Mutation { addBook(title: String!): Book! }
`,
resolvers: {
Query: {
books: () => books,
myBooks: (_p, _a, ctx) => {
if (!ctx.userId) throw new GraphQLError("Sign in first", { extensions: { code: "UNAUTHENTICATED" } });
return books.filter((b) => b.ownerId === ctx.userId);
},
},
Mutation: {
addBook: (_p, { title }: { title: string }, ctx) => {
if (!ctx.userId) throw new GraphQLError("Sign in first", { extensions: { code: "UNAUTHENTICATED" } });
const b = { id: String(books.length + 1), title, ownerId: ctx.userId };
books.push(b);
return b;
},
},
},
}),
maskedErrors: true, // unexpected errors are hidden; GraphQLError messages pass through
});
test/yoga.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { yoga } from "../src/yoga.ts";
async function gql(query: string, user?: string, variables?: object) {
const res = await yoga.fetch("http://yoga/graphql", { method: "POST", headers: { "content-type": "application/json", ...(user ? { "x-user-id": user } : {}) }, body: JSON.stringify({ query, variables }) });
return res.json();
}
test("public query", async () => {
assert.deepEqual((await gql("{ books { title } }")).data.books[0], { title: "SICP" });
});
test("auth error carries a code", async () => {
const r = await gql("{ myBooks { id } }");
assert.equal(r.errors[0].extensions.code, "UNAUTHENTICATED");
});
test("mutation with variables and context", async () => {
const r = await gql("mutation($t: String!) { addBook(title: $t) { id title } }", "bob", { t: "TAOCP" });
assert.equal(r.data.addBook.title, "TAOCP");
assert.equal((await gql("{ myBooks { title } }", "bob")).data.myBooks.length, 1);
});
test("validation errors for bad queries", async () => {
assert.ok((await gql("{ nope }")).errors.length);
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "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.