docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거

Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를
Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류.

- 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로
  자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거,
  동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거.
- 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming,
  Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business,
  Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로,
  나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는
  title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백).
  원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지.
- 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서.
- 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는
  지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지.
- Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경.
- 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
This commit is contained in:
Antigravity Agent
2026-07-05 00:33:48 +09:00
parent 1cfd3bbb56
commit 9148c358d0
6455 changed files with 1 additions and 86875 deletions
@@ -0,0 +1,417 @@
---
id: backend-graphql-yoga-pothos
title: GraphQL Yoga / Pothos — Modern GraphQL Server
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [backend, graphql, yoga, pothos, vibe-coding]
tech_stack: { language: "TS", applicable_to: ["Backend"] }
applied_in: []
aliases: [GraphQL Yoga, Pothos, code-first GraphQL, Apollo Server alternative, Mercurius]
---
# GraphQL Yoga / Pothos
> Apollo Server 의 modern alternative. **Yoga (server) + Pothos (schema builder, type-safe)**. Edge runtime + 빠른 + 작은. Federation 지원.
## 📖 핵심 개념
- Yoga: Server (request handler).
- Pothos: code-first schema builder (TS).
- DataLoader: N+1 해결.
- Federation: 마이크로서비스 결합.
## 💻 코드 패턴
### Yoga + Pothos 시작
```bash
yarn add graphql graphql-yoga @pothos/core
```
```ts
import { createYoga } from 'graphql-yoga';
import SchemaBuilder from '@pothos/core';
const builder = new SchemaBuilder<{
Context: { user: User | null; db: DB };
}>({});
builder.objectType('User', {
fields: t => ({
id: t.exposeID('id'),
email: t.exposeString('email'),
name: t.exposeString('name', { nullable: true }),
posts: t.field({
type: ['Post'],
resolve: (user, _, ctx) => ctx.db.posts.findByUser(user.id),
}),
}),
});
builder.queryType({
fields: t => ({
me: t.field({
type: 'User',
nullable: true,
resolve: (_, __, ctx) => ctx.user,
}),
}),
});
const yoga = createYoga({
schema: builder.toSchema(),
context: async ({ request }) => ({
user: await getUser(request),
db,
}),
});
```
```ts
// Server (Bun / Node)
import { createServer } from 'node:http';
const server = createServer(yoga);
server.listen(4000);
```
### Type-safe input
```ts
const CreatePostInput = builder.inputType('CreatePostInput', {
fields: t => ({
title: t.string({ required: true }),
body: t.string({ required: true }),
tags: t.stringList(),
}),
});
builder.mutationType({
fields: t => ({
createPost: t.field({
type: 'Post',
args: {
input: t.arg({ type: CreatePostInput, required: true }),
},
resolve: async (_, { input }, ctx) => {
if (!ctx.user) throw new Error('UNAUTHORIZED');
return ctx.db.posts.create({ ...input, userId: ctx.user.id });
},
}),
}),
});
```
→ Schema + resolver type-safe.
### Pothos Prisma plugin
```ts
import PrismaPlugin from '@pothos/plugin-prisma';
import type PrismaTypes from './generated/pothos-types';
const builder = new SchemaBuilder<{ PrismaTypes: PrismaTypes }>({
plugins: [PrismaPlugin],
prisma: { client: prisma },
});
builder.prismaObject('User', {
fields: t => ({
id: t.exposeID('id'),
email: t.exposeString('email'),
posts: t.relation('posts'), // N+1 자동 해결
}),
});
builder.queryFields(t => ({
user: t.prismaField({
type: 'User',
args: { id: t.arg.id({ required: true }) },
resolve: (query, _, { id }) => prisma.user.findUnique({ ...query, where: { id } }),
}),
}));
```
→ Prisma + Pothos = N+1 자동.
### Drizzle plugin
```ts
import { drizzlePlugin } from '@pothos/plugin-drizzle';
const builder = new SchemaBuilder<{ DrizzleSchema: typeof schema }>({
plugins: [drizzlePlugin],
drizzle: { client: db },
});
builder.drizzleObject('users', {
name: 'User',
fields: t => ({
id: t.exposeID('id'),
email: t.exposeString('email'),
posts: t.relation('posts'),
}),
});
```
### DataLoader (manual)
```ts
import DataLoader from 'dataloader';
function makeLoaders(db: DB) {
return {
postsByUser: new DataLoader<string, Post[]>(async (userIds) => {
const posts = await db.posts.where('userId', 'in', userIds);
const grouped = new Map<string, Post[]>();
for (const p of posts) {
const arr = grouped.get(p.userId) ?? [];
arr.push(p);
grouped.set(p.userId, arr);
}
return userIds.map(id => grouped.get(id) ?? []);
}),
};
}
// Per-request
const yoga = createYoga({
context: ({ request }) => ({
user: ...,
loaders: makeLoaders(db),
}),
});
```
→ Pothos + Prisma 가 자동. 자체 = manual loader.
### Subscription (real-time)
```ts
builder.subscriptionType({
fields: t => ({
postCreated: t.field({
type: 'Post',
subscribe: (_, __, ctx) => ctx.pubsub.subscribe('POST_CREATED'),
resolve: (payload) => payload,
}),
}),
});
```
```ts
// Yoga + WebSocket
import { createServer } from 'node:http';
import { useServer } from 'graphql-ws/lib/use/ws';
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ server: httpServer, path: '/graphql' });
useServer({ schema, context: () => ({ ... }) }, wss);
```
### Persisted queries
```ts
import { usePersistedOperations } from '@graphql-yoga/plugin-persisted-operations';
const yoga = createYoga({
plugins: [
usePersistedOperations({
getPersistedOperation: (key) => operations[key],
allowArbitraryOperations: false, // prod
}),
],
});
```
→ Client = hash, server = registered query. Bandwidth + security.
### Cost analysis (DoS 방지)
```ts
import { useCostAnalysis } from '@envelop/cost-analysis';
const yoga = createYoga({
plugins: [
useCostAnalysis({
maximumCost: 1000,
defaultCost: 1,
// 매 field 의 cost 정의
}),
],
});
```
→ 큰 nested query (10 → 100 → 1000) 차단.
### Depth limit
```ts
import { useDepthLimit } from '@envelop/depth-limit';
useDepthLimit({ maxDepth: 7 });
```
### Error masking
```ts
import { useMaskedErrors } from '@envelop/core';
useMaskedErrors({
maskError: (error, message) => {
if (error.extensions?.code === 'INTERNAL_ERROR') {
return new Error('Internal server error');
}
return error;
},
});
```
→ Internal error 사용자에 자세 X.
### Authentication
```ts
const yoga = createYoga({
context: async ({ request }) => {
const token = request.headers.get('authorization')?.replace('Bearer ', '');
const user = token ? await verifyJwt(token) : null;
return { user };
},
});
// Resolver
resolve: (_, __, ctx) => {
if (!ctx.user) throw new GraphQLError('UNAUTHORIZED', { extensions: { code: 'UNAUTHORIZED' } });
// ...
}
```
### Authorization (field-level)
```ts
import AuthPlugin from '@pothos/plugin-scope-auth';
const builder = new SchemaBuilder<{
AuthScopes: { admin: boolean; loggedIn: boolean };
}>({
plugins: [AuthPlugin],
authScopes: ({ user }) => ({
admin: user?.role === 'admin',
loggedIn: !!user,
}),
});
builder.queryFields(t => ({
adminStats: t.field({
type: 'Stats',
authScopes: { admin: true },
resolve: () => ...,
}),
}));
```
### Federation (마이크로서비스)
```ts
import { fastify } from 'fastify';
import { useApolloFederation } from '@graphql-yoga/apollo-federation';
const subgraph = builder.toSchema();
useApolloFederation({ subgraph });
// Gateway
import { stitchSchemas } from '@graphql-tools/stitch';
const supergraph = stitchSchemas({
subschemas: [usersSubgraph, ordersSubgraph],
});
```
### Edge runtime (Hono + Yoga)
```ts
import { Hono } from 'hono';
import { createYoga } from 'graphql-yoga';
const yoga = createYoga({ schema });
const app = new Hono();
app.all('/graphql', (c) => yoga.fetch(c.req.raw, c.env));
export default app;
```
→ Cloudflare Workers / Vercel Edge.
### Mercurius (Fastify GraphQL, fast)
```ts
import Fastify from 'fastify';
import mercurius from 'mercurius';
const app = Fastify();
app.register(mercurius, { schema, resolvers });
```
→ Yoga 의 Fastify 대안 — 매우 빠름.
### Code-first vs Schema-first
```
Code-first (Pothos):
+ Type-safe (TS 가 schema 만듦)
+ Refactoring 쉬움
- Schema = code (다른 lang client 가 generate 필요)
Schema-first (SDL .graphql 파일):
+ Schema 가 truth
+ 다른 lang 가 generate 가능
+ Tools (codegen) 친화
- Type 가 다른 곳 — drift 가능
```
→ Pothos 추세.
### vs Apollo Server
```
Apollo:
+ 큰 ecosystem
+ Apollo Studio (managed)
- 옛 (some legacy)
Yoga:
+ Modern, 빠름
+ Edge 호환
+ 작은 bundle
- 작은 community
```
### Persisted queries (Apollo Persisted Queries)
```ts
// Build-time:
// Client 의 모든 query → hash → registry.
// Runtime:
// Client 가 hash 만 보냄.
// Server 가 hash → query 조회.
// 장점:
// - Bandwidth 작음
// - Public schema 숨김 (allowlist)
// - DoS 방지
```
## 🤔 의사결정 기준
| 상황 | 추천 |
|---|---|
| Modern TS GraphQL | Yoga + Pothos |
| Federation | Apollo / Yoga + tools |
| Edge runtime | Yoga |
| Fastify ecosystem | Mercurius |
| Existing Apollo | 점진 migrate |
| Schema-first 강 | GraphQL Codegen + Yoga |
## ❌ 안티패턴
- **Cost analysis 없음**: 큰 nested query DoS.
- **N+1 무관심**: DataLoader 또는 plugin.
- **Depth limit 없음**: deep query.
- **모든 field public**: auth scope.
- **Schema 자주 변경 — version 없음**: client 깨짐.
- **Public schema 추가 + persisted X**: introspection leak.
## 🤖 LLM 활용 힌트
- Yoga + Pothos = modern stack.
- Pothos + Prisma plugin 가 N+1 자동.
- Cost / depth / scope auth 3종 항상.
- Edge runtime 호환.
## 🔗 관련 문서
- [[Backend_GraphQL_Server_Patterns]]
- [[Web_GraphQL_Client_Patterns]]
- [[Backend_Hono_Modern]]