Node.js 18 LTS 전환하면서 마주친 fetch API 이슈
배경
10월 25일 Node.js 18이 LTS로 전환되면서 회사 프로젝트의 런타임 업그레이드를 검토했다. 가장 눈에 띄는 변화는 fetch API가 실험적 기능에서 정식 지원으로 전환된 점이었다.
기존 프로젝트는 axios를 사용 중이었지만, 번들 사이즈 감소와 표준 API 사용을 위해 fetch 전환을 고려했다.
마주친 문제
1. 인터셉터 패턴
axios의 인터셉터 패턴을 많이 사용하고 있었다.
axios.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${getToken()}`;
return config;
});
fetch는 이런 패턴을 기본 제공하지 않아 래퍼 함수를 직접 구현해야 했다.
const fetchWithAuth = async (url, options = {}) => {
const headers = {
...options.headers,
Authorization: `Bearer ${getToken()}`
};
return fetch(url, { ...options, headers });
};
2. 에러 핸들링
axios는 4xx, 5xx 응답을 자동으로 reject하지만, fetch는 네트워크 오류만 reject한다.
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
매번 이런 체크를 추가하는 게 번거로웠다.
해결 방안
전면 교체보다는 점진적 전환을 선택했다. 새로운 API 호출은 fetch를 사용하고, 기존 코드는 유지하는 방식이다.
공통 fetch 유틸리티를 만들어 프로젝트 전체에서 사용하도록 했다.
// lib/fetch.js
export const apiFetch = async (url, options = {}) => {
const config = {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
Authorization: `Bearer ${getToken()}`
}
};
const response = await fetch(url, config);
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.message || `HTTP ${response.status}`);
}
return response.json();
};
결과
새로 작성하는 서버리스 함수들은 fetch를 사용하고 있다. 번들 사이즈가 약 200KB 정도 줄었고, 별도 의존성 없이 표준 API만으로 작동하는 점이 만족스럽다.
기존 axios 코드는 당분간 유지할 예정이다. 급하게 교체할 이유가 없고, 안정성이 검증된 코드를 굳이 건드릴 필요는 없다고 판단했다.