Claude Code for GraphQL Yoga: Server, Schema, and Subscriptions — Claude Skills 360 Blog
Blog / Backend / Claude Code for GraphQL Yoga: Server, Schema, and Subscriptions
Backend

Claude Code for GraphQL Yoga: Server, Schema, and Subscriptions

Published: February 19, 2027
Read time: 8 min read
By: Claude Skills 360

GraphQL Yoga is a spec-compliant GraphQL server built on fetch API — it runs on Node.js, Cloudflare Workers, Deno, Bun, and Next.js API routes with the same setup. createSchema({ typeDefs, resolvers }) builds the schema from SDL type definitions. Pothos provides a code-first builder alternative where types and resolvers are colocated. createYoga({ schema, context }) produces the server. Subscriptions use server-sent events — Yoga handles the SSE transport automatically via @graphql-yoga/subscription. GraphQLError with extensions.code returns structured error codes. graphql-shield adds resolver-level authorization rules. Persisted queries reduce request payload size. Claude Code generates GraphQL Yoga servers, Pothos schema builders, subscription resolvers, shield configurations, and the deployment wrappers for Cloudflare Workers and Next.js handlers.

CLAUDE.md for GraphQL Yoga

## GraphQL Yoga Stack
- Version: graphql-yoga >= 5.0, graphql >= 16.9
- Schema: createSchema({ typeDefs: gql`...`, resolvers }) — SDL first
- Code-first: Pothos with @pothos/core — builder.queryType, builder.mutationType
- Context: createYoga({ context: async ({ request }) => ({ db, currentUser }) })
- Subscriptions: Pub/Sub from @graphql-yoga/subscription — subscribe + resolve
- Auth: graphql-shield with rule() — compose allow/deny rules per field/type
- Errors: throw new GraphQLError("msg", { extensions: { code: "NOT_FOUND" } })
- Deploy: yoga.handleRequest for CF Workers, export { default: yoga } for Next.js

Schema-First Server

// src/schema.ts — SDL type definitions
import { createSchema } from "graphql-yoga"
import { gql } from "graphql-tag"
import { ordersResolvers } from "./resolvers/orders"
import { usersResolvers } from "./resolvers/users"

const typeDefs = gql`
  scalar DateTime
  scalar JSON

  enum OrderStatus {
    PENDING
    PROCESSING
    SHIPPED
    DELIVERED
    CANCELLED
  }

  type OrderItem {
    productId: ID!
    name: String!
    quantity: Int!
    priceCents: Int!
  }

  type Order {
    id: ID!
    status: OrderStatus!
    items: [OrderItem!]!
    totalCents: Int!
    customer: User!
    createdAt: DateTime!
    updatedAt: DateTime!
  }

  type User {
    id: ID!
    email: String!
    name: String!
    orders(limit: Int, status: OrderStatus): [Order!]!
  }

  type OrdersConnection {
    orders: [Order!]!
    totalCount: Int!
    hasNextPage: Boolean!
    endCursor: String
  }

  input CreateOrderInput {
    items: [OrderItemInput!]!
  }

  input OrderItemInput {
    productId: ID!
    quantity: Int!
    priceCents: Int!
    name: String!
  }

  type Query {
    order(id: ID!): Order
    orders(
      customerId: ID
      status: OrderStatus
      limit: Int
      after: String
    ): OrdersConnection!
    me: User
  }

  type Mutation {
    createOrder(input: CreateOrderInput!): Order!
    cancelOrder(id: ID!): Order!
    updateOrderStatus(id: ID!, status: OrderStatus!): Order!
  }

  type Subscription {
    orderUpdated(orderId: ID!): Order!
    newOrderForCustomer(customerId: ID!): Order!
  }
`

export const schema = createSchema({
  typeDefs,
  resolvers: {
    ...ordersResolvers,
    ...usersResolvers,
  },
})

Resolvers

// src/resolvers/orders.ts — resolver implementations
import { GraphQLError } from "graphql"
import type { GraphQLContext } from "../context"

export const ordersResolvers = {
  Query: {
    order: async (_: unknown, { id }: { id: string }, ctx: GraphQLContext) => {
      const order = await ctx.db.orders.findById(id)
      if (!order) {
        throw new GraphQLError(`Order ${id} not found`, {
          extensions: { code: "NOT_FOUND", orderId: id },
        })
      }
      return order
    },

    orders: async (
      _: unknown,
      {
        customerId,
        status,
        limit = 20,
        after,
      }: { customerId?: string; status?: string; limit?: number; after?: string },
      ctx: GraphQLContext
    ) => {
      const [orders, totalCount] = await Promise.all([
        ctx.db.orders.list({ customerId, status, limit: limit + 1, after }),
        ctx.db.orders.count({ customerId, status }),
      ])

      const hasNextPage = orders.length > limit
      const page = hasNextPage ? orders.slice(0, limit) : orders
      const endCursor = page.length > 0 ? page[page.length - 1].id : null

      return { orders: page, totalCount, hasNextPage, endCursor }
    },
  },

  Mutation: {
    createOrder: async (
      _: unknown,
      { input }: { input: { items: OrderItemInput[] } },
      ctx: GraphQLContext
    ) => {
      if (!ctx.currentUser) {
        throw new GraphQLError("Authentication required", {
          extensions: { code: "UNAUTHENTICATED" },
        })
      }

      const totalCents = input.items.reduce(
        (sum, item) => sum + item.priceCents * item.quantity,
        0
      )

      const order = await ctx.db.orders.create({
        customerId: ctx.currentUser.id,
        items: input.items,
        totalCents,
        status: "PENDING",
      })

      // Publish for subscriptions
      ctx.pubSub.publish("ORDER_CREATED", { customerId: ctx.currentUser.id, order })

      return order
    },

    cancelOrder: async (
      _: unknown,
      { id }: { id: string },
      ctx: GraphQLContext
    ) => {
      const order = await ctx.db.orders.findById(id)

      if (!order) throw new GraphQLError("Order not found", { extensions: { code: "NOT_FOUND" } })

      if (order.customerId !== ctx.currentUser?.id) {
        throw new GraphQLError("Forbidden", { extensions: { code: "FORBIDDEN" } })
      }

      if (!["PENDING", "PROCESSING"].includes(order.status)) {
        throw new GraphQLError("Cannot cancel order in current status", {
          extensions: { code: "BAD_USER_INPUT", currentStatus: order.status },
        })
      }

      const updated = await ctx.db.orders.update(id, { status: "CANCELLED" })
      ctx.pubSub.publish("ORDER_UPDATED", { orderId: id, order: updated })

      return updated
    },
  },

  Subscription: {
    orderUpdated: {
      subscribe: (_: unknown, { orderId }: { orderId: string }, ctx: GraphQLContext) =>
        ctx.pubSub.subscribe("ORDER_UPDATED", orderId),
      resolve: (payload: { order: Order }) => payload.order,
    },

    newOrderForCustomer: {
      subscribe: (_: unknown, { customerId }: { customerId: string }, ctx: GraphQLContext) =>
        ctx.pubSub.subscribe("ORDER_CREATED", customerId),
      resolve: (payload: { order: Order }) => payload.order,
    },
  },

  Order: {
    customer: async (order: { customerId: string }, _: unknown, ctx: GraphQLContext) =>
      ctx.db.users.findById(order.customerId),
  },

  User: {
    orders: async (
      user: { id: string },
      { limit = 20, status }: { limit?: number; status?: string },
      ctx: GraphQLContext
    ) => ctx.db.orders.list({ customerId: user.id, status, limit }),
  },
}

Context and PubSub

// src/context.ts — context factory with PubSub
import { createPubSub } from "@graphql-yoga/subscription"
import { db } from "./db"
import { verifyToken } from "./auth"

type PubSubEvents = {
  ORDER_UPDATED: [orderId: string, payload: { orderId: string; order: Order }]
  ORDER_CREATED: [customerId: string, payload: { customerId: string; order: Order }]
}

export const pubSub = createPubSub<PubSubEvents>()

export interface GraphQLContext {
  db: typeof db
  pubSub: typeof pubSub
  currentUser: { id: string; email: string } | null
}

export async function createContext({
  request,
}: {
  request: Request
}): Promise<GraphQLContext> {
  const auth = request.headers.get("Authorization") ?? ""
  const currentUser = auth.startsWith("Bearer ")
    ? await verifyToken(auth.slice(7))
    : null

  return { db, pubSub, currentUser }
}

Yoga Server

// src/server.ts — Yoga server setup
import { createYoga } from "graphql-yoga"
import { usePersistedOperations } from "@graphql-yoga/plugin-persisted-operations"
import { schema } from "./schema"
import { createContext } from "./context"
import store from "./persisted-queries.json"  // { "operationId": "query { ... }" }

// For Node.js / Bun
export const yoga = createYoga({
  schema,
  context: createContext,
  plugins: [
    // Persisted queries — only allow known operations in production
    usePersistedOperations({
      getPersistedOperation(key) {
        return store[key as keyof typeof store] ?? null
      },
      allowArbitraryOperations: process.env.NODE_ENV !== "production",
    }),
  ],
  graphiql: process.env.NODE_ENV !== "production",
  cors: {
    origin: ["https://app.example.com"],
    credentials: true,
  },
})

// Node.js server
import { createServer } from "http"
const server = createServer(yoga)
server.listen(4000, () => {
  console.log("GraphQL Yoga running at http://localhost:4000/graphql")
})

Next.js API Route

// app/api/graphql/route.ts — Next.js App Router
import { createYoga } from "graphql-yoga"
import { schema } from "@/graphql/schema"
import { createContext } from "@/graphql/context"

const { handleRequest } = createYoga({
  schema,
  context: createContext,
  graphqlEndpoint: "/api/graphql",
  fetchAPI: { Response, Request, ReadableStream },
})

export const GET = handleRequest
export const POST = handleRequest

Cloudflare Workers

// src/worker.ts — Cloudflare Workers deployment
import { createYoga } from "graphql-yoga"
import { schema } from "./schema"

const yoga = createYoga({ schema })

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    return yoga.fetch(request, { env, ctx })
  },
}

For the Apollo Server alternative when Apollo Studio tracing, federated supergraph architecture, or Apollo Client cache integration are needed beyond Yoga’s standalone server, see the GraphQL federation guide for Apollo federation setup. For the Pothos code-first GraphQL schema builder that eliminates SDL drift and colocates types with resolvers for large TypeScript-first schemas, the TypeScript API guide covers type-safe resolver patterns. The Claude Skills 360 bundle includes GraphQL Yoga skill sets covering schema design, subscriptions, and shield authorization. Start with the free tier to try GraphQL server generation.

Keep Reading

Backend

Claude Code for Bun: Fast JavaScript Runtime and Toolkit

Build with Bun and Claude Code — Bun.serve for HTTP servers, Bun.file for fast file I/O, Bun.$ for shell commands, Bun.sql for SQLite and PostgreSQL, Bun.build for bundling, bun:test for testing, Bun.hash for hashing, bun.lock for deterministic installs, bun run for package.json scripts, hot reloading with --hot, bun init for project scaffolding, and compatibility with Node.js modules.

6 min read Jun 13, 2027
Backend

Claude Code for Express.js Advanced: Patterns for Production APIs

Advanced Express.js patterns with Claude Code — typed request handlers with RequestHandler generics, async error handling middleware, Zod validation middleware factory, rate limiting with express-rate-limit and Redis store, helmet security middleware, compression, dependency injection with tsyringe, file upload with multer and S3, pagination utilities, JWT middleware, and structured logging with pino.

6 min read Jun 8, 2027
Backend

Claude Code for KeystoneJS: Node.js CMS and App Framework

Build full-stack apps with KeystoneJS and Claude Code — config with lists, fields.text and fields.relationship for schema definition, access control with isAuthenticated and isAdmin functions, hooks with beforeOperation and afterOperation, GraphQL API auto-generation from schema, AdminUI for content management, session with statelessSessions, Prisma adapter for database, file storage with images and files fields, and custom REST endpoints.

6 min read Jun 7, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free