Apollo Router를 로컬에서 구성하기
Created at 2026년 03월 01일
•Updated at 2026년 03월 01일
•By 강병준
AI가 발전하면서 각 엔지니어가 다룰 수 있는 영역이 빠르게 넓어졌습니다. 프론트엔드 엔지니어가 API를 짜고, 백엔드 엔지니어가 UI를 수정하는 일이 더 이상 낯설지 않습니다. 코드 한 줄보다 제품 관점에서 사고하고 검증하는 역할이 더 중요해졌고, 기술적 장벽이 협업을 막는 시대는 지났습니다.
그럼에도 대부분의 팀은 여전히 자신의 영역에서만 서버를 띄웁니다. 프론트엔드 작업할 때는 프론트엔드 서버만, 서버 작업할 때는 서버만 켭니다. 그렇다면 두 영역을 함께 작업해야 할 때는 어떻게 할까요? 서버를 먼저 작업하고, 프론트엔드는 모킹으로 테스트한 뒤 배포 후 연동하는 게 최선일까요?
모킹이 실제 환경을 완전히 대체할 수 있을까요?
실제 서버와 함께 개발하려면 로컬에서 프론트엔드와 백엔드를 동시에 띄울 수 있어야 합니다. 하지만 Apollo Federation 환경에서는 서비스 하나만 올린다고 되는 게 아닙니다. 모든 서비스의 스키마를 합성하고, 서비스 간 gRPC 통신도 맞물려야 합니다.
오늘은 이 문제를 어떻게 해결하였는지 공유하겠습니다. 먼저 Apollo Federation이 어떤 구조인지 간단히 짚고 넘어가겠습니다.
Apollo Federation
GraphQL의 강점은 클라이언트가 원하는 데이터를 하나의 요청으로 정확하게 받아올 수 있다는 것입니다. 여러 도메인의 데이터를 한 쿼리에 담아 가져올 수 있죠.
하지만 서비스 규모가 커지면 단일 GraphQL 서버로 모든 도메인을 다루기 어려워집니다. 결국 팀이 나뉘고, 코드베이스가 커지고, 각 도메인을 독립적으로 배포해야 하는 상황이 옵니다. 그렇다고 도메인별로 별도 GraphQL 서버를 운영하면 클라이언트는 여러 엔드포인트를 호출해야 합니다.
Federation은 이 문제를 해결하는 아키텍처 패턴입니다. 각 팀이 자신의 도메인에 집중하는 독립 GraphQL 서버(subgraph)를 운영하면서도 클라이언트에서는 하나의 통합 API에서 모든 데이터를 가져올 수 있게 합니다.
Apollo Federation은 이 패턴의 구체적인 구현입니다. 각 subgraph의 스키마를 합성해 통합 스키마인 supergraph를 만들고 Apollo Router가 이를 기반으로 요청을 오케스트레이션합니다.

Apollo Federation의 전체적인 구조는 위 이미지와 같습니다. 클라이언트는 Apollo Router라는 단 하나의 진입점만 바라봅니다. 요청이 들어오면 Router는 supergraph를 참고해 쿼리 계획을 세우고, 관련 subgraph들에 분산 요청한 뒤 응답을 합쳐 반환합니다. 클라이언트 입장에서는 일반 GraphQL API를 쓰는 것과 완전히 동일합니다.
두 가지 문제
저희 팀도 이 구조로 개발하고 있었습니다. 프론트엔드는 개발 서버를 바라보게 하고, 서버 작업은 GraphQL Playground에서 서비스만 따로 띄워 테스트하는 식이었죠. 클라이언트/서버를 따로 개발하는 환경에서는 문제가 없었지만, 로컬에서 두 영역을 함께 개발하려니 두 가지 문제가 드러났습니다.
1. Supergraph 없이 단독 실행 불가
특정 서비스만 로컬에 띄우면 Playground에서는 직접 접근해 쿼리를 테스트할 수 있습니다. 하지만 클라이언트는 subgraph에 직접 연결하지 않고 Router를 통해 요청하는 구조이기에 Router 없이는 연동 자체가 불가능합니다. 또한 쿼리는 대부분 여러 도메인에 걸쳐 있어 특정 서비스 단독으로는 실제 사용 흐름을 검증하기 어렵습니다.
결국 Apollo Router를 로컬에 띄워야 하는데, 여기서 subgraph 단독으로 실행하게 되는 경우 다음과 같은 문제가 발생합니다.
supergraph 합성 시도
→ service-a 스키마 ✓ (local 실행)
→ service-b 스키마 x
→ service-c 스키마 x
→ service-d 스키마 x
결과: Router 실행 불가Router는 supergraph를 기반으로 라우팅합니다. supergraph는 모든 subgraph의 스키마를 합성해서 만들어지는데, service-a 외에 나머지가 없다면 합성 단계에서 바로 막혀 Router가 뜨지 않습니다.
그렇다고 모든 서비스를 로컬에 올리는 건 현실적이지 않습니다.
- 서비스마다 DB, Redis 등 별도 인프라 필요
- 내 변경사항을 정확히 검증하기 위해 다른 서비스가 항상 최신 상태 유지
- 서비스의 수에 비례해서 로컬 머신의 부담
2. 서버 간 gRPC 통신
service-a만 로컬에 두고 테스트하는 경우라면 문제없습니다. 문제는 service-a와 service-b 간 gRPC 통신까지 로컬에서 테스트해야 하는 경우입니다. service-a와 service-b를 둘 다 로컬에 띄워도, service-a의 .env에 있는 B_GRPC_HOST는 여전히 dev 서버 주소입니다. 환경변수를 업데이트하지 않으면 gRPC 호출은 로컬 service-b가 아닌 dev service-b로 향합니다.
GraphQL 요청은 Router가 중개하기 때문에 이런 문제가 없지만, gRPC는 서버끼리 직접 연결하는 구조라 환경변수에 명시된 주소로 바로 연결됩니다.
┌────────────────────────────────────────────────────────────┐
│ 로컬 서비스 │
│ │
│ ┌─────────────┐ gRPC ┌─────────────┐ │
│ │ service-a │ ──────→│ service-b │ │
│ └─────────────┘ └─────────────┘ │
│ .env: B_GRPC_HOST=dev서버 ← 여전히 dev 주소 │
└────────────────────────────────────────────────────────────┘수동으로 .env를 고칠 수는 있지만, 매번 환경변수를 직접 수정하는 과정이 반복될수록 개발 흐름이 끊깁니다.
Apollo Router 스크립트로 로컬 환경 구성하기
The Rover dev Command로도 로컬과 dev 스키마를 혼합해 supergraph를 합성할 수 있지만, 서비스 간 gRPC 환경변수 처리는 지원하지 않습니다. 그래서 gRPC 통신까지 로컬에서 검증하고 수정하기 위해서는 커스텀 스크립트가 필요했습니다.
router-config.yaml: Apollo Router 실행 설정start-local-router.sh: supergraph 합성부터 Router 실행까지 전 과정 처리
# router-config.yaml
supergraph:
listen: "0.0.0.0:4000"
introspection: true
path: /graphql
traffic_shaping:
all:
timeout: 60s
include_subgraph_errors:
all: true
headers:
all:
request:
- propagate:
matching: ".*"
cors:
origins:
- http://localhost:3000
- http://localhost:4000
allow_credentials: trueApollo Router 실행 설정 파일에는 포트(4000), CORS 허용 origin, 헤더 전파를 정의합니다. supergraph 스키마는 스크립트가 실행 시점에 주입합니다.
start-local-router.sh의 전체 흐름은 다음과 같습니다.
# start-local-router.sh
main() {
load_env() # dev 서버 URL 로드
ensure_dependencies() # Rover CLI, Apollo Router 없으면 자동 설치
backup_env_files() # .env 백업
update_grpc_hosts() # gRPC 호스트 변경 (두 서비스 모두 로컬일 때만)
start_services() # 서비스 시작 (이미 실행 중이면 재사용)
compose_supergraph() # 로컬/dev URL 조합 → supergraph 합성
run_router() # Apollo Router 실행 (localhost:4000)
}
cleanup() { # Ctrl+C 시 자동 실행
stop_services()
restore_env_files() # .env 원복
}스크립트 실행 시 인자로 전달한 서비스 이름이 LOCAL_SERVICES 배열로 관리됩니다. pnpm dev:router service-a service-b를 실행하면 LOCAL_SERVICES=("service-a" "service-b")가 되고, ALL_SERVICES는 등록된 전체 서비스 목록입니다. 이 두 배열을 기준으로 로컬/dev 여부를 판단합니다.
이 중 두 함수가 핵심입니다.
update_grpc_hosts- 두 서비스가 모두 로컬인 경우에만 실행됩니다. service-a만 로컬이라면.env의 dev 주소가 이미 정확하기 때문에 수정이 불필요합니다.LOCAL_SERVICES배열 크기를 확인해 2개 미만이면 바로 건너뜁니다. 단, 이 함수는 모든 서비스가 동일한DEV_GRPC_HOST를 공유한다는 전제로 동작합니다. 서비스 간 실제 gRPC 호출 관계는 파악하지 않으며, 로컬 서비스가 2개 이상이면 해당 서비스들의.env를 일괄 치환합니다.
update_grpc_hosts() {
[ ${#LOCAL_SERVICES[@]} -lt 2 ] && return
for service in "${LOCAL_SERVICES[@]}"; do
local grpc_port="${SERVICE_GRPC_PORTS[$service]}"
sed -i "" "s|${DEV_GRPC_HOST}|localhost:${grpc_port}|g" "${service}/.env"
done
}compose_supergraph-LOCAL_SERVICES에 있는 서비스는localhost, 나머지는 dev URL로 Rover CLI 설정 파일을 조합한 뒤 supergraph를 합성합니다. 어떤 서비스 조합이든 이 함수 하나로 처리됩니다.
compose_supergraph() {
local config_file
config_file=$(mktemp)
cat > "$config_file" << 'CONF'
federation_version: =2.0.0
subgraphs:
CONF
for service in "${ALL_SERVICES[@]}"; do
local url
if [[ " ${LOCAL_SERVICES[*]} " =~ " ${service} " ]]; then
url="http://localhost:${SERVICE_PORTS[$service]}/graphql"
else
url="${DEV_URLS[$service]}/graphql"
fi
printf " %s:\n routing_url: %s\n schema:\n subgraph_url: %s\n" \
"$service" "$url" "$url" >> "$config_file"
done
rover supergraph compose --config "$config_file" > supergraph.graphql
rm "$config_file"
}사용 방법은 단순합니다.
# service-a 시작 → supergraph 합성 → Router 실행
pnpm dev:router service-a
# 두 서비스 시작 → gRPC 업데이트 → supergraph 합성 → Router 실행
pnpm dev:router service-a service-b스키마 합성부터 gRPC 연결, 로컬 프론트엔드 연동까지 따로따로 해결해야 했던 문제들이 하나의 명령어로 처리되면서, 그동안 모킹으로 채웠던 자리를 실제 서버가 대신할 수 있게 됐습니다.
마치며
Apollo Federation 환경에서 로컬 개발을 어렵게 만들었던 두 가지를 이렇게 해결했습니다.
- Supergraph 없이 단독 실행 불가: Rover CLI로 로컬 서비스 스키마와 dev 서버 스키마를 합성해 supergraph 구성
- 서버 간 gRPC 통신: 두 서비스가 모두 로컬일 때
.env내부의 gRPC Host를 자동으로 수정하고 종료 시 원복
사실 이 작업을 시작하게 된 배경은 작은 불편함이었습니다. 기능 하나를 추가할 때 서버 작업을 먼저 진행하고, 배포 이후에야 클라이언트 코드를 수정할 수 있는 과정 자체가 병목으로 느껴졌거든요. 그래서 스스로 질문을 던졌습니다. 클라이언트 개발을 할 때 매번 모킹을 하는 게 최선일까? 모킹이 실제 환경을 대체할 수 있는 걸까? 실제 서버와 함께 개발할 수는 없는 걸까?
물론 과거의 저였다면 질문에서 그칠 수도, 혹은 "Rover CLI가 로컬과 dev URL을 혼합해 supergraph를 합성하는 게 가능한지"와 같은 설계 판단 지점을 검증하고 개념을 이해하며 구현하는데에만 2주가 넘는 시간이 걸릴 수도 있었을 것입니다.
하지만 AI와 Apollo Federation을 이루는 낯선 개념들을 이해하고, 설계 방향을 의논하고, 코드를 구현하고, 의도와 다르게 동작하면 이유를 물어 원인을 파악한 뒤 다시 시도하는 루프를 반복하며 그 검증 루프를 훨씬 짧게 유지할 수 있었습니다.
이 과정에서 낯선 개념을 마주했을 때 일단 뛰어드는 것과 거기서 설계 판단을 빠르게 내리고 수정하는 루프를 짧게 유지하는 것이 중요하다는 점을 배울 수 있었습니다.