Node.js 18 LTS에서 내장 fetch API 사용하기
배경
10월에 Node.js 18이 LTS로 전환되면서 실험적 기능이었던 내장 fetch API를 프로덕션에서 사용할 수 있게 되었다. 기존 프로젝트에서 axios와 node-fetch를 혼용하고 있었는데, 이 기회에 네이티브 fetch로 통일하기로 결정했다.
마이그레이션 과정
기존에는 다음과 같이 axios를 사용했다.
const axios = require('axios');
const response = await axios.get('https://api.example.com/data', {
headers: { 'Authorization': `Bearer ${token}` },
timeout: 5000
});
const data = response.data;
네이티브 fetch로 변경하면서 몇 가지 차이점을 발견했다.
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetch('https://api.example.com/data', {
headers: { 'Authorization': `Bearer ${token}` },
signal: controller.signal
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
clearTimeout(timeoutId);
주의사항
-
에러 처리: axios는 4xx, 5xx 응답에서 자동으로 throw하지만, fetch는 네트워크 실패시에만 reject된다. 수동으로
response.ok체크가 필요하다. -
타임아웃: axios의 timeout 옵션과 달리 AbortController를 직접 구현해야 한다.
-
인터셉터: axios의 interceptor 기능이 없어서 공통 헤더나 에러 처리를 위한 wrapper 함수를 만들었다.
async function apiRequest(url, options = {}) {
const defaultHeaders = {
'Content-Type': 'application/json',
...(token && { 'Authorization': `Bearer ${token}` })
};
const response = await fetch(url, {
...options,
headers: { ...defaultHeaders, ...options.headers }
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
결과
package.json에서 axios와 node-fetch 의존성을 제거하면서 번들 사이즈가 약 300KB 줄었다. 네이티브 API를 사용하니 보일러플레이트는 늘었지만, 외부 의존성 관리 부담이 줄어든 것은 확실한 이점이다.
간단한 API 호출이 대부분이라면 네이티브 fetch로도 충분하다고 판단된다.