Node.js 18의 fetch API 도입과 기존 axios 코드 마이그레이션

배경

Node.js 18이 LTS로 올라오면서 가장 눈에 띄는 변화는 native fetch API 지원이다. 그동안 node-fetch나 axios 같은 라이브러리에 의존했던 HTTP 클라이언트 로직을 표준 API로 처리할 수 있게 되었다.

사내 API 서버에서 axios를 사용하던 부분을 fetch로 전환하기로 결정했다. 번들 사이즈 감소보다는 브라우저와 서버 코드의 일관성 확보가 주된 목적이었다.

axios vs fetch 차이점

실제 마이그레이션하며 주의했던 부분들:

1. 에러 처리

axios는 4xx, 5xx 응답을 자동으로 reject하지만, fetch는 네트워크 에러만 reject한다.

// axios
try {
  const res = await axios.get('/api/users');
  return res.data;
} catch (error) {
  // 4xx, 5xx 자동 catch
}

// fetch
try {
  const res = await fetch('/api/users');
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return await res.json();
} catch (error) {
  // 명시적 체크 필요
}

2. JSON 변환

axios는 res.data로 자동 파싱되지만, fetch는 res.json() 호출이 필요하다.

3. Timeout 처리

axios의 timeout 옵션을 AbortController로 대체했다.

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);

try {
  const res = await fetch('/api/data', { signal: controller.signal });
  clearTimeout(timeoutId);
  return await res.json();
} catch (error) {
  if (error.name === 'AbortError') {
    // timeout 처리
  }
}

마이그레이션 전략

전체 코드를 한번에 바꾸지 않고, 새로 작성하는 코드부터 fetch를 사용했다. 기존 axios 코드는 레거시로 남겨두되, 리팩토링 기회가 있을 때 점진적으로 전환하는 방식을 택했다.

interceptor 기능이 필요한 인증 로직 등은 당분간 axios를 유지하기로 했다. fetch로 구현하려면 wrapper 함수를 직접 만들어야 해서 득보다 실이 클 것 같았다.

결론

fetch가 만능은 아니지만, 간단한 HTTP 요청에는 충분히 실용적이다. 무엇보다 의존성 하나를 줄일 수 있다는 점이 장점이다. 다만 프로젝트 규모가 크다면 axios의 편의 기능들이 여전히 유용할 것 같다.