TIP/AI

AI로 개발하는 모노레포에서, Hono를 쓰면 스웨거가 더 이상 필요 없는 이유

고생쨩 2026. 7. 20. 08:59

AI 에이전트로 개발하는 모노레포 환경에서 Hono를 쓰면 스웨거(Swagger/OpenAPI)가 사실상 불필요해짐. 이유는 하나임 — 타입 자체가 API 명세가 되기 때문. 서버 타입을 클라이언트가 직접 import 하니 별도 명세 문서가 필요 없고, AI는 그 타입을 곧바로 읽어 코드를 생성함. 스웨거가 하던 "명세 공유"와 "문서화" 역할을 타입 시스템이 대신함.


원래 스웨거가 왜 필요했나

스웨거의 본질은 명세 공유(spec sharing)임. 서버와 클라이언트가 물리적으로, 조직적으로 분리돼 있을 때 필요한 계약서 역할.

  • 백엔드팀과 프론트팀이 다른 저장소, 다른 언어로 개발함.
  • 그래서 "이 엔드포인트는 뭘 받고 뭘 뱉는지"를 적은 문서가 필요함.
  • 그 문서가 바로 OpenAPI 스펙(JSON/YAML)이고, Swagger UI로 렌더링함.

핵심 문제는 이 명세가 실제 코드와 따로 논다는 것(drift). 코드를 고쳐도 스펙을 안 고치면 문서가 거짓말을 함. 데코레이터나 zod-openapi 같은 도구로 코드에서 스펙을 뽑아내는 방식도 있지만, 어쨌든 "코드 → 스펙 → 클라이언트 코드 생성"이라는 중간 단계가 존재함.


Hono RPC가 이 구조를 뒤집음

Hono RPC는 명세 공유 문제를 문서가 아니라 TypeScript 타입으로 해결함. 이게 핵심 전환.

서버는 라우트 정의의 타입만 export 함.

// server.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const route = new Hono()
  .post(
    '/posts',
    zValidator('json', z.object({ title: z.string(), body: z.string() })),
    (c) => {
      const { title } = c.req.valid('json')
      return c.json({ id: 1, title }, 201)
    }
  )

export type AppType = typeof route

클라이언트는 이 타입만 가져와서 hc로 감쌈.

// client.ts
import { hc } from 'hono/client'
import type { AppType } from './server'

const client = hc<AppType>('http://localhost:8787/')

// 입력 타입, 출력 타입, 상태 코드까지 전부 자동 추론됨
const res = await client.posts.$post({
  json: { title: 'hello', body: 'world' },
})

여기서 일어나는 일:

  • 코드 생성 없음. openapi-generator 같은 빌드 단계가 아예 없음. typeof route를 import 하는 게 전부임.
  • 타입이 곧 단일 진실 공급원(single source of truth). 서버 코드를 고치는 순간 클라이언트 타입이 자동으로 바뀜. drift가 원천적으로 불가능함.
  • zValidator가 스키마 역할. zod로 정의한 입력 스키마가 그대로 클라이언트 입력 타입이 됨. 문서를 따로 쓸 필요가 없음.
  • 응답도 타입 안전. c.json(data, 201)의 상태 코드와 페이로드가 판별 유니온(discriminated union)으로 클라이언트에 전달됨. 성공/에러 분기까지 타입으로 잡힘.

Hono 개발자 본인도 RPC가 "OpenAPI나 gRPC로 하고 싶었던 걸 대신 채워줄 수 있다"고 말함. 즉 보완재가 아니라 대체재로 본 것.


모노레포가 이걸 완성함

Hono RPC가 진짜 빛나는 조건이 바로 모노레포임. 서버와 클라이언트가 같은 저장소, 같은 언어(TypeScript)에 있을 때 타입을 직접 import 할 수 있기 때문.

  • 프론트가 import type { AppType } from '@/server/app' 한 줄로 서버 계약을 그대로 씀.
  • 네트워크 경계를 넘어도 IDE 자동완성, 타입 체크가 전부 살아있음.
  • TypeScript Project References(composite: true + references)를 걸어주면 대형 모노레포에서도 타입이 any로 뭉개지지 않고 제대로 추론됨.

정리하면, 스웨거는 "분리된 두 세계를 잇는 다리"였는데, 모노레포는 애초에 세계가 하나임. 다리가 필요 없어짐.


AI 개발이 결정타를 날림

여기에 AI 에이전트 개발이 더해지면 스웨거의 마지막 존재 이유까지 사라짐.

스웨거 UI의 또 다른 용도는 사람이 눈으로 읽는 문서였음. "이 API가 뭘 받는지" 개발자가 브라우저로 확인하는 용도. 그런데 AI 에이전트는 브라우저로 문서를 볼 필요가 없음.

  • AI는 소스 코드와 타입을 직접 읽음. zod 스키마, 라우트 핸들러, AppType을 그대로 컨텍스트로 넣으면 됨.
  • 타입이 곧 스펙이니, AI에게 정확하고 검증 가능한 계약이 이미 코드 안에 있음. 별도 OpenAPI 파일을 만들어 먹일 이유가 없음.
  • 오히려 중간에 낀 OpenAPI 스펙은 drift 위험이 있는 부정확한 2차 정보임. AI 입장에서 원본 타입이 더 신뢰할 만함.
  • AI가 클라이언트 코드를 짤 때도 hc<AppType>가 컴파일 타임에 검증해 줌. 타입이 안 맞으면 즉시 에러가 남 → AI의 환각(hallucination)을 타입 시스템이 잡아줌.

즉 AI 시대에는 "AI가 읽을 문서"를 따로 유지하는 게 아니라, AI가 읽을 코드(타입)를 잘 짜는 것이 문서화임. Hono RPC가 정확히 그 방향임.


그래도 스웨거가 필요한 경우

균형을 위해 짚음. 스웨거가 여전히 유효한 상황도 분명히 있음.

  • 외부 공개 API. 우리 타입을 import 할 수 없는 제3자(다른 회사, 다른 언어 클라이언트)가 소비자라면 OpenAPI 스펙이 여전히 표준 계약서임.
  • 비(非) TypeScript 클라이언트. 모바일 네이티브, Python, Go 등에서 붙는다면 언어 중립적인 스펙이 필요함.
  • 공식 문서 포털. 대외적으로 렌더링된 API 문서(Swagger UI, Scalar 등)를 제공해야 하는 제품이라면 필요함.
  • 계약 기반 테스트/목킹. OpenAPI 스펙 기반으로 mock 서버를 돌리거나 계약 테스트를 하는 조직 관행이 있다면 유지 가치 있음.

참고로 Hono도 이걸 버리라고 강요하지 않음. @hono/zod-openapi로 필요할 때 OpenAPI 스펙을 뽑아낼 수 있게 열어둠. 즉 스웨거를 기본값이 아니라 선택지로 격하시킨 것.


결론

정리하면 이거임:

  • 스웨거의 역할은 "분리된 서버·클라이언트 간 명세 공유 + 사람이 읽는 문서"였음.
  • 모노레포는 서버·클라이언트를 하나로 합쳐 명세 공유를 타입 import로 대체함.
  • Hono RPC는 코드 생성 없이 AppType 하나로 이걸 실현함 — drift 제로, 단일 진실 공급원.
  • AI 에이전트는 문서가 아니라 타입을 직접 읽어 코드를 짜고, 타입 시스템이 그 결과를 검증함.
  • 결국 내부용 모노레포 + Hono + AI 조합에선 스웨거가 불필요한 중간 레이어로 남음.
  • 단, 외부 공개·비TS 클라이언트·공식 문서가 필요하면 여전히 유효함. 기본값이 아니라 선택지일 뿐.

한 줄 요약: 타입이 곧 문서이고 계약인 시대. Hono 모노레포에선 스웨거를 기본으로 깔 이유가 없음.