"Node의 전역 fetch는 브라우저와 익숙한 요청 API를 공유해. 다만 서버 환경의 쿠키, 프록시, 연결 관리까지 브라우저와 같다고 가정하면 안 돼."
Node에 들어온 Fetch API
Node 18에서 전역 fetch가 처음 제공됐고 Node 21에서 안정 기능이 됐어. 구현은 Node에 포함된 Undici를 사용해. 그래서 단순한 HTTP 요청이라면 별도 클라이언트 패키지 없이 Request, Response, Headers, FormData 같은 웹 표준 API를 쓸 수 있어.
이 말이 모든 HTTP 라이브러리가 불필요하다는 뜻은 아니야. 인증 갱신, 스키마 검증, 세밀한 재시도 정책, 쿠키 저장소처럼 프로젝트가 요구하는 기능이 있다면 전용 계층이 여전히 유용해. 먼저 내장 API가 필요한 범위를 충족하는지 확인하고 의존성을 고르면 돼.
응답 상태는 직접 확인해
const res = await fetch('https://api.github.com/zen');
if (!res.ok) {
throw new Error(`${res.status} ${res.statusText}`);
}
console.log(await res.text());
const created = await fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Pippa' }),
});
if (!created.ok) throw new Error(`HTTP ${created.status}`);
fetch는 서버가 404나 500을 반환해도 정상적인 HTTP 응답으로 처리해. 이때 프로미스는 이행되고 res.ok가 false가 돼. DNS 실패나 연결 실패처럼 응답 자체를 받지 못한 경우에는 프로미스가 거부돼. 따라서 호출부는 전송 실패와 HTTP 상태 실패를 따로 다뤄야 해.
서버 환경에서 달라지는 점
- CORS: 브라우저의 동일 출처 정책이 적용되지 않아. 대신 호출할 주소를 애플리케이션이 직접 검증해야 해.
- 쿠키: 브라우저 같은 쿠키 저장소가 자동으로 붙지 않아. 상태이 필요한 요청이라면 별도로 관리해야 해.
- 본문 스트림:
res.body는 WebReadableStream이어서for await이나getReader()로 점진적으로 읽을 수 있어. - 연결과 프록시: 연결 재사용과 전송 세부 사항은 포함된 Undici 구현이 맡아. 특별한 프록시나 풀 정책이 필요하면 현재 Node와 Undici 문서에 맞춰 명시적으로 구성해.
본문을 전부 버퍼링하지 않기
res.json(), res.text(), res.arrayBuffer()는 편리하지만 본문 전체를 모은 뒤 결과를 돌려줘. 큰 파일이나 JSON Lines 응답은 res.body를 스트리밍하면 메모리 사용량을 일정하게 유지할 수 있어. 청크 경계가 줄 경계와 일치하지 않으므로 남은 문자열을 다음 청크와 이어 붙여야 해.
응답 본문을 사용하지 않을 때도 무시한 채 남겨 두지 마. 필요 없다면 취소하고, 필요하다면 끝까지 소비해야 연결 자원을 예측 가능하게 관리할 수 있어.
취소와 시간 제한
fetch는 AbortSignal을 받아. 독립된 시간 제한에는 AbortSignal.timeout(ms)를 쓰고, 사용자 취소나 상위 작업 취소와 함께 묶어야 한다면 컨트롤러나 결합 신호를 사용해. 취소는 요청과 본문 소비에 전달되며, 구체적인 소켓 재사용과 정리는 런타임 구현이 맡아.
try {
const res = await fetch('https://slow.example/data', {
signal: AbortSignal.timeout(3_000),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
console.log(await res.json());
} catch (error) {
if (error.name === 'TimeoutError') {
console.error('request timed out');
} else {
throw error;
}
}
재시도는 요청의 성격부터 확인해
네트워크 오류나 5xx 응답을 재시도하면 일시적인 장애를 견딜 수 있지만, 모든 요청을 같은 방식으로 다시 보내면 안 돼. GET처럼 멱등인 요청은 비교적 안전하지만 결제 생성 같은 POST는 서버가 이미 처리했을 수 있어. 재시도 가능 상태, 최대 횟수, 지수 백오프, 지터, Retry-After, 멱등성 키를 API 계약에 맞춰 정해야 해.
Pippa의 고백
fetch로 충분해. 프로젝트가 쿠키 저장소나 복잡한 재시도 정책을 실제로 요구할 때만 그 기능을 소유한 계층을 더해. 의존성을 줄이는 것보다 중요한 건 경계를 알고 선택하는 거야.