현록

fetch와 axios 차이

프론트엔드에서 API를 호출할 때 fetchaxios를 자주 만난다.
둘 다 HTTP 요청을 보내고 응답을 받는 데 사용하지만, 같은 도구는 아니다.
fetch는 브라우저에 내장된 Web API이고, axios는 별도로 설치해서 사용하는 HTTP 클라이언트 라이브러리다.

초보자 입장에서는 “둘 중 무엇을 써야 하는지”가 먼저 궁금할 수 있다.
간단한 요청은 fetch만으로 충분한 경우가 많고, 공통 설정과 에러 처리와 timeout 같은 편의 기능이 필요하면 axios가 편하다.

fetch의 역할

fetch는 리소스를 가져오기 위한 브라우저 내장 API다.
MDN 문서 기준으로 Fetch API는 네트워크를 포함한 리소스 요청을 위한 인터페이스를 제공한다.
별도 패키지를 설치하지 않아도 브라우저와 Node.js의 최신 런타임에서 사용할 수 있다.

const response = await fetch('/api/users')
const data = await response.json()

fetch는 Promise를 반환한다.
응답을 받으면 Response 객체가 오고, 본문을 JSON으로 읽으려면 response.json()을 따로 호출해야 한다.

이 흐름은 조금 길어 보이지만 표준 API라는 장점이 있다.
브라우저 기본 기능만으로 HTTP 요청을 다룰 수 있고, 추가 의존성이 생기지 않는다.

axios의 역할

axios는 Promise 기반 HTTP 클라이언트 라이브러리다.
사용하려면 프로젝트에 패키지를 설치해야 한다.

npm install axios

설치 후에는 아래처럼 사용할 수 있다.

import axios from 'axios'

const response = await axios.get('/api/users')
const data = response.data

axios는 응답 본문을 response.data에 담아준다.
JSON 응답을 다룰 때 response.json()을 직접 호출하지 않아도 되는 점이 초보자에게는 더 직관적으로 느껴질 수 있다.

설치 여부

가장 먼저 다른 점은 설치 여부다.
fetch는 내장 API라서 설치가 필요 없다.

await fetch('/api/users')

axios는 외부 패키지라서 의존성에 추가해야 한다.

npm install axios
import axios from 'axios'

프로젝트에 의존성을 하나 더 추가하는 것이 부담이 아니라면 axios를 써도 된다.
반대로 단순한 요청 몇 개만 필요하다면 fetch로 시작하는 편이 가볍다.
의존성 위치가 헷갈린다면 dependencies와 devDependencies 차이를 같이 보면 좋다.

JSON 처리 차이

fetch는 응답을 받은 뒤 본문을 직접 읽어야 한다.

const response = await fetch('/api/users')
const data = await response.json()

여기서 response.json()도 비동기 작업이다.
응답 헤더를 받은 것과 본문을 JSON으로 파싱하는 것은 별도 단계라고 생각하면 된다.

axios는 JSON 응답을 data에 넣어준다.

const response = await axios.get('/api/users')
const data = response.data

그래서 같은 GET 요청이라면 axios 코드가 더 짧아 보이는 경우가 많다.
다만 짧다는 이유만으로 항상 axios를 고를 필요는 없다.
프로젝트가 이미 fetch 기반으로 통일되어 있다면 그 흐름을 유지하는 것도 좋은 선택이다.

HTTP 에러 처리 차이

가장 중요한 차이는 HTTP 에러 처리다.
fetch는 서버가 404500 같은 상태 코드를 반환해도 네트워크 요청 자체가 성공했다면 Promise를 reject하지 않는다.
MDN 문서 기준으로 fetch()는 서버가 HTTP 에러 상태로 응답해도 Response로 resolve될 수 있다.

그래서 fetch에서는 response.ok를 직접 확인하는 코드가 필요하다.
response.ok는 상태 코드가 200부터 299 사이인지 알려주는 boolean 값이다.

const response = await fetch('/api/users')

if (!response.ok) {
  throw new Error(`HTTP error: ${response.status}`)
}

const data = await response.json()

axios는 기본적으로 2xx 범위를 벗어난 응답을 에러로 처리한다.
axios 문서도 validateStatus 기본 조건을 통해 어떤 상태 코드를 reject할지 정할 수 있다고 설명한다.

try {
  const response = await axios.get('/api/users')
  console.log(response.data)
} catch (error) {
  console.error(error)
}

이 차이를 모르고 fetch를 쓰면 404 응답인데도 catch로 가지 않는 상황을 만날 수 있다.
API 호출 코드에서 에러 처리가 중요하다면 이 차이를 꼭 기억해야 한다.

timeout 처리

axios는 요청 config에 timeout을 넣을 수 있다.

const response = await axios.get('/api/users', {
  timeout: 5000,
})

axios 문서도 production에서는 timeout을 설정하라고 안내한다.
요청이 무한히 기다리는 상황을 줄일 수 있기 때문이다.

fetch에는 axios처럼 단순한 timeout 옵션이 바로 있는 것은 아니다.
대신 AbortController를 사용해 요청을 취소하는 방식으로 timeout을 구현한다.

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

try {
  const response = await fetch('/api/users', {
    signal: controller.signal,
  })

  const data = await response.json()
} finally {
  clearTimeout(timeoutId)
}

가능은 하지만 axios보다 코드가 길다.
요청 timeout을 여러 곳에서 반복해서 다뤄야 한다면 axios나 별도 fetch wrapper를 만드는 편이 편하다.

공통 설정과 interceptor

서비스가 커질수록 API 요청에는 공통 설정이 많아진다.
base URL, 인증 토큰, 공통 헤더, 에러 로깅, refresh token 처리 같은 것들이다.

axios는 instance와 interceptor를 제공해서 이런 공통 처리를 모으기 쉽다.

const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,
})

api.interceptors.request.use((config) => {
  config.headers.Authorization = `Bearer ${token}`
  return config
})

fetch도 wrapper 함수를 만들면 비슷한 구조를 만들 수 있다.

async function apiFetch(path: string, init?: RequestInit) {
  const response = await fetch(`https://api.example.com${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${token}`,
      ...init?.headers,
    },
  })

  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`)
  }

  return response.json()
}

작은 프로젝트라면 wrapper로 충분하다.
요청 전후 처리가 많고 팀에서 axios 패턴에 익숙하다면 axios가 더 편할 수 있다.

취소 처리

fetchAbortControllerAbortSignal을 사용해 요청을 취소한다.
브라우저 표준 API라서 fetch와 잘 맞는다.

const controller = new AbortController()

fetch('/api/users', {
  signal: controller.signal,
})

controller.abort()

axios도 AbortController 기반 취소를 지원한다.

const controller = new AbortController()

axios.get('/api/users', {
  signal: controller.signal,
})

controller.abort()

요즘 코드는 두 방식 모두 AbortController로 이해하면 된다.
과거 axios 예시에서 보이던 CancelToken은 새 코드에서 먼저 선택할 방식은 아니다.

선택 기준

단순한 API 호출이라면 fetch로 충분하다.
설치가 필요 없고, 표준 API라서 의존성이 늘어나지 않는다.
React, Next.js, 브라우저 기본 기능을 익히는 단계라면 fetch를 먼저 이해하는 것이 좋다.

요청 설정이 많고 에러 처리를 한 곳에 모으고 싶다면 axios가 편하다.
response.data, timeout, instance, interceptor 같은 기능은 실무 코드에서 자주 도움이 된다.

팀 프로젝트에서는 기술 자체보다 일관성이 더 중요하다.
이미 fetch wrapper가 잘 만들어져 있다면 굳이 axios를 추가하지 않아도 된다.
이미 axios instance와 interceptor 중심으로 구성되어 있다면 그 패턴을 따르는 편이 좋다.

Nuxt fetch와의 구분

예전 Nuxt 2에는 fetch라는 이름의 hook이 있었다.
이 글에서 말하는 fetch는 브라우저와 런타임에서 HTTP 요청을 보내는 Fetch API다.
이름은 같지만 역할이 다르다.

Nuxt 2의 fetch hook과 lifecycle 차이는 Fetch API와 created hook에서 API 호출 시 차이에 따로 정리해두었다.
새 글을 읽을 때는 두 개념을 섞지 않는 편이 좋다.

정리

fetch는 브라우저 내장 Web API다.
설치가 필요 없고 표준에 가깝지만, JSON 처리와 HTTP 에러 처리를 직접 챙겨야 한다.

axios는 별도 패키지로 사용하는 HTTP 클라이언트다.
response.data, 기본 에러 처리, timeout, instance, interceptor 같은 편의 기능이 있다.

작고 단순한 요청은 fetch로 충분하다.
공통 설정과 에러 처리가 많아지면 axios나 잘 설계된 fetch wrapper가 필요해진다.

참고 자료

관련 포스트
CORS 에러와 해결 기준 thumbnail
CORS 에러와 해결 기준
브라우저의 Same-Origin Policy, CORS 응답 헤더, preflight, 서버에서 해결해야 하는 이유를 초보자 기준으로 정리합니다.
Vue 3 입문기 thumbnail
Vue 3 입문기
Vue의 메인 버전이 3가 된지 꽤 됐다. 작성 당시에는 Nuxt 3가 RC 단계였기 때문에 실서비스에 바로 적용하기에는 조심스러웠다. 그래서 궁금하고 심심하던 참에 간단한 Todo App을 만들어봤다.
marked renderer custom 하기 thumbnail
marked renderer custom 하기
현록을 개발하면서 티스토리에 있던 포스트들을 markdown으로 변환하여 올리고 있는데, 간혹 이미지 가로 사이즈가 viewport width보다 크면 overflow되는 현상이 있었습니다. 사실 css로 해결하였지만, styled-components 환경에서 nesting하는 것을 꺼리기도 하고, marked에서 직접 inline style을 넣는 방법이 궁금하기도 해서 찾아보았습니다.
가독성 있게 상수 넣기 thumbnail
가독성 있게 상수 넣기
오늘 회사 동료의 PR 리뷰 과정에서 좋은 기능을 공유해주셔서 TIL로 남겨봅니다. 아래와 같이 상수에 언더바(_)로 콤마처럼 구분을 시켜줄 수 있습니다. 앞으로 깔끔하고 좋은 코드를 작성하기 위해 자주 사용해야겠습니다 :)
TS에서 generic optional 하게 설정하기 thumbnail
TS에서 generic optional 하게 설정하기
오늘 next에서 `getStaticProps`와 `getLayout` 패턴을 함께 사용할 때, typescript generic을 넘겨주는 작업을 하고 있었는데, 기본값이 없다보니 기존 코드에 에러가 발생했었다. 이를 해결하기 위해 찾아보니 단순히 아래 예시처럼 `= {}`을 추가해주면 해결된다고 한다.
Backend에서 API Response가 snake_case인 경우엔? thumbnail
Backend에서 API Response가 snake_case인 경우엔?
안녕하세요. 프론트엔드 개발자의 경우, 가끔 백엔드의 API Response 값이 snake_case일 경우 어떻게 관리할지에 대해 고민에 빠지게 됩니다. 저도 오늘 같은 상황을 겪게 되었는데,  이번엔 네이밍 컨벤션을 맞춰주기로 했습니다. 컨벤션을 맞추는 데에는 여러가지 방법이 있겠지만, 고민 끝에 저는 axios의 interceptors를 통해 해결을 해보았습니다.
Promise 다루기 (feat. 병렬실행, 순차실행) thumbnail
Promise 다루기 (feat. 병렬실행, 순차실행)
오늘은 Promise를 통해 구문을 동기 처리 할 때, 여러 Promise들을 다루는 법을 소개해보겠습니다. Javscript를 작성하다 보면, 가끔 여러 Promise들을 다룰 때가 있습니다. 필자도 Nodejs 서버에서 동시에 여러 쿼리를 실행할 때 자주 마주쳤었는데요. 오늘은 어떻게 하면 Promise들을 유연하게 다룰 수 있는지 알아보겠습니다. 시작하기 앞서, 네 가지 Promise를 선언하고, 그들을 하나의 Array에 묶어보겠습니다.