Communitygithub.com

GraphQL Yoga 패턴 (v5)

GraphQL Yoga v5 서버 설정, Envelop 플러그인, 구독(subscriptions), 오류 마스킹 등을 다루는 스킬입니다.

GraphQL Yoga 패턴 (v5)란 무엇인가요?

이 스킬은 GraphQL Yoga v5를 사용하여 생산 준비가 완료된 GraphQL 서버를 구축하는 방법을 설명합니다. Yoga는 WHATWG Fetch API를 기반으로 하여 Node.js, Bun, Deno, Cloudflare Workers 등 모든 자바스크립트 런타임에서 동일한 코드로 실행할 수 있습니다. 스킬은 `createYoga`와 `createSchema`를 사용한 서버 설정부터, Envelop 플러그인 시스템을 통한 캐싱, 인증, 로깅, CORS, CSRF 방어와 같은 프로덕션 강화 패턴까지 포괄합니다. 또한 SSE(Server-Sent Events) 기반의 기본 구독, WebSocket 구독, 오류 마스킹 및 `GraphQLError`를 통한 의도적인 오류 노출, 파일 업로드 등을 다룹니다. 핵심 원칙으로는 Yoga 전용 플러그인을 우선 사용하여 HTTP 계층에서 GraphQL 실행 자체를 건너뛰어 성능을 최적화하고, `GraphQLError`는 반드시 `graphql` 패키지에서 임포트해야 합니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/agents-inc/skills/tree/HEAD/src/skills/api-graphql-yoga

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

GraphQL Yoga 패턴

빠른 안내: createYoga + createSchema를 사용하여 Fetch API 호환 GraphQL 서버를 만들면 모든 JS 런타임에서 실행됩니다. Yoga v5는 Envelop을 사용한 플러그인 구성, SSE 기반 기본 구독, 내장 오류 마스킹, CORS 등을 기본 제공합니다. 의도적으로 클라이언트에 노출할 오류는 graphql-yoga가 아닌 graphql에서 GraphQLError를 임포트하여 사용하세요. HTTP 수준 최적화를 위해 Envelop 플러그인보다는 Yoga 전용 플러그인을 선호하세요.


<critical_requirements>

중요: 이 스킬 사용 전에

모든 코드는 CLAUDE.md의 프로젝트 규칙을 따라야 합니다. (kebab-case, named exports, import 순서, import type, named constants)

(GraphQLError는 반드시 'graphql'에서 임포트해야 합니다. 'graphql-yoga'에서 임포트하면 안 됩니다.)

(Yoga 전용 플러그인이 있다면 반드시 Envelop 플러그인보다 우선 사용해야 합니다. Yoga 플러그인은 HTTP 계층에서 작동하여 캐시되거나 유지된 결과에 대해 GraphQL 실행을 건너뛸 수 있습니다.)

(스키마 우선 접근 방식을 사용할 때는 반드시 'graphql-yoga'의 createSchema를 사용해야 합니다. v5에서는 createYoga에 직접 typeDefs/resolvers 객체를 전달하는 방식을 지원하지 않습니다.)

(모든 숫자 값(타임아웃, TTL, 포트 번호, 제한)은 반드시 named constants를 사용해야 합니다.)

</critical_requirements>


자동 감지 키워드: GraphQL Yoga, graphql-yoga, createYoga, createSchema, createPubSub, Envelop, useResponseCache, useCSRFPrevention, usePersistedOperations, GraphQL 구독 SSE, 오류 마스킹, maskedErrors, graphql-ws, Yoga 플러그인 훅, onRequest, onParams

사용 시기:

  • Node.js, Bun, Deno, Cloudflare Workers에서 실행될 GraphQL 서버를 구축할 때
  • SSE(기본) 또는 WebSocket을 통한 구독이 필요한 API
  • Envelop 플러그인(캐싱, 인증, 로깅)으로 GraphQL 실행을 확장할 때
  • GraphQL Multipart Request 스펙을 사용한 파일 업로드가 필요할 때
  • 오류 마스킹, CORS, CSRF 보호가 필요한 프로덕션 API

사용하지 말아야 할 때:

  • GraphQL 없이 REST API만 필요한 경우
  • 프레임워크의 기본 라우트 핸들러로 충분한 단순 CRUD의 경우
  • 페더레이션 게이트웨이가 필요한 경우 (전용 게이트웨이 솔루션 고려)

다루는 주요 패턴:

  • createYoga와 createSchema를 사용한 서버 설정 (스키마 우선)
  • createYoga<ServerContext> 제네릭을 사용한 타입 안전 컨텍스트
  • Envelop 플러그인 시스템: 생명주기 훅, 커스텀 플러그인, Yoga 전용 플러그인
  • 구독: SSE(기본), graphql-ws를 통한 WebSocket, 내장 PubSub
  • 오류 마스킹과 의도적인 GraphQLError 노출
  • WHATWG File 스칼라를 사용한 파일 업로드
  • 프로덕션 강화: CORS, CSRF 방어, GraphQL Armor, 로깅
  • 크로스 런타임 배포: Node.js, Bun, Deno, Cloudflare Workers

상세 자료:


철학

GraphQL Yoga는 필수 기능을 기본 탑재한 Fetch API 호환 GraphQL 서버입니다. 핵심은 WHATWG Fetch API(Request/Response)를 기반으로 하여 런타임에 구애받지 않습니다. 동일한 서버 코드를 Node.js, Bun, Deno, 엣지 런타임에 배포할 수 있습니다. Envelop 플러그인 시스템은 HTTP 계층과 GraphQL 실행 계층 모두에서 구성 가능한 미들웨어를 제공합니다.

스키마 접근 방식: Yoga는 스키마 라이브러리에 중립적입니다. createSchema(스키마 우선 SDL), Pothos(코드 우선), 또는 바닐라 graphql-js 등 GraphQLSchema를 생성하는 모든 방법을 사용할 수 있습니다.

플러그인 우선순위: 동일한 기능(캐싱, 유지된 작업, defer/stream)에 대해 Envelop 플러그인과 Yoga 전용 플러그인이 모두 존재하는 경우, 항상 Yoga 변형을 선택하세요. Yoga 플러그인은 HTTP 계층에 연결되어 GraphQL 실행이 시작되기 전에 단축 회로(short-circuit)를 통해 캐시되거나 유지된 결과에 대해 파싱과 검증을 건너뛸 수 있습니다.

오류 철학: 예상치 못한 오류는 프로덕션 환경에서 기본적으로 모두 마스킹됩니다. 의도적인 오류는 graphql 패키지의 GraphQLError로 던져집니다. 이 오류는 마스킹을 우회하여 메시지와 extensions이 그대로 클라이언트에 전달됩니다.


핵심 패턴

패턴 1: createYoga를 사용한 서버 설정

SDL 기반 스키마를 위해 createSchema와 함께 Yoga 인스턴스를 생성합니다. yoga 인스턴스 자체가 Fetch API 핸들러이므로, 모든 런타임의 HTTP 서버에 직접 전달할 수 있습니다.

import { createYoga, createSchema } from "graphql-yoga";
import { createServer } from "node:http";

const PORT = 4000;

const yoga = createYoga({
  schema: createSchema({
    typeDefs: /* GraphQL */ `
      type Query {
        greeting(name: String!): String!
      }
    `,
    resolvers: {
      Query: {
        greeting: (_, { name }) => `Hello, ${name}!`,
      },
    },
  }),
});

const server = createServer(yoga);
server.listen(PORT, () => {
  console.info(`Server running on http://localhost:${PORT}/graphql`);
});

왜 좋은가: createSchema는 makeExecutableSchema를 래핑하며, yoga 인스턴스는 표준 Fetch 핸들러이므로 모든 런타임에서 작동합니다.

전체 설정, 크로스 런타임 배포, 타입 안전 컨텍스트는 examples/core.md를 참조하세요.


패턴 2: 타입 안전 컨텍스트

서버별 컨텍스트 타입을 위해 createYoga에 제네릭을 전달합니다. context 팩토리는 YogaInitialContext( request와 params 포함)를 받아 커스텀 컨텍스트를 반환합니다.

import { createYoga, type YogaInitialContext } from "graphql-yoga";

interface ServerContext {
  user: { id: string } | null;
}

const yoga = createYoga<ServerContext>({
  schema,
  context: async ({ request }: YogaInitialContext) => {
    const token = request.headers.get("authorization");
    return { user: token ? await verifyToken(token) : null };
  },
});

왜 좋은가: 제네릭 타입이 리졸버 컨텍스트 매개변수로 흘러가며, request는 표준 Fetch API(프레임워크별 req/res가 아님)를 사용합니다.

전체 컨텍스트 패턴은 examples/core.md를 참조하세요.


패턴 3: Envelop 플러그인 시스템

플러그인은 plugins 배열에 전달합니다. 가능하면 Yoga 전용 플러그인을 사용하세요. 이 플러그인은 HTTP 계층에서 작동하여 GraphQL 실행을 완전히 건너뛸 수 있습니다.

import { createYoga } from "graphql-yoga";
import { useResponseCache } from "@graphql-yoga/plugin-response-cache";

const CACHE_TTL_MS = 2_000;

const yoga = createYoga({
  schema,
  plugins: [
    useResponseCache({
      session: () => null,
      ttl: CACHE_TTL_MS,
    }),
  ],
});

왜 좋은가: Yoga 응답 캐시는 캐시된 결과에 대해 파싱/검증을 건너뜁니다(Envelop 버전은 불가능). 여러 플러그인이 충돌 없이 구성됩니다.

커스텀 플러그인, 생명주기 훅, 모든 Yoga 전용 플러그인은 examples/plugins.md를 참조하세요.


패턴 4: SSE(기본)를 사용한 구독

Yoga는 기본적으로 Server-Sent Events를 사용하여 구독을 지원합니다. WebSocket 설정이 필요 없습니다. 구독 리졸버에서 AsyncGenerator 문법을 사용하세요.

const schema = createSchema({
  typeDefs: /* GraphQL */ `
    type Subscription {
      countdown(from: Int!): Int!
    }
  `,
  resolvers: {
    Subscription: {
      countdown: {
        subscribe: async function* (_, { from }) {
          for (let i = from; i >= 0; i--) {
            await new Promise((resolve) => setTimeout(resolve, 1_000));
            yield { countdown: i };
          }
        },
      },
    },
  },
});

왜 좋은가: WebSocket 인프라가 필요 없으며, HTTP 프록시와 로드 밸런서를 통과하여 작동합니다. 클라이언트는 graphql-sse 라이브러리를 사용합니다.

PubSub, WebSocket 설정, 필터링은 examples/subscriptions.md를 참조하세요.


패턴 5: 오류 마스킹과 GraphQLError

Yoga는 기본적으로 모든 예상치 못한 오류를 마스킹합니다. 의도적으로 클라이언트에 노출할 오류는 graphql에서 GraphQLError를 던지세요. 이 오류는 마스킹을 우회합니다.

import { GraphQLError } from "graphql";

const NOT_FOUND_CODE = "USER_NOT_FOUND";

throw new GraphQLError("User not found", {
  extensions: { code: NOT_FOUND_CODE },
});

왜 좋은가: 예상치 못한 오류는 내부 정보(데이터베이스 세부 정보, 스택 트레이스)를 절대 유출하지 않으며, 의도적인 오류는 메시지와 extensions과 함께 통과합니다.

커스텀 마스킹, 마스킹 비활성화, 개발 모드는 examples/error-handling.md를 참조하세요.


패턴 6: 파일 업로드

Yoga는 GraphQL Multipart Request 스펙을 지원합니다. File 스칼라를 추가하고 리졸버에서 WHATWG File 객체를 받으세요.

const schema = createSchema({
  typeDefs: /* GraphQL */ `
    scalar File
    type Mutation {
      uploadFile(file: File!): Boolean!
    }
  `,
  resolvers: {
    Mutation: {
      uploadFile: async (_, { file }: { file: File }) => {
        const content = await file.arrayBuffer();
        // 파일 내용 처리
        return true;
      },
    },
  },
});

왜 좋은가: 표준 WHATWG File API(브라우저와 동일)를 사용하며, 추가 패키지가 필요 없습니다. multipart: false로 비활성화할 수 있습니다.

전체 파일 업로드 패턴은 examples/core.md를 참조하세요.


<decision_framework>

결정 프레임워크

스키마 접근 방식

SDL에서 자동 생성된 타입이 필요한가?
+-- 예 --> createSchema (typeDefs + resolvers를 사용한 스키마 우선)
+-- 아니오 --> 스키마 정의에서 완전한 TypeScript 추론을 원하는가?
    +-- 예 --> 코드 우선 라이브러리 (예: Pothos) -- 결과 GraphQLSchema를 Yoga에 전달
    +-- 아니오 --> 바닐라 graphql-js GraphQLSchema

구독 전송

구독이 필요한가?
+-- 예 --> 클라이언트가 양방향 통신을 필요로 하는가?
|   +-- 예 --> graphql-ws를 통한 WebSocket (ws + graphql-ws 패키지 추가)
|   +-- 아니오 --> SSE (기본 설정, 구성 불필요, 프록시 통과 가능)
+-- 아니오 --> 구독 설정 불필요

플러그인 선택

원하는 기능에 Yoga 전용 플러그인이 존재하는가?
+-- 예 --> Yoga 플러그인 사용 (HTTP 수준 훅, 실행 생략 가능)
+-- 아니오 --> Envelop 플러그인 사용 (GraphQL 실행 수준 훅)

Yoga 전용 플러그인 (Envelop보다 선호)

플러그인패키지Yoga 전용인 이유
응답 캐시@graphql-yoga/plugin-response-cache캐시된 쿼리에 대해 실행 생략
유지된 작업@graphql-yoga/plugin-persisted-operationsHTTP 계층에서 알 수 없는 작업 거부
Defer/Stream@graphql-yoga/plugin-defer-streamHTTP 청크 인코딩을 통한 스트리밍
CSRF 방어@graphql-yoga/plugin-csrf-prevention파싱 전에 커스텀 헤더 요구
GraphQL SSE@graphql-yoga/plugin-graphql-sse단일 연결 SSE 모드

</decision_framework>


<red_flags>

위험 신호

높은 우선순위:

  • GraphQLError를 graphql-yoga에서 임포트하는 경우 (올바른 패키지: graphql) -- 잘못된 패키지이며 실패합니다.
  • createYoga에 createSchema 없이 typeDefs/resolvers 객체를 직접 전달하는 경우 -- v5에서 지원되지 않습니다.
  • Yoga 전용 플러그인이 존재하는데 Envelop 플러그인을 사용하는 경우 -- HTTP 수준 최적화를 놓칩니다 (Yoga 응답 캐시는 파싱을 완전히 건너뛰지만 Envelop 버전은 불가능합니다).
  • 리졸버에서 클라이언트가 메시지를 볼 것으로 기대하고 일반 Error를 던지는 경우 -- 프로덕션에서 "예기치 않은 오류가 발생했습니다."로 마스킹됩니다.

중간 우선순위:

  • 프로덕션에서 CORS 출처를 설정하지 않는 경우 -- 기본값은 *이며, 이는 제한되어야 합니다.
  • 여러 서버 인스턴스에서 인메모리 PubSub을 사용하는 경우 -- 이벤트가 전파되지 않습니다 (Redis 기반 createRedisEventTarget 사용).
  • graphql 피어 의존성이 누락된 경우 -- graphql-yoga는 graphql을 피어 의존성으로 필요로 하므로 둘 다 설치해야 합니다.
  • 스키마 없이 createSchema를 호출하는 경우 -- Yoga는 스키마를 필요로 하며, 추론하지 않습니다.

주의 사항 및 엣지 케이스:

  • YogaInitialContext.request는 Fetch API Request 객체이지 Node.js IncomingMessage가 아닙니다. req.headers가 아니라 request.headers.get()을 사용하세요.
  • v5에서 플러그인 실행 순서가 변경되었습니다. onPluginInit에서 addPlugin으로 추가된 플러그인은 이제 추가하는 플러그인 바로 다음에 실행되며, 마지막에 실행되지 않습니다.
  • useResponseCache의 session 콜백은 PRIVATE 범위를 위해 문자열(사용자 ID)을 반환하거나 PUBLIC을 위해 null을 반환해야 합니다. undefined를 반환하면 캐싱이 중단됩니다.
  • SSE 구독은 HTTP(text/event-stream)를 통해 전달됩니다. 일부 프록시는 이벤트를 버퍼링할 수 있습니다. Nginx의 경우 X-Accel-Buffering: no를 설정하세요.
  • 업로드의 File 스칼라는 WHATWG File 객체를 제공합니다. .text(), .arrayBuffer(), .stream() 메서드를 사용하세요(Node.js Buffer 직접 사용 금지).
  • Yoga의 내장 GraphiQL은 기본적으로 활성화되어 있습니다. 프로덕션에서는 graphiql: false로 비활성화하세요.
  • maskedErrors를 false로 설정하면 스택 트레이스를 포함한 모든 마스킹이 해제됩니다. 선택적 노출을 위해 대신 커스텀 maskError 함수를 사용하세요.
  • origin: '*'와 함께 credentials: true를 설정하면 Fetch 스펙에 따라 브라우저에서 거부됩니다. 정확한 출처를 지정하세요.

</red_flags>


<critical_reminders>

중요 알림

모든 코드는 CLAUDE.md의 프로젝트 규칙을 따라야 합니다.

(GraphQLError는 반드시 'graphql'에서 임포트해야 합니다. 'graphql-yoga'에서 임포트하면 안 됩니다.)

(Yoga 전용 플러그인이 있다면 반드시 Envelop 플러그인보다 우선 사용해야 합니다. Yoga 플러그인은 HTTP 계층에서 작동하여 캐시되거나 유지된 결과에 대해 GraphQL 실행을 건너뛸 수 있습니다.)

(스키마 우선 접근 방식을 사용할 때는 반드시 'graphql-yoga'의 createSchema를 사용해야 합니다. v5에서는 createYoga에 직접 typeDefs/resolvers 객체를 전달하는 방식을 지원하지 않습니다.)

(모든 숫자 값(타임아웃, TTL, 포트 번호, 제한)은 반드시 named constants를 사용해야 합니다.)

**이 규칙을 따르지 않으면 가져오기 오류, 마스킹 실패, 잘못된 동작이 발생합니다.

</critical_reminders>

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

관련 스킬