WEGO ) Custom Fetcher를 작성하여 API 공통 로직을 중앙 집중화하기!
API 통신은 현대 웹 애플리케이션의 핵심 요소입니다. 하지만 기본적인 fetch API를 직접 사용하면 다양한 문제점들이 발생할 수 있습니다. 이번 포스트에서는 우리 프로젝트에 Custom Fetcher를 도입하게 된 이유와 구현 방식, 효과에 대해 살펴보겠습니다.
▪︎ 기존 fetch 사용 방식의 단점
1 | export const getReview = async ({ pageParam, sortOrder }: ReviewParams): Promise<ReviewResponse> => { |
기존에 API 요청 코드들을 작성할 때에는 각 요청마다 method와 credentials와 같은 config 코드들을 작성해야 했고, 반환할 에러 타입들도 각각 작성해주어야 했습니다. 추가로 Next.js의 서버 컴포넌트와 같이 쿠키가 자동으로 실려가지 않는 상황에 대비하기 위한 코드들도 작성해야 했습니다.
따라서 API 관련 코드를 작성하는 개발자들은 각 API가 어디서 쓰일지, 어떤 설정이 필요한지를 매번 생각하고 코드에 적용해야 했습니다. 또한 이러한 API 요청 코드 구조의 변화가 있을 경우, 각 API 요청 코드를 전부 찾아가 일일이 수정해주어야 했습니다. 뿐만아니라 각 개발자마다 다른 구현 방식으로 인해 코드의 일관성이 떨어졌고, 이는 가독성을 저해하는 요소였습니다.
정리해보자면 다음과 같은 문제점들이 있었습니다.
- 반복적인 코드
- 기본 URL 설정
- 헤더 설정
- 에러 처리
- 응답 파싱
- 일관성 부족
- 개발자마다 다른 구현 방식
- 에러 처리 방식의 차이
- 타입 처리의 비일관성
- 유지보수의 어려움
- API 로직 변경 시 여러 곳에 전체적인 수정 필요
위와 같은 기본 fetcher의 단점을 극복하기 위해 우리 프로젝트에 Custom Fetcher를 만들었습니다.
▪︎ Custom Fetcher 구현
▫︎ 전체 코드
1 | import { FetcherError } from "@/@types/api"; |
▫︎ 기본 구조 설정
1 | type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; |
우선 Custom Fetcher의 기본 구조를 타입스크립트를 통해 설정했습니다. 이를 통해 HTTP 메서드의 타입 안정성을 확보하였고, 설정 객체의 타입 또한 명확하게 정의했습니다.
▫︎ 핵심 Fetcher 함수
1 | async function fetcher<T>(endpoint: string, config?: FetcherConfig): Promise<T> { |
fetcher 함수에서는 타입스크립트 제네릭을 통해 응답 타입을 보장할 수 있도록 하였습니다. 또한 기본적인 설정에 사용자 설정을 병합하도록 하여 보다 유연하고 확장성있게 사용될 수 있도록 하였습니다. 뿐만아니라 Next.js SSR 환경을 고려한 토큰 처리도 해주었습니다.
특수케이스 처리
1 | if (mergedConfig.body instanceof FormData) { |
우리 앱에서 기본적으로 사용되는 api 의 content type인 application/json이 아닌 회원가입, 여행 만들기 등에 활용되는 FormData 타입일 때 content type을 바꿔주도록 구성했습니다. FormData 처리를 위해 Content-Type을 자동으로 제거하였고, JSON 데이터를 자동으로 직렬화하도록 하였습니다.
에러 처리
1 | if (!response.ok) { |
에러처리는 Tanstack Query와의 조합을 생각하여 Query를 작성할 때 가독성이 좋을 것이라고 판단한 구조로 커스텀 에러 객체를 반환하도록 하였습니다. 이를 통해 HTTP 상태 코드를 포함한 객체와 메시지로 일관된 에러 형식을 제공할 수 있었습니다.
1 | ... |
백엔드에서 반환하는 response의 status를 바로 읽어 에러객체에 저장해주었고 실제 Tanstack Query에 사용할 때에는 에러 객체에서 status를 읽는 방식으로 가독성을 높이고 구조를 통일했습니다.
▫︎ 편의성 메서드
1 | export const http = { |
위에 서술한 fetcher함수를 이용하여 편의성 메서드를 작성하였습니다. 이를 통해 메서드별 타입을 최적화하고, 팀원들에게 간결한 사용법을 통한 일관된 인터페이스를 제공할 수 있었습니다.
▪︎ 실제 사용 예시
1 | export const getMyReview = (limit: number, offset: number) => { |
처음 소개했던 기존 방식에 비해 훨씬 간결하고 가독성있게 API 요청 코드를 관리할 수 있게 되었습니다. API 설정 관련하여 고민하며 작성했던 기존 코드에 비해 중앙 집중화되어 관리되는 Custom Fetcher를 통해 실제 팀원들이 API 요청을 작성할 때에는 “메서드”, “엔드포인트”, “반환 타입”만 신경쓴다면 바로 쉽게 사용할 수 있었습니다.
▪︎ 마치며
Custom Fetcher는 단순한 HTTP 클라이언트를 넘어 애플리케이션의 데이터 통신 계층을 체계화합니다. 타입 안정성, 코드 재사용성, 유지보수성을 모두 고려한 설계로, 개발 생산성을 크게 향상시킬 수 있습니다. 특히 Next.js의 서버 컴포넌트와 미들웨어 환경에서도 원활하게 동작하도록 구현되어, 현대적인 웹 애플리케이션 개발에 매우 적합합니다.