[OpenAPI 2탄] openapi-fetch + Swagger로 type-safe한 API 클라이언트 자동화하기 (feat. TanStack Query)

유태승·2025년 3월 6일
post-thumbnail

이전 글에서 OpenAPI TypeScript를 이용해서 Swagger에 정의된 API Schema들을 자동으로 타입으로 생성하는 방법에 대해 알아봤습니다.

이번 포스팅에서는 schema 타입뿐만 아니라, Swagger 기반으로 fetcher client를 생성해보고 마지막에는 TanStack Query까지 적용해보겠습니다.

openapi-fetch란?

openapi-ts 문서에는 다음과 같이 나와있습니다.

openapi-fetch는 OpenAPI 스키마를 가져와 사용하는 타입 안전한(fetch) 클라이언트입니다.

openapi-fetch는 openapi-typescript 처럼 OAS파일(예: swagger)을 읽어서 클라이언트를 만드는 방식이 아니라,
openapi-typescript로 만들어진 파일을 이용해서 클라이언트를 만듭니다.

사용방법

import createClient from "openapi-fetch";
import { paths } from '@schema'; // openapi-typescript로 만들어진 파일

const client = createFetchClient<paths>({
  baseUrl: import.meta.env.VITE_API_BASE_URL,
});

생성된 파일의 paths와 서버의 baseUrl을 넣어서 클라이언트를 만들 수 있습니다.

API 호출은

const {
  data,
  error,
} = await client.GET("/api/v1/problems/{id}", {
  params: {
    path: { id: "123" },
  },
});

await client.POST("/problems", {
  body: {
    title: "New Problem",
    ...
  },
});

이와 같은 방식으로 사용할 수 있습니다.

이렇게 코드로만 보면 js fetch를 쓰거나, axios등과 같은 라이브러리를 쓰는것과 뭐가 다르지? 라고 생각하실 수도 있습니다.

어떤 장점이 있을까요?

장점

자동완성

영상과 같이 스웨거를 기반으로 메소드별 API path와 parameter 등을 모두 자동 완성시켜줍니다!!

타입 체크

또한 parameter의 타입체크도 제공하는데,
만약 number 타입이어야할 id가 string으로 전달된다면 위와 같이 에러도 띄워줍니다.
뿐만 아니라 필수로 보내야하는 값을 빼먹었다면 이것도 알려줍니다.

성능

openapi-fetch는 성능적인 부분에서도 장점을 갖습니다.

제가 주로 사용했던 axios 라이브러리의 경우에는 번들 사이즈가 크고 무겁다는 단점이 있는데, 그에 비해 훨씬 가볍고 빠른 API 클라이언트를 제공합니다.

공식문서에도 다음과 같이 사이즈와 속도 비교가 되어있는 것을 볼 수 있습니다.

즉 openapi-fetch를 이용하여 API 클라이언트를 만들면 더 안전하고, 더 편하고, 더 빠른 API 호출이 가능합니다.

이런게 정말 우아하게 개발하는거 아닐까요?

다양한 옵션과 더 자세한 내용은 공식문서를 참고해보시면 됩니다.


TanStack Query와 함께 쓰기

위에서 살펴봤듯, openapi-fetch를 이용해서 클라이언트를 만들고 API 통신을 하는건 충분히 매력적이고 편하지만,
요즘 FE 프로젝트에서는 TanStack Query(React Query)를 사용하는 경우가 많습니다.

그래서 지금부터는 openapi-fetch 클라이언트를 이용해서 TanStack Query hook을 만들어 보겠습니다.

방법1) 커스텀 훅 만들기

처음에는 Type-safe TanStack Query with OpenAPI 이 글을 참고해서 TanStack Query 훅 만들기를 시도했습니다.

사실 그저 따라치는것에 가까웠기 때문에 별 문제없이 별 문제가 없는 것처럼 보였습니다.

하지만 UseQuery 훅에서 문제가 생겼습니다...

어마 무시한 타입에러가 발생했습니다…
해당 오류는 useQuery 훅의 options 타입에 관련된 오류였습니다.

해당 오류를 잡기위해 chatGPT와 copilot과 함께 이런저런 시도를 해봤지만,,, 저의 능력 부족으로 모두 실패했습니다…

몇가지 타입 에러가 나지 않는 방법은 있었지만, 이는 타입이 제대로 설정되지 않아, 자동완성도 안되고 훅을 사용할때 에러가 발생했습니다 ㅠㅠ

결국 이 방법을 hook 만들기를 실패하고 차선책으로
openapi-react-query 를 사용하기로 했습니다.


방법2) openapi-react-query

openapi-react-query란?
openapi-fetch와 마찬가지로 openapi-ts제공하는 라이브러리로,
OpenAPI schema 를 이용해서 TanStack Query hook을 사용할 수 있게 해주는 라이브러리 입니다.

import { paths } from '@schema';
import createFetchClient from 'openapi-fetch';
import createClient from 'openapi-react-query';

const client = createFetchClient<paths>({
  baseUrl: import.meta.env.VITE_API_BASE_URL,
});

export const $api = createClient(client); // 이 부분만 추가

사용법은 정말 간단합니다.
기존의 openapi-fetch 클라이언트를 openapi-react-query가 제공하는 createClient로 감싸주기만 하면 끝입니다.

useQuery 사용법

const { data } = $api.useQuery(
  'get',
  '/api/v1/problems/{id}',
  {
    params: {
      path: {
        id: problemId,
      },
    },
  },
  {
    staleTime: 1000 * 60 * 60,
    gcTime: 1000 * 60 * 60 * 24,
  }
);

useMutation 사용법

const { mutate } =  $api.useMutation('post', '/api/v1/problems');

mutate(
  {
    body: data,
  },
  {
    onSuccess: (data) => {
	    // 성공시 동작
    },
  }
);

위에서 만든 $api 클라이언트가 return 해주는 data, mutate 등은
TanStack Query의 useQuery와 useMutation이 return 해주는 값과 동일합니다!
(평소와 똑같이 사용하시면 됩니다.)

openapi-fetch를 사용할 때와 유사하지만, API 메서드를 첫번째 인자로 넘겨줘야 한다는 차이가 있습니다.

당연히 API 파라미터, react qeury 옵션들 (staleTime, gcTime 등) 모두 type-safe하게 관리되고, 자동완성 기능도 제공합니다! 👍👍


query key

여기서 생기는 의문이 있습니다.

저희는 보통 TanStack Query를 사용할때 queryKey를 설정해줍니다.(특히 useQuery의 경우)

그런데 위의 사용코드에서는 query key를 설정해주는 부분이 없는데, 어떻게 설정해야 할까요?

정답은… 안해도 됩니다!
openapi-react-query가 자동으로 query key를 생성해줍니다.

Devtools 를 보면 method, path, params등을 이용해서 query key가 생성된 것을 확인할 수 있습니다.

그럼 query key가 필요할때는 어떻게 해야할까요?

$api.queryOptions('get', '/api/v1/problems/search').queryKey,

요런식으로 해서 특정 query의 query key를 가져올 수 있습니다.

정말 간단하지 않나요? 이제 더 이상 queryKey를 신경쓸 필요가 없어졌습니다.
뭐라고 지을지 고민하지 않아도 되고, 뭐라고 지었는지 찾아볼 필요도 없습니다!


그럼 이렇게 생성된 queryKey를 이용해서 위의 mutate 함수의 onSuccess 부분을 수정해보겠습니다.

const queryClient = useQueryClient();
const { mutate } =  $api.useMutation('post', '/api/v1/problems');

mutate(
  {
    body: data,
  },
  {
    onSuccess: (data) => {
      queryClient.invalidateQueries({
        queryKey: $api.queryOptions('get', '/api/v1/problems/search').queryKey,
      });
    },
  }
);

problem을 post(등록) 하고, 성공했을 때 problems/search(조회 API)를 invalidate시켜 다시 불러오는 코드입니다.


결론

이렇게 해서 type-safe하고, 자동 완성되는 TanStack Query hook을 만들어봤습니다.

openapi-react-query 라이브러리가 npm downloads가 많은 라이브러리는 아니라 조금 찝찝함하긴 하지만, 그래도 axios를 사용하는 것보다 훨씬 안전하고 멋진 API 클라이언트를 만들어서 API 통신 과정에서의 오류도 훨씬 줄어들고, 생산성도 크게 오른 것을 체감할 수 있었습니다!

다들 꼭 한번 사용해보세요! ㅎㅎ

참고자료

profile
FE Developer

0개의 댓글