API 요청 유틸리티 개발기

cho·2025년 1월 25일

💥 트러블 슈팅

목록 보기
5/11
post-thumbnail

이번 프로젝트에서는 Next의 app router를 사용하게 됐는데, axios를 사용하지 않고 Next의 fetch를 활용하고 싶었다. 이유는

  1. Next.js는 서버에서 각 요청이 지속적인 캐싱 및 재검증 방식을 설정할 수 있도록 Web fetch() API를 확장한다.

  2. 브라우저에서는 cache 옵션이 브라우저의 HTTP 캐시와 fetch 요청이 어떻게 상호작용할지를 결정하는 반면,Next.js에서는 이 확장을 통해 서버 측 fetch 요청이 프레임워크의 지속적인 데이터 캐시(Data Cache) 와 어떻게 상호작용할지를 cache 옵션으로 설정할 수 있다.

  3. 그리고 Server Components 안에서는 async와 await를 사용해 fetch를 직접 호출할 수 있다.

이러한 fetch 기능을 사용해보고 싶었고, axios와 혼용하여 쓰기보다는 fetch 하나만 사용하는것이 통일성이 있어 좋을 것이라고 생각되었다.

그렇게 개발을 시작하게 되었는데 아무래도 fetch는 각 api 함수마다 헤더와 바디를 모두 써야해서

// 지정가 취소
export async function cancelTrade({
  token,
  orderId,
}: CancelData): Promise<string> {
  if (token === null) {
    throw new Error();
  }
  try {
    const res = await fetch(
      `${process.env.NEXT_PUBLIC_API_URL}/api/account/order/${orderId}/cancel`,
      {
        method: "DELETE",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${token}`,
        },
      },
    );

    if (!res.ok) {
      throw new Error(`Failed to post transaction data: ${res.status}`);
    }

    return await res.text();
  } catch {
    throw new Error();
  }
}

모든 api 함수들이 이런 형태를 띄게 되었다. 중복되는 코드들이 너무 많다고 느꼈고 하나의 함수를 만들어서 축약할 수 있으면 좋겠다는 생각이 들었다. 그래서 코드 리팩토링을 하며 api request를 개발하였다.

아래 코드와 같이 메소드, 엔드포인트, 옵션을 지정해 개발자가 원하는 api request에 대응할 수 있으며, 에러처리도 함께 되는 함수를 작성했다.

export default async function makeApiRequest<T, R = string>(
  method: "GET" | "POST" | "PUT" | "DELETE",
  endpoint: string,
  options: {
    token?: string | null;
    data?: T;
    responseType?: "json" | "text";
  },
): Promise<R> {
  const { token, data, responseType = "json" } = options;

  const headers: HeadersInit = {
    ...(data && { "Content-Type": "application/json" }),
    ...(token && { Authorization: `Bearer ${token}` }),
  };

  const config: RequestInit = {
    method,
    headers,
    ...(data && { body: JSON.stringify(data) }),
  };

  const response = await fetch(
    `${process.env.NEXT_PUBLIC_API_URL}${endpoint}`,
    config,
  );

  if (!response.ok) {
    const errorData = await response.text();
    throw new Error(
      errorData || `요청 실패: ${response.status} ${response.statusText}`,
    );
  }

  return responseType === "json" ? response.json() : response.text();
}

이렇게 작성하고 나니 사용하는 측에서는

export async function cancelTrade({
  token,
  orderId,
}: CancelData): Promise<string> {
  return makeApiRequest("DELETE", `/api/account/order/${orderId}/cancel`, {
    token,
    responseType: "text",
  });
}

이와 같이 간단한 api 함수만 작성할 수 있게 되어 훨씬 더 간단해진 모습을 볼 수 있었다. 처음 코드의 절반 이상이 줄어들었고, 중복되는 로직을 계속 반복해서 작성하지 않아도 되어 뿌듯했다✨

0개의 댓글