eatthefrog
"분명히 보냈는데 없다고?" GraphQL 필수 변수 누락 오류 해결하기 본문
개발 과정에서 클라이언트는 분명히 데이터를 보냈고, 서버 로그에도 찍히는데, GraphQL 엔진은 "필수 변수가 제공되지 않았다"며 요청을 거부하는 이해할 수 없는 에러를 만났습니다.
1. 문제의 발단 : "Variable $name was not provided"
회원가입 API호출 시 다음과 같은 에러가 발생했습니다.
[ERROR] GraphQL Validation Errors: {
"errors": [
{
"message": "Variable \"$name\" of required type \"String!\" was not provided."
}
],
"variables": {
"name": "홍길동"
}
}
모순적인 상황: 로그 하단의 variables 객체에는 nickname이 멀쩡히 들어있습니다. 하지만 GraphQL 엔진은 Stirng! 타입인 $name 변수가 비어있다고 판단하고 GRAPJQL_VALIDATION_FAILD 에러를 뱉어 내고 있었습니다.
2. 원인 분석: 정적 검증 미들웨어의 함정
문제의 핵심은 백엔드에 적용된 쿼리 복잡도 검증 (Query Complexity Validation) 로직에 있었습니다.
보통 GraphQL 서버는 과도한 중첩 쿼리로 인한 DoS 공격을 방지하기 위해 쿼리 복잡도를 계산합니다. 기존 설정은 다음과 같은 구조적 문제를 가지고 있었습니다.
- 기존 방식: 서버 초기화 시점에 queryComplexityRule을 유효성 검사 규칙 (Valiadion Rules)으로 등록하면서, 변수 목록을 빈 객체 (vaiables: {})로 고정해둠
- 프로세스의 충돌:
1. 클라이언트로부터 요청이 들어옴
2. GraphQL 실행 전, validation 단계에서 등록된 규칙들을 검사함
3. 복잡도 검증 미들웨어가 쿼리를 훑어 보는데, 이때 주입된 변수가 {}(빈 값)이다 보니 필수인 name 변수가 없다고 판단
4. 실제 싱행 단계로 넘어가 실제 데이터를 확인하기도 전에 에러를 발생시키고 요청을 차단함
결론: 보안을 위한 정적 검사 로직이 실제 런타임 데이터를 인지하지 못해 발생한 검증의 병목이였습니다.

3. 해결방안: 정적 규칙에서 동적 플러그인으로
문제를 해결하기 위해, 고정된 규칙으로 작동하던 검증 로직을 요청 시마다 실제 변수를 참조하는 동적 플러그인 방식으로 변경했습니다.
- 변경 전: 서버 실정 시점에 빈 변수 객체로 queryComplexityRule 적용
- 변경 후: Apollo Server Plugin 을 사용하여, 요청이 들어온 직후(requestDidStart)해당 요청에 포함된 실제 variables를 추출하여 복잡도를 계산하도록 수정하여 GraphQL 엔진이 실제 전달된 데이터를 기반으로 복잡도를 계산하게 만들고, 필수 변수 누락에 대한 오판을 멈추게 만들었습니다.
4. 결론
1. 유연한 설계: 보안 설정(복잡도 검사등)은 엄격해야 하지만, 실제 비즈니스 로직(변수 데이터)과는 유연하게 상호작용할 수 있도록 설계해야합니다.
2. 미들웨어의 실행 시점: GraphQL의 Validation 단계와 Execuation 단계 사이의 차이를 이해하는것이 중요합니다.
5. 교훈
혹시 GrpahQL 서버가 데이터를 무시한다면, 설정된 Validaion Rules 중 외부 변수를 정적으로 참조하고 있는 로직이 없는지 확인해보자!
GraphQL 심화학습
GraphQL의 동작 원리를 깊이 있게 이해하면, 단순히 에러를 고치는 것을 넘어 더 효율적이고 안전한 아키텍쳐를 설계할 수 있다. 요청이 서버에 들어와서 응답으로 나가기까지의 과정을 공부해보자.
1. GraphQL 요청의 일생 (Lifecycle)
GraphQL 서버는 요청을 받으면 크게 Parsing -> Validation -> Execution의 세 단계를 거친다.
| 단계 | 역할 | 비유 |
| Parsing | 쿼리 문자열을 컴퓨터가 이해할 수 있는 트리 구조 (AST)로 변환합니다. | 문장의 오타나 괄호 짝이 맞는지 검사하는 단계 |
| Validation | 스키마와 비교하여 쿼리가 유효한지 검사합니다. (필수 값 존재 여부, 타입 체크 등) | 주문서에 적힌 메뉴가 우리 식당에 실제로 있는지, 필수 옵션을 선택했는지 체크하는 다계 |
| Execution | 실제로 데이터를 가져오기 위해 리졸버 (Resolver)함수를 실행합니다. | 주방에서 실제로 요리를 시작하는 다계 |
문제의 핵심: 쿼리 복잡도 검증 (Complexity Rule)은 2단계인 Validation에서 발생했다.
2. 왜 내 데이터가 무시되었을까? (정적 검증의 함정)
전통적인 방식의 validationRules 설정은 서버가 처음 뜰 때(Boot time) 한 번 정의된다. 이때 변수(variables)를 넣어주지 않으면, 서버는 모든 요청에 대해 "변수가 없는 상태"로 검사기를 돌린다.
[정적 검증 VS 실제 요청 데이터 ].
[ 클라이언트의 요청 ] ---------------------------------> [ 서버의 Validation 단계 ]
{ |
query: "mutation API(...)", | 검사기(ComplexityRule)의 시점:
variables: { name: "홍길동" } ----X----> "나는 서버 설정 때 받은
} | 빈 객체({})만 알고 있어!"
|
| 결과: "name이 필수인데
| 왜 빈 값을 보냈니?" (Error!)
서버 로그에 데이터가 찍혔던 이유는, 서버가 요청 자체는 받았기 때문이다. 하지만 Validation 검사기가 실제 데이터(Runtime Variables)를 쳐다보지 않고, 자신이 처음에 설정받은 빈 값만 확인했기 때문에 에러가 발생한 것이다.
3. 해결책: 동적 참조 (Apollo Plugin 방식)
문제를 해결하려면 검사기가 "요청이 들어올때마다(Request time)" 그 안에 들어있는 실제 변수를 가로채서 확인하게 해야한다.
[개선된 프로세스 흐름]
- 요청 도착: variables: { name: "홍길동" }가 포함된 요청이 들어옴.
- 플러그인 작동: Apollo 플러그인이 requestDidStart 단계에서 이 변수들을 손에 쥠.
- 동적 주입: Validation 단계가 시작될 때, 플러그인이 가지고 있던 진짜 변수를 검사기(queryComplexity)에 전달함.
- 검증 통과: 검사기가 "오, name이 여기 있네! 복잡도 계산할게!"라고 하며 정상 통과시킴.
4. 실전 응용: "데이터가 무시될 때" 확인해야 할 체크리스트
만약 추후에 비슷한 문제가 발생한다면 다음의 **'데이터 흐름도'**를 머릿속에 그려보자!
"내가 만든 검증 로직이 런타임(Runtime) 데이터를 보는가, 아니면 설정(Config) 데이터만 보는가?"
- 정적 참조 (Static): 서버 설정 시점에 결정됨. 유동적인 사용자 입력값 처리에 취약함.
- 동적 참조 (Dynamic): context나 plugin을 통해 매 요청마다 데이터를 주입받음. API 로직에 적합함.
항상 "이 로직이 실행되는 시점에 필요한 데이터가 주입(Injection)되었는가?"를 확인하는 습관을 들이면 주니어와 시니어의 차이를 만드는 디테일이 된다!
'시행착오 노트' 카테고리의 다른 글
| Apollo Client v4 업그레이드: Shim 파일과 Client Preset으로 아키텍처 현대화하기 (0) | 2026.01.02 |
|---|---|
| GraphQL Resolver에서 발생한 타입 불일치 이슈 정리 (0) | 2025.11.16 |
| React Native Permissions권한 관리 (0) | 2025.11.06 |
| React Native 디버깅 자동화 구축기 (0) | 2025.11.05 |
| 앱스토어 출시 후에 애드몹 광고가 안보이는 문제 (0) | 2025.05.16 |