Skip to content

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

김수빈 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. 새로운 Swagger 문서를 받기
  2. pnpm generate-api 명령어 실행하여 타입과 API 클라이언트 재생성
  3. 생성된 코드에 맞춰 서비스 레이어 수정
  4. Query Hook 업데이트
  5. MSW 핸들러 수정

주의사항

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

장점

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