Skip to content

API 스펙 동기화 및 프론트엔드 API 연동 가이드

seonyoung Kang edited this page Dec 1, 2024 · 4 revisions

개요

이 가이드는 Swagger 문서를 기반으로 타입 안전한 프론트엔드 API 연동 환경을 구축하는 방법을 설명합니다. OpenAPI Generator, React Query, MSW를 활용하여 효율적이고 안정적인 개발 환경을 구성하는 과정을 다룹니다.

주요 도구

  • OpenAPI Generator: API 타입과 클라이언트 코드 자동 생성
  • React Query: 서버 상태 관리 및 데이터 요청 처리
  • MSW: API 모킹으로 백엔드 개발 전에도 테스트 가능한 환경



1. 초기 설정: OpenAPI Generator

1.1 설치 및 설정

# OpenAPI Generator CLI 설치
pnpm add -D @openapitools/openapi-generator-cli
// package.json
{
  "scripts": {
    "generate-api": "openapi-generator-cli generate -i swagger.json -g typescript-axios -o src/api/generated --skip-validate-spec"
  }
}

1.2 주요 기능

  • Swagger 문서로부터 TypeScript 타입과 API 클라이언트를 자동 생성
  • 생성된 코드는 src/api/generated 폴더에 저장되며, API 응답 타입과 호출 메서드를 포함



2. API 클라이언트 구성

// shared/api/client.ts
import { Configuration, FacilityControllerApi } from '@/api/generated';

const config = new Configuration({
  basePath: '' // Swagger 문서 경로와 일치
});

export const api = {
  facility: new FacilityControllerApi(config)
};
  • shared/api/client.ts에서 API 클라이언트를 중앙 관리
  • Configuration 객체를 통해 baseURL 등 공통 설정을 관리
  • 각 도메인별 API(예: FacilityControllerApi)를 구성하여 사용



3. API 연동 레이어 구현

프론트엔드의 API 연동은 다음 세 가지 레이어로 구성됩니다:

3.1 Service Layer

// entities/facility/api/facilityService.ts
import { api } from '@/shared/api/client';
import type { FacilityDetailsResponseDTO } from '@/api/generated';

export const facilityService = {
  // 생성된 Response DTO 타입 활용
  getFacilityDetail: async (id: string): Promise<FacilityDetailsResponseDTO> => {
    // API 클라이언트를 통한 데이터 요청
    const { data } = await api.facility.readFacilityDetails(id);
    return data;
  }
};
  • API 통신 로직 담당
  • API 클라이언트 사용
  • 순수한 데이터 요청/응답 처리

3.2 UI Layer

A. 기본 구현 (React Query 없이)

// 직접 서비스 사용 시
const FacilityDetail = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  const [data, setData] = useState<FacilityDetailsResponseDTO | null>(null);

  const fetchData = async () => {
    try {
      setIsLoading(true);
      const response = await facilityService.getFacilityDetail(id);
      setData(response);
    } catch (error) {
      setError(error as Error);
      toast.error('시설 정보를 불러오는데 실패했습니다.');
    } finally {
      setIsLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, [id]);

  // loading, error, data 상태에 따른 UI 렌더링
  return (
    <div>
      {isLoading && <LoadingSpinner />}
      {error && <ErrorMessage message={error.message} />}
      {data && <FacilityInfo facility={data} />}
    </div>
  );
};
  • UI 레이어에서 로딩, 에러 직접 상태 관리
  • useEffect로 데이터 요청
  • 사용자 피드백 처리
  • UI 렌더링 로직

B. React Query 활용

const FacilityDetail = () => {
  const { data, isLoading, error } = useFacilityDetail(id, {
    enabled: !!id,
    onError: (error) => {
      toast.error('시설 정보를 불러오는데 실패했습니다.');
    }
  });

  return (
    <div>
      {isLoading && <LoadingSpinner />}
      {error && <ErrorMessage />}
      {data && <FacilityInfo facility={data} />}
    </div>
  );
};
  • Query Hook 사용
  • React Query를 통한 상태 관리 자동화
  • 선언적인 데이터 fetching
  • 에러 및 로딩 상태 처리 간소화

3.3. Query Layer

// features/facility/model/constants.ts
export const QUERY_KEYS = {
  facilityDetail: (id: string) => ['facility', 'detail', id] as const
};

// features/facility/model/queries.ts
import { useQuery } from '@tanstack/react-query';
import { facilityService } from '../api/facilityService';

export const useFacilityDetail = (id: string, options?: UseQueryOptions) => {
  return useQuery({
    queryKey: QUERY_KEYS.facilityDetail(id),
    queryFn: () => facilityService.getFacilityDetail(id),
    ...options 
  });
};
  • React Query를 활용한 서버 상태 관리
  • 쿼리 키 관리
  • 캐싱, 재시도 등 처리



4. MSW 설정

MSW(Mock Service Worker)는 서비스 워커를 사용하여 네트워크 수준에서 API 요청을 가로채고 모의 응답을 제공합니다. 이를 통해 백엔드 API 완성 전에도 프론트엔드 개발을 진행할 수 있습니다.

// mocks/handlers/facility.ts
import { HttpResponse, http } from 'msw';
import type { FacilityDetailsResponseDTO } from '@/api/generated';

export const facilityHandlers = [
  // Swagger 문서의 경로와 동일하게 설정
  http.get<{ facilityId: string }>(
    '/api/facility-detail/:facilityId',
    ({ params }) => {
      // 생성된 DTO 타입과 동일한 형태로 응답
      const mockData: FacilityDetailsResponseDTO = {
        facilityId: params.facilityId,
        // ... 나머지 필드
      };

      return HttpResponse.json(mockData);
    }
  )
];
  • 실제 API가 완성되기 전에 모의 응답을 제공하는 핸들러를 설정
  • Swagger 문서의 경로와 동일한 엔드포인트를 사용
  • 생성된 DTO 타입과 동일한 구조로 응답을 작성하여 실제 API와 동일한 환경을 모사



API 스펙 변경 시 작업 순서

  1. SwaggerHub에서 API 스펙 문서 저장 후 git Sync 실행 시, 저장소에 swagger.json 파일 동기화
  2. GitHub actions를 통해 새로운 swagger.json 기반으로 API 코드 자동 재생성
  3. 생성된 코드에 맞춰 서비스 레이어 수정
  4. Query Hook 업데이트
  5. MSW 핸들러 수정

주의사항

  • 타입 단언(as) 사용 지양
  • API 응답 구조를 반드시 확인
  • 에러 처리 로직의 일관성 유지
  • MSW 모의 데이터는 실제 API 응답과 동일하게 구성

장점

  • 타입 안정성:
    • API 응답 타입 자동 생성
    • 컴파일 타임에 타입 오류 감지
  • 개발 효율성:
    • API 스펙 변경 시 자동 동기화
    • React Query를 통한 상태 관리 자동화
  • 테스트 용이성:
    • MSW를 통한 API 모킹
    • 백엔드 의존성 없는 개발 가능