GraphQL 쿼리 깊이 제한을 올바르게 하는 방법
Source: Dev.to
graphql‑safe‑depth 소개
GraphQL은 강력하고 유연하며 표현력이 풍부하지만, 쿼리가 적절히 제한되지 않으면 그 유연성이 오히려 위험 요소가 될 수 있습니다.
너무 깊은 쿼리는 과도한 리졸버 실행, 높은 메모리 사용량, 혹은 서비스 거부(DoS) 상황을 초래할 수 있습니다.
이 글에서 배우게 될 내용:
- 깊은 GraphQL 쿼리가 실제로 문제가 되는 이유
- 기존의 많은 깊이 제한 솔루션이 부족한 이유
- graphql‑safe‑depth가 문제에 접근하는 차별화된 방법
- Apollo Server와 NestJS에서 사용하는 방법
- 다른 보안 조치와 결합하는 시점 및 방법
🚨 문제: 깊은 GraphQL 쿼리
query {
user {
posts {
comments {
author {
profile {
avatar {
url
}
}
}
}
}
}
}
첫눈에 보기에는 무해해 보이지만, 다음과 같은 문제를 일으킬 수 있습니다:
- N+1 쿼리 폭발을 유발함
- CPU와 메모리를 크게 소비함
- 의도적이든 아니든 DoS 벡터가 될 수 있음
GraphQL은 기본 깊이 또는 복잡도 제한을 설정하지 않습니다.
🤔 기존 솔루션이 부족한 이유
| Issue | Description |
|---|---|
| ❌ Counting fields instead of execution depth | 일부 라이브러리는 전체 필드 수를 세어 가장 깊은 실행 경로를 파악하지 않아 오탐이나 혼란스러운 동작을 일으킵니다. |
| ❌ Breaking introspection | introspection 쿼리(__schema, __type, __typename)는 본질적으로 깊을 수 있으며 차단되어서는 안 됩니다. |
| ❌ Hard to reason about | 특정 구현은 커스터마이징, 디버깅, 팀에 설명하기가 어렵습니다. |
✅ 접근 방식: graphql‑safe‑depth
graphql‑safe‑depth는 한 가지에 집중한 가벼운 GraphQL 검증 규칙입니다:
- 🧠 가장 깊은 리졸버 경로 측정
- 🔍 기본적으로 introspection 필드 무시
- 🧩 프래그먼트 완전 지원
- ⚡ 런타임 의존성 제로
- 🛠 TypeScript‑first, JavaScript‑friendly
작동 원리
- 라이브러리가 GraphQL의 validation phase에 훅을 겁니다.
- 쿼리 AST를 순회합니다.
- 중첩된 필드 선택을 기반으로 깊이를 계산합니다.
- 최대 실행 깊이를 추적합니다.
- 깊이가
maxDepth를 초과하면 실행 전에 쿼리를 거부합니다.
깊이 계산 예시
✅ 유효한 쿼리 (깊이 = 3)
query {
user {
profile {
name
}
}
}
❌ 유효하지 않은 쿼리 (깊이 = 4)
query {
user {
profile {
address {
city
}
}
}
}
가장 깊은 실행 경로만 중요합니다 — 전체 필드 수는 중요하지 않습니다.
🚀 사용 예시
Apollo Server (Node.js)
import { ApolloServer } from "apollo-server";
import { createDepthLimitRule } from "graphql-safe-depth";
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
createDepthLimitRule({ maxDepth: 3 }),
],
});
Apollo Server (NestJS)
import { createDepthLimitRule } from "graphql-safe-depth";
GraphQLModule.forRoot({
autoSchemaFile: true,
validationRules: [
createDepthLimitRule({ maxDepth: 3 }),
],
});
⚙️ 설정 옵션
createDepthLimitRule({
maxDepth: number; // required
ignoreIntrospection?: boolean; // default: true
message?: (depth: number, maxDepth: number) => string; // optional
});
-
maxDepth – 허용되는 최대 실행 깊이.
createDepthLimitRule({ maxDepth: 3 }); -
ignoreIntrospection –
true인 경우 introspection 필드가 무시됩니다.createDepthLimitRule({ maxDepth: 3, ignoreIntrospection: false, }); -
message – 사용자 정의 검증 오류 메시지.
createDepthLimitRule({ maxDepth: 3, message: (depth, max) => `Query depth ${depth} exceeds the allowed maximum of ${max}`, });
🔐 보안 고려 사항
Depth limiting은 만능 해결책은 아니지만, 다음과 함께 사용하면 효과적입니다:
- ✅ Query complexity limits
- ✅ Proper authentication & authorization
- ✅ Rate limiting
- ✅ Caching and batching (e.g., DataLoader)
graphql‑safe‑depth는 한 가지를 잘 수행하는 데 집중합니다 — 예측 가능한 방식으로 위험하게 깊은 쿼리를 방지합니다.
📦 설치
npm i graphql-safe-depth
🔗 링크
- GitHub 저장소:
- npm 패키지:
🧠 최종 생각
이 라이브러리는 학습용 연습으로 시작했으며, 안정적인 v1.0.0 릴리스를 갖춘 프로덕션‑준비 도구로 발전했습니다. 프로덕션 환경에서 GraphQL을 사용하고 간단하고 예측 가능한 깊이 제한이 필요하다면 graphql‑safe‑depth가 좋은 선택이 될 수 있습니다.
피드백, 이슈, 그리고 기여를 언제든 환영합니다! 🙌