본문 바로가기

axios 라이브러리 소개

Axios로 GET·POST·수정·삭제 요청을 보내고 공통 설정 인스턴스를 만들어 Fetch API와 사용 방식을 비교합니다.

이 글에서는 프론트엔드 개발에서 널리 쓰이는 HTTP 클라이언트 라이브러리인 Axios를 알아봅니다. fetch는 브라우저에 내장된 표준 API이고 AbortController를 포함해 요청에 필요한 기본 기능을 갖추고 있지만, HTTP 오류의 판정과 JSON 변환은 호출하는 코드에서 직접 처리해야 합니다. Axios는 인스턴스의 기본값과 요청·응답 인터셉터(interceptor), timeout, 응답 변환을 한 클라이언트 계층에 모으고 싶을 때 유용합니다. 어느 쪽이 알맞은지는 프로젝트의 요청 정책이 얼마나 복잡한지와 번들 비용에 따라 정합니다.

실습 예제는 외부 공개 API 대신 앞 글들과 같은 로컬 목 API(json-server, http://localhost:4000)를 기준으로 합니다. 4000번은 목 API, 4100번은 별도의 백엔드나 실시간 서버로 포트를 나누어 두면 실습 환경끼리 부딪히는 일을 줄일 수 있습니다.

Axios 요청 파이프라인과 React UI 커밋 경계 — 상태 추적

실제 요청 config 병합/선택 interceptor/HTTP/응답과 최신 UI commit에서 전달 값이 달라진다. strict JSON과 shape 검사는 별도이며 source 전체가 interceptor를 구현한다는 주장을 피한다.

호출 의도에서 Axios 설정 병합, 선택적 요청 인터셉터, 어댑터와 HTTP, 응답 인터셉터를 거쳐 React UI 상태에 결과를 커밋하는 요청 파이프라인

Intent → transport → UI commit

Axios 설정에서 전송·응답·UI 커밋까지

Axios 인스턴스는 호출 의도와 전송 정책 사이의 경계입니다. 설정의 출처와 인터셉터 반환 계약을 분명히 하고, 네트워크 취소와 오래된 UI 커밋 방지를 함께 설계합니다.

Axios 요청과 결과의 전달 경계호출 의도에서 library defaults보다 instance defaults, request config가 우선하는 설정을 만들고 선택적인 요청 인터셉터가 config를 반환합니다. adapter가 HTTP를 전송하고 응답 인터셉터는 response 또는 reject를 반환합니다. 마지막 UI는 현재 요청인지 확인한 결과만 상태에 씁니다. 인터셉터를 등록하지 않았다면 해당 보강 단계는 생략되며 이 장의 모든 코드가 인터셉터를 구현했다는 주장은 아닙니다.호출 의도method · URLparams · data설정 병합defaults < instance< request요청 인터셉터선택적 보강config 반환adapter · HTTPsignal 연결응답 인터셉터response 반환또는 reject현재 요청만 UIdata · errorloading설정·인터셉터·전송·UI는 서로 다른 책임화살표: 요청과 결과를 넘기는 계약
Axios 요청과 결과의 전달 경계호출 의도에서 library defaults보다 instance defaults, request config가 우선하는 설정을 만들고 선택적인 요청 인터셉터가 config를 반환합니다. adapter가 HTTP를 전송하고 응답 인터셉터는 response 또는 reject를 반환합니다. 마지막 UI는 현재 요청인지 확인한 결과만 상태에 씁니다. 인터셉터를 등록하지 않았다면 해당 보강 단계는 생략되며 이 장의 모든 코드가 인터셉터를 구현했다는 주장은 아닙니다.호출 의도method · URLparams · data설정 병합defaults < instance< request요청 인터셉터선택적 보강config 반환adapter · HTTPsignal 연결응답 인터셉터response 반환또는 reject현재 요청만 UIdata · errorloading화살표: 요청·결과 전달
  1. API 함수가 호출 의도를 표현

    getUser(id, { signal })처럼 method·URL·params/data와 취소 신호를 전달합니다. 컴포넌트는 전송 구현보다 필요한 작업을 말합니다.

  2. 설정을 출처별 우선순위로 병합

    library defaults < instance.defaults < request config. 뒤의 요청별 값이 앞의 공통값을 덮어쓰므로 예외 정책을 호출 지점에서 명시할 수 있습니다.

  3. 선택한 요청 인터셉터가 config를 보강

    인증·추적처럼 여러 요청에 공통인 관심사만 다룹니다. fulfilled handler는 config를 반환하고, 실패를 계속 전달하려면 reject를 반환합니다.

  4. 어댑터가 실제 HTTP 전송을 수행

    브라우저·Node 환경에 맞는 adapter가 최종 config를 전송합니다. AbortSignal은 이 전송에 연결되어 대기 중인 요청을 실제로 중단합니다.

  5. 응답 인터셉터가 계약을 명시적으로 이어감

    성공 handler는 원래 response 또는 의도적으로 정규화한 값을 반환합니다. 오류 handler는 복구하지 않는 한 Promise.reject(error)로 거절 상태를 보존합니다.

  6. 호출자가 UI 생명주기에 맞춰 커밋

    현재 요청만 성공·오류·loading 상태를 갱신합니다. cleanup 시 transport를 취소하고, active flag나 최신 request ID로 늦은 catch·finally의 stale commit도 막습니다.

전송 취소

controller.abort()

Axios 0.22.0부터 지원하는 signal을 통해 진행 중인 요청을 중단합니다. 취소 오류는 실패 안내와 분리합니다.

상태 커밋 게이트

active 또는 최신 요청 ID

취소가 늦게 관찰되거나 다른 계층이 이미 완료한 Promise라도 이전 요청이 새 화면 상태를 덮어쓰지 못하게 합니다.

인스턴스에는 서버별 base URL과 timeout 같은 안정적인 기본값을 두고, method·params/data·signal처럼 호출마다 달라지는 값은 요청 config에서 명시합니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
원도표의 전체 관계·조건·설명
구분내용
원도표 설명getUser(id, { signal })처럼 method·URL·params/data와 취소 신호를 전달합니다. 컴포넌트는 전송 구현보다 필요한 작업을 말합니다.
원도표 조건·주석API 함수가 호출 의도를 표현 getUser(id, { signal })처럼 method·URL·params/data와 취소 신호를 전달합니다. 컴포넌트는 전송 구현보다 필요한 작업을 말합니다.
원도표 설명library defaults < instance.defaults < request config. 뒤의 요청별 값이 앞의 공통값을 덮어쓰므로 예외 정책을 호출 지점에서 명시할 수 있습니다.
원도표 조건·주석설정을 출처별 우선순위로 병합 library defaults < instance.defaults < request config. 뒤의 요청별 값이 앞의 공통값을 덮어쓰므로 예외 정책을 호출 지점에서 명시할 수 있습니다.
원도표 설명브라우저·Node 환경에 맞는 adapter가 최종 config를 전송합니다. AbortSignal은 이 전송에 연결되어 대기 중인 요청을 실제로 중단합니다.
원도표 조건·주석어댑터가 실제 HTTP 전송을 수행 브라우저·Node 환경에 맞는 adapter가 최종 config를 전송합니다. AbortSignal은 이 전송에 연결되어 대기 중인 요청을 실제로 중단합니다.
원도표 설명성공 handler는 원래 response 또는 의도적으로 정규화한 값을 반환합니다. 오류 handler는 복구하지 않는 한 Promise.reject(error)로 거절 상태를 보존합니다.
원도표 조건·주석응답 인터셉터가 계약을 명시적으로 이어감 성공 handler는 원래 response 또는 의도적으로 정규화한 값을 반환합니다. 오류 handler는 복구하지 않는 한 Promise.reject(error)로 거절 상태를 보존합니다.
원도표 설명Axios 0.22.0부터 지원하는 signal을 통해 진행 중인 요청을 중단합니다. 취소 오류는 실패 안내와 분리합니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
요청 configconfig: 병합 결과 / interceptor: 등록 시 선택적 보강library < instance < request 우선으로 method·URL·params·data를 준비한다구성의 병합과 UI의 state는 다르다
transport와 응답response: 값 또는 reject / 검증: JSON/shape 별도adapter가 HTTP를 전송하고 설정된 응답 처리를 거친다모든 예제가 interceptor를 구현한다는 전제는 없다
현재 UI 반영gate: 최신성 / UI: 현재 결과현재 요청의 검증된 결과만 data·error·loading에 쓴다Axios의 response.data 자체가 게시글 필드의 검증은 아니다

Axios 요청 파이프라인과 React UI 커밋 경계 — 상태 추적

실제 요청 config 병합/선택 interceptor/HTTP/응답과 최신 UI commit에서 전달 값이 달라진다. strict JSON과 shape 검사는 별도이며 source 전체가 interceptor를 구현한다는 주장을 피한다.

호출 의도에서 Axios 설정 병합, 선택적 요청 인터셉터, 어댑터와 HTTP, 응답 인터셉터를 거쳐 React UI 상태에 결과를 커밋하는 요청 파이프라인

Intent → transport → UI commit

Axios 설정에서 전송·응답·UI 커밋까지

Axios 인스턴스는 호출 의도와 전송 정책 사이의 경계입니다. 설정의 출처와 인터셉터 반환 계약을 분명히 하고, 네트워크 취소와 오래된 UI 커밋 방지를 함께 설계합니다.

Axios 요청과 결과의 전달 경계호출 의도에서 library defaults보다 instance defaults, request config가 우선하는 설정을 만들고 선택적인 요청 인터셉터가 config를 반환합니다. adapter가 HTTP를 전송하고 응답 인터셉터는 response 또는 reject를 반환합니다. 마지막 UI는 현재 요청인지 확인한 결과만 상태에 씁니다. 인터셉터를 등록하지 않았다면 해당 보강 단계는 생략되며 이 장의 모든 코드가 인터셉터를 구현했다는 주장은 아닙니다.호출 의도method · URLparams · data설정 병합defaults < instance< request요청 인터셉터선택적 보강config 반환adapter · HTTPsignal 연결응답 인터셉터response 반환또는 reject현재 요청만 UIdata · errorloading설정·인터셉터·전송·UI는 서로 다른 책임화살표: 요청과 결과를 넘기는 계약
Axios 요청과 결과의 전달 경계호출 의도에서 library defaults보다 instance defaults, request config가 우선하는 설정을 만들고 선택적인 요청 인터셉터가 config를 반환합니다. adapter가 HTTP를 전송하고 응답 인터셉터는 response 또는 reject를 반환합니다. 마지막 UI는 현재 요청인지 확인한 결과만 상태에 씁니다. 인터셉터를 등록하지 않았다면 해당 보강 단계는 생략되며 이 장의 모든 코드가 인터셉터를 구현했다는 주장은 아닙니다.호출 의도method · URLparams · data설정 병합defaults < instance< request요청 인터셉터선택적 보강config 반환adapter · HTTPsignal 연결응답 인터셉터response 반환또는 reject현재 요청만 UIdata · errorloading화살표: 요청·결과 전달
  1. API 함수가 호출 의도를 표현

    getUser(id, { signal })처럼 method·URL·params/data와 취소 신호를 전달합니다. 컴포넌트는 전송 구현보다 필요한 작업을 말합니다.

  2. 설정을 출처별 우선순위로 병합

    library defaults < instance.defaults < request config. 뒤의 요청별 값이 앞의 공통값을 덮어쓰므로 예외 정책을 호출 지점에서 명시할 수 있습니다.

  3. 선택한 요청 인터셉터가 config를 보강

    인증·추적처럼 여러 요청에 공통인 관심사만 다룹니다. fulfilled handler는 config를 반환하고, 실패를 계속 전달하려면 reject를 반환합니다.

  4. 어댑터가 실제 HTTP 전송을 수행

    브라우저·Node 환경에 맞는 adapter가 최종 config를 전송합니다. AbortSignal은 이 전송에 연결되어 대기 중인 요청을 실제로 중단합니다.

  5. 응답 인터셉터가 계약을 명시적으로 이어감

    성공 handler는 원래 response 또는 의도적으로 정규화한 값을 반환합니다. 오류 handler는 복구하지 않는 한 Promise.reject(error)로 거절 상태를 보존합니다.

  6. 호출자가 UI 생명주기에 맞춰 커밋

    현재 요청만 성공·오류·loading 상태를 갱신합니다. cleanup 시 transport를 취소하고, active flag나 최신 request ID로 늦은 catch·finally의 stale commit도 막습니다.

전송 취소

controller.abort()

Axios 0.22.0부터 지원하는 signal을 통해 진행 중인 요청을 중단합니다. 취소 오류는 실패 안내와 분리합니다.

상태 커밋 게이트

active 또는 최신 요청 ID

취소가 늦게 관찰되거나 다른 계층이 이미 완료한 Promise라도 이전 요청이 새 화면 상태를 덮어쓰지 못하게 합니다.

인스턴스에는 서버별 base URL과 timeout 같은 안정적인 기본값을 두고, method·params/data·signal처럼 호출마다 달라지는 값은 요청 config에서 명시합니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.

관계도의 보충 설명

원도표의 전체 관계·조건·설명
구분내용
원도표 설명getUser(id, { signal })처럼 method·URL·params/data와 취소 신호를 전달합니다. 컴포넌트는 전송 구현보다 필요한 작업을 말합니다.
원도표 조건·주석API 함수가 호출 의도를 표현 getUser(id, { signal })처럼 method·URL·params/data와 취소 신호를 전달합니다. 컴포넌트는 전송 구현보다 필요한 작업을 말합니다.
원도표 설명library defaults < instance.defaults < request config. 뒤의 요청별 값이 앞의 공통값을 덮어쓰므로 예외 정책을 호출 지점에서 명시할 수 있습니다.
원도표 조건·주석설정을 출처별 우선순위로 병합 library defaults < instance.defaults < request config. 뒤의 요청별 값이 앞의 공통값을 덮어쓰므로 예외 정책을 호출 지점에서 명시할 수 있습니다.
원도표 설명브라우저·Node 환경에 맞는 adapter가 최종 config를 전송합니다. AbortSignal은 이 전송에 연결되어 대기 중인 요청을 실제로 중단합니다.
원도표 조건·주석어댑터가 실제 HTTP 전송을 수행 브라우저·Node 환경에 맞는 adapter가 최종 config를 전송합니다. AbortSignal은 이 전송에 연결되어 대기 중인 요청을 실제로 중단합니다.
원도표 설명성공 handler는 원래 response 또는 의도적으로 정규화한 값을 반환합니다. 오류 handler는 복구하지 않는 한 Promise.reject(error)로 거절 상태를 보존합니다.
원도표 조건·주석응답 인터셉터가 계약을 명시적으로 이어감 성공 handler는 원래 response 또는 의도적으로 정규화한 값을 반환합니다. 오류 handler는 복구하지 않는 한 Promise.reject(error)로 거절 상태를 보존합니다.
원도표 설명Axios 0.22.0부터 지원하는 signal을 통해 진행 중인 요청을 중단합니다. 취소 오류는 실패 안내와 분리합니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
요청 configconfig: 병합 결과 / interceptor: 등록 시 선택적 보강library < instance < request 우선으로 method·URL·params·data를 준비한다구성의 병합과 UI의 state는 다르다
transport와 응답response: 값 또는 reject / 검증: JSON/shape 별도adapter가 HTTP를 전송하고 설정된 응답 처리를 거친다모든 예제가 interceptor를 구현한다는 전제는 없다
현재 UI 반영gate: 최신성 / UI: 현재 결과현재 요청의 검증된 결과만 data·error·loading에 쓴다Axios의 response.data 자체가 게시글 필드의 검증은 아니다

위 그림은 호출 의도에서 시작해 Axios의 설정 병합, 실제 전송, 응답 처리를 거쳐 React의 상태에 결과를 쓰기까지의 요청 파이프라인을 보여 줍니다. 호출 의도는 method와 URL, params나 data로 표현하고, 설정은 라이브러리의 기본값보다 인스턴스의 기본값이, 그보다 요청마다 넘긴 config가 우선하도록 병합됩니다. 등록한 요청 인터셉터가 있다면 config를 보강해 반환하고, 어댑터(adapter)가 실제 HTTP 전송을 하며, 응답 인터셉터는 response를 반환하거나 거절을 이어 갑니다. 마지막으로 UI는 현재 요청의 결과인지 확인한 뒤에만 data와 error, loading을 씁니다. 그림의 캡션이 밝히듯 인터셉터를 등록하지 않았다면 그 보강 단계는 생략되며, 이 글의 모든 코드가 인터셉터를 쓰는 것은 아닙니다.

그림 아래의 단계 설명은 각 칸이 하는 일을 이어서 풉니다. 컴포넌트는 getUser(id, { signal })처럼 전송의 구현보다 필요한 작업을 말하고, 뒤에 오는 요청별 값이 앞의 공통값을 덮어쓰므로 예외적인 정책은 호출하는 지점에서 드러냅니다. 요청 인터셉터는 인증이나 추적처럼 여러 요청에 공통인 일만 다루며, 응답 인터셉터의 오류 처리 함수는 복구할 생각이 없다면 Promise.reject(error)로 거절 상태를 그대로 넘깁니다. 전송 취소는 Axios 0.22.0부터 지원하는 signal로 진행 중인 요청을 멈추고, 상태를 쓰기 전의 확인은 active 플래그나 최신 요청 ID로 늦게 끝난 이전 요청이 새 화면을 덮어쓰지 못하게 합니다. 그림의 마지막 설명처럼 인스턴스에는 서버별 base URL과 timeout 같은 안정적인 기본값을 두고, method와 params, data, signal처럼 호출마다 달라지는 값은 요청 config에 적습니다.


Axios란 무엇인가?

Axios는 브라우저와 Node.js에서 모두 쓸 수 있는 Promise 기반의 HTTP 클라이언트 라이브러리로, 서버와의 통신(API 호출)을 더 쉽게 작성하도록 돕습니다. Promise를 돌려주므로 async/await와 함께 비동기 코드를 간결하게 쓸 수 있고, 응답 본문을 기본 설정에 따라 변환해 response.data로 주므로 response.json()을 따로 부르지 않아도 됩니다.

오류 처리 방식도 다릅니다. Axios는 기본 validateStatus에서 2xx 밖의 HTTP 응답을 거부(reject)하며, 이 기준을 바꾸면 같은 상태 코드가 이행(fulfilled) 경로로 갈 수도 있습니다. 반면 기본 fetch는 HTTP 상태와 관계없이 응답 Promise를 이행하므로 response.ok 같은 값을 직접 확인해야 합니다.

그 밖에 어댑터가 요청을 보내기 전과 응답이나 오류가 돌아온 뒤에 공통 로직을 적용하는 요청·응답 인터셉터, 정해진 시간 안에 응답이 없으면 요청을 멈추는 timeout, 파일을 올릴 때의 진행률 추적 기능이 있습니다. 요청 취소는 Axios 0.22.0부터 표준 AbortController의 signal을 지원하며, 예전의 CancelToken은 사용 중단(deprecated)된 API이므로 새 코드에서는 쓰지 않습니다(Axios 문서의 취소).

더 깊이: 브라우저에서 Axios가 요청을 보내는 어댑터

Axios는 요청을 직접 보내지 않고, 실행 환경에 맞는 어댑터(adapter)에 맡깁니다. Axios 저장소의 README에 따르면 기본 어댑터 목록은 ['xhr', 'http', 'fetch']이고 그 가운데 환경에서 쓸 수 있는 첫 번째를 고르므로, 브라우저에서는 fetch가 아니라 XMLHttpRequest를 쓰는 xhr 어댑터가 요청을 보냅니다. 1.7.0에서 들어온 fetch 어댑터는 xhr과 http를 쓸 수 없는 환경에서만 기본으로 쓰이며, 원한다면 adapter: 'fetch'로 직접 고를 수 있습니다.

어댑터와 함께 알아 둘 기본값은 timeout이 0, 곧 시간 제한이 없다는 점입니다. README는 프로덕션에서는 timeout을 정해 두라고 권하며, 그러지 않으면 멈춘 요청이 끝없이 기다릴 수 있다고 경고합니다. 뒤의 인스턴스 예제처럼 timeout을 명시해 두더라도, 이 시간 제한은 사용자가 화면을 떠났을 때 요청을 멈추는 일과는 별개입니다. 그래서 컴포넌트의 정리에는 여전히 AbortController의 signal을 씁니다.


Axios 설치

Axios를 쓰려면 먼저 프로젝트에 설치합니다. fetch와 달리 Axios는 브라우저에 내장된 기능이 아니라 외부 패키지이므로, 앱 번들에 함께 들어가는 일반 의존성으로 설치합니다.

npm install axios
# 또는
yarn add axios

설치가 끝나면 package.json의 dependencies에 axios가 추가되고, 실제로 설치된 버전은 lockfile에 기록됩니다.


Axios의 기본 사용법

설치한 뒤에는 다음과 같이 Axios를 가져와 씁니다. 이 import 문은 Axios로 요청을 보내는 파일마다 맨 위에 둡니다.

import axios from 'axios';

기본 내보내기인 axios는 바로 요청을 보낼 수 있는 기본 인스턴스이며, get, post, put, patch, delete처럼 HTTP 메서드 이름을 딴 메서드를 가집니다. 아래 절들은 이 메서드를 하나씩 봅니다.

GET 요청

데이터를 조회할 때 씁니다. axios.get(주소)는 응답을 기다리는 Promise를 돌려주므로, 아래처럼 then에서 응답을, catch에서 오류를 받습니다. 두 요청은 서로 기다리지 않고 거의 동시에 출발하므로, 콘솔에 찍히는 순서는 응답이 도착한 순서를 따릅니다.

// 모든 게시물 가져오기
axios.get('http://localhost:4000/posts')
  .then(response => {
    console.log('게시물 목록:', response.data);
  })
  .catch(error => {
    console.error('게시물 가져오기 오류:', error);
  });

// 특정 ID의 게시물 가져오기
axios.get('http://localhost:4000/posts/1')
  .then(response => {
    console.log('단일 게시물:', response.data);
  })
  .catch(error => {
    console.error('단일 게시물 가져오기 오류:', error);
  });

useEffect와 데이터 요청에서 만든 db.json이라면 첫 요청의 response.data는 게시물 하나가 든 배열이고, 둘째 요청은 { id: "1", userId: 1, title: "첫 게시물", body: "요청 흐름 연습" } 객체입니다. Axios가 JSON 응답을 변환해 주므로 바로 객체로 읽을 수 있습니다.

JSON Server v1의 페이지네이션

예전 판의 json-server에서는 ?_limit=10으로 개수를 제한했지만, 현행 JSON Server v1의 저장소 문서는 페이지네이션에 _limit 대신 _page와 _per_page를 쓰라고 안내합니다(JSON Server 저장소). 이때 페이지 응답은 게시물 배열 자체가 아니라, first, prev, next, last, pages, items 같은 페이지 정보와 현재 페이지의 레코드를 담은 data 배열로 이루어진 객체입니다.

JSON Server v1 페이지 응답 읽기
axios.get('http://localhost:4000/posts', {
  params: { _page: 1, _per_page: 10 },
}).then(response => {
  console.log('게시물 목록:', response.data.data);
});

뒤의 완성 실습은 페이지네이션을 넣지 않은 /posts 배열을 씁니다. 페이지 응답 객체를 배열을 기대하는 PostsPage에 그대로 넘기면 배열이 아니라는 오류가 나므로, 페이지 단위의 UI를 더할 때는 배열을 꺼내는 코드와 페이지 정보를 함께 설계합니다.

POST 요청

새 데이터를 만들 때 쓰며, 두 번째 인자로 보낼 데이터를 객체로 넘깁니다. Axios는 넘긴 일반 객체를 JSON 문자열로 바꾸고 JSON 형식이라는 Content-Type 헤더를 붙여 보내므로, fetch처럼 JSON.stringify를 직접 부르지 않아도 됩니다.

axios.post('http://localhost:4000/posts', {
  title: '새로운 게시물',
  body: '이것은 Axios로 작성된 새 게시물입니다.',
  userId: 1,
})
.then(response => {
  console.log('게시물 생성 성공:', response.data);
})
.catch(error => {
  console.error('게시물 생성 오류:', error);
});

json-server는 받은 게시물을 db.json에 저장하고, 저장한 게시물을 응답으로 돌려줍니다. 이 글의 쓰기 요청은 실제로 파일을 바꾸므로, 다시 시험하기 전에는 useEffect와 데이터 요청에서 복사해 둔 db.seed.json으로 db.json을 되돌립니다.

PUT/PATCH 요청

기존 데이터를 수정할 때 씁니다. PUT은 일반적으로 대상 리소스를 요청의 표현으로 통째로 바꾸는 의미이고, PATCH는 리소스의 일부를 바꾸는 의미입니다. 어떤 필드가 꼭 필요한지와 실제 동작, 지원하는 patch 문서의 형식은 서버 API의 명세를 따릅니다. 같은 요청을 여러 번 보내도 서버에 남는 효과가 한 번 보낸 것과 같은 성질을 멱등성(idempotence)이라 하며, PUT과 DELETE가 멱등한 메서드인 이유와 재시도와의 관계는 네트워크 교재의 HTTP 기본 구조에서 다룹니다.

// PUT 요청 (전체 업데이트)
axios.put('http://localhost:4000/posts/1', {
  title: '업데이트된 제목',
  body: '내용도 완전히 바뀌었습니다.',
  userId: 1,
})
.then(response => {
  console.log('게시물 PUT 업데이트 성공:', response.data);
})
.catch(error => {
  console.error('게시물 PUT 업데이트 오류:', error);
});

// PATCH 요청 (부분 업데이트)
axios.patch('http://localhost:4000/posts/1', {
  title: '부분적으로 업데이트된 제목',
})
.then(response => {
  console.log('게시물 PATCH 업데이트 성공:', response.data);
})
.catch(error => {
  console.error('게시물 PATCH 업데이트 오류:', error);
});

이 예제의 PUT은 title, body, userId를 모두 보내고, PATCH는 바꿀 title 하나만 보낸다는 점이 다릅니다. 서버가 PUT을 통째 교체로 처리한다면 보내지 않은 필드는 남지 않으므로, PUT에는 남겨야 할 필드를 모두 담습니다.

DELETE 요청

데이터를 지울 때 씁니다. 삭제 요청에는 보낼 본문이 없으므로 주소만 넘기고, 성공했는지는 응답의 status로 확인합니다.

axios.delete('http://localhost:4000/posts/1')
  .then(response => {
    console.log('게시물 삭제 성공:', response.status); // 200 OK, 204 No Content 등
  })
  .catch(error => {
    console.error('게시물 삭제 오류:', error);
  });

삭제한 뒤에 /posts/1을 다시 조회하면 그 게시물이 없으므로 404 응답이 오고, Axios의 기본 설정에서는 이 응답이 catch로 갑니다.

async/await와 함께 사용

Axios는 Promise를 돌려주므로 async/await 문법과 함께 쓰면 비동기 코드를 동기 코드처럼 깔끔하게 쓸 수 있습니다. 다음 컴포넌트는 JSX가 들어 있으므로 .jsx로 저장합니다.

src/components/AxiosExample.jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';

function AxiosExample() {
  const [post, setPost] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    let active = true;

    const fetchPost = async () => {
      try {
        setLoading(true);
        setError(null);
        const response = await axios.get('http://localhost:4000/posts/1', {
          signal: controller.signal,
        });
        if (active) setPost(response.data);
      } catch (err) {
        if (!active) return;
        if (axios.isAxiosError(err) && err.code === 'ERR_CANCELED') return;

        if (!axios.isAxiosError(err)) {
          setError(new Error('예상하지 못한 오류가 발생했습니다.'));
        } else if (err.response) {
          setError(new Error(`서버가 ${err.response.status} 상태로 응답했습니다.`));
        } else if (err.request) {
          const message = ['ECONNABORTED', 'ETIMEDOUT'].includes(err.code)
            ? '요청 시간이 초과되었습니다.'
            : '서버 응답을 받지 못했습니다.';
          setError(new Error(message));
        } else {
          setError(new Error(`요청을 구성하지 못했습니다: ${err.message}`));
        }
      } finally {
        if (active) setLoading(false);
      }
    };

    fetchPost();

    return () => {
      active = false;
      controller.abort();
    };
  }, []);

  if (loading) {
    return <div style={{ textAlign: 'center', padding: '20px' }}>Axios로 게시물을 불러오는 중...</div>;
  }

  if (error) {
    return <div style={{ textAlign: 'center', padding: '20px', color: 'red' }}>Axios 오류: {error.message}</div>;
  }

  return (
    <div style={{ maxWidth: '600px', margin: '20px auto', padding: '25px', border: '1px solid #ddd', borderRadius: '8px', boxShadow: '0 2px 5px rgba(0,0,0,0.05)', backgroundColor: '#fdfdfd' }}>
      <h2 style={{ textAlign: 'center', color: '#2c3e50', marginBottom: '20px' }}>Axios로 가져온 게시물</h2>
      <h3 style={{ color: '#3498db', marginBottom: '10px' }}>{post.title}</h3>
      <p>{post.body}</p>
    </div>
  );
}

export default AxiosExample;

App.jsx에 AxiosExample을 넣어 시험해 보세요. 목 API가 떠 있다면 로딩 문구 뒤에 “첫 게시물” 제목과 본문이 보이고, 서버를 끈 채로 열면 응답을 받지 못했으므로 “서버 응답을 받지 못했습니다.”가 나옵니다. Axios의 catch 블록에서는 오류 객체의 어느 필드가 채워졌는지에 따라 사용자에게 보일 메시지와 복구 방식을 다르게 잡아야 합니다.

Axios 오류인지 확인한 뒤 취소, 서버 응답 오류, 응답 없는 요청과 timeout, 요청 설정 오류, 예상 밖 오류를 순서대로 분류하는 기준

Error evidence · recovery choice

Axios 오류 증거에 따른 분류와 복구

오류 메시지보다 먼저 Axios가 남긴 증거를 봅니다. 취소인지, 서버 응답이 있는지, 요청은 만들어졌지만 응답이 없는지에 따라 사용자 안내와 재시도 가능성이 달라집니다.

  1. axios.isAxiosError(error)로 경계를 확인

    false라면 요청 구성이나 응답 처리 코드 등에서 생긴 예상 밖 오류일 수 있습니다. Axios 전송 오류처럼 필드를 가정하지 말고 별도 오류 경로로 보냅니다.

  2. error.code === 'ERR_CANCELED'

    화면 이탈·요청 교체 같은 의도적 취소를 먼저 분리합니다. 보통 오류 배너나 자동 재시도의 대상이 아니며 stale state도 커밋하지 않습니다.

  3. error.response가 있으면 서버가 응답

    status, data, headers를 바탕으로 인증·검증·서버 실패를 구분합니다. 상태 코드만으로 사용자 메시지를 단정하지 말고 API 오류 계약을 읽습니다.

  4. error.request만 있으면 응답을 받지 못한 요청

    네트워크·CORS·연결 실패를 조사합니다. timeout은 보통 ECONNABORTED, 설정에 따라 ETIMEDOUT 코드가 될 수 있으므로 code도 함께 확인합니다.

  5. Axios 오류지만 response와 request가 없으면 설정 단계

    잘못된 config처럼 전송 전에 Axios가 만든 오류일 수 있습니다. interceptor가 일반 Error를 던졌다면 첫 분기의 예상 밖 오류 경로로 보내며 이 분기와 섞지 않습니다.

상태 판정

validateStatus가 resolve와 reject를 가른다

기본 설정은 2xx를 성공으로 봅니다. 기준을 바꾸면 같은 HTTP 상태가 fulfilled 응답 인터셉터로 갈 수 있으므로 호출자 오류 정책과 함께 관리합니다.

재시도 판단

분류는 자동 재시도 허가가 아니다

멱등성, 사용자 의도, 서버 힌트, 요청 body 재전송 가능성을 확인합니다. 특히 취소·검증 실패·설정 오류는 무조건 재시도하지 않습니다.

응답 인터셉터는 adapter가 상태를 판정한 뒤 실행됩니다. 오류 handler가 값을 반환하면 체인은 복구된 fulfilled 상태가 되므로, 복구 의도가 없으면 reject를 그대로 반환합니다.

위 그림은 Axios의 오류 객체를 어떤 순서로 판별하고 어떤 복구를 고를지 보여 줍니다. 오류 메시지보다 먼저 Axios가 남긴 단서를 봐야 하는데, 먼저 axios.isAxiosError(error)가 false라면 요청을 구성하거나 응답을 처리하는 코드에서 생긴 예상 밖의 오류일 수 있으므로 Axios 전송 오류의 필드를 가정하지 않고 다른 경로로 보냅니다. 다음으로 error.code === 'ERR_CANCELED'인 의도적인 취소를 떼어 내며, 이것은 보통 오류 배너나 자동 재시도의 대상이 아니고 오래된 상태도 쓰지 않습니다.

error.response가 있다면 서버가 응답한 것이므로 status와 data, headers로 인증과 검증, 서버 실패를 구분하되, 상태 코드만으로 사용자 메시지를 단정하지 말고 API의 오류 명세를 읽습니다. error.request만 있다면 요청은 보냈지만 응답을 받지 못한 경우이므로 네트워크나 CORS, 연결 실패를 살피며, timeout은 보통 ECONNABORTED이고 설정에 따라 ETIMEDOUT이 될 수 있어 code도 함께 봅니다. Axios 오류인데 response와 request가 모두 없다면 잘못된 config처럼 전송하기 전에 생긴 오류일 수 있고, 인터셉터가 일반 Error를 던졌다면 첫 분기의 예상 밖 오류로 다룹니다.

그림 아래의 두 칸은 판별을 둘러싼 조건을 덧붙입니다. 성공과 실패를 가르는 것은 validateStatus이므로 기준을 바꾸면 같은 HTTP 상태가 응답 인터셉터의 성공 쪽으로 갈 수 있고, 오류를 분류했다고 해서 자동 재시도가 허락되는 것은 아니므로 멱등성과 사용자의 의도, 서버의 힌트, 요청 본문을 다시 보낼 수 있는지를 확인합니다. 그림의 마지막 설명처럼 응답 인터셉터는 어댑터가 상태를 판정한 뒤에 실행되며, 오류 처리 함수가 값을 반환하면 체인이 복구된 이행 상태가 되므로 복구할 생각이 없다면 거절을 그대로 반환합니다(Axios 문서의 오류 처리).

HTTP 성공, JSON 변환, 게시글 모양은 따로 확인하기

AxiosExample은 성공 응답이 게시물 객체라고 전제합니다. Axios가 response.data를 준다고 해서 title과 body 필드까지 검증하지는 않으며, 잘못된 JSON에 대한 기본 변환 정책도 언제나 오류를 던진다고 보장하지 않습니다. 엄격한 JSON 문법 검사가 필요하다면 Axios의 요청 설정에 맞춰 responseType: 'json'과 transitional: { silentJSONParsing: false }를 함께 지정하고, 파싱한 뒤에 필수 필드를 따로 확인합니다. 상태 코드와 파싱, 필드의 모양은 각각 다른 단계에서 확인해야 합니다.


Axios 인스턴스 (Instance)

여러 API 요청에 공통으로 적용할 설정(기본 URL, 타임아웃 같은)이 있다면, Axios 인스턴스를 만들어 관리할 수 있습니다. 이렇게 하면 코드의 중복이 줄고 유지보수가 쉬워집니다. 다음 파일에는 JSX가 없으므로 .js로 저장합니다.

src/api/mockApi.js
import axios from 'axios';

const mockApi = axios.create({
  baseURL: 'http://localhost:4000', // 로컬 mock 서버 기본 URL
  timeout: 5000, // 5초 동안 응답이 없으면 타임아웃
});

export default mockApi;

axios.create가 돌려준 mockApi는 기본 axios와 같은 메서드를 가지되, 만들 때 준 설정을 모든 요청에 적용합니다. 인스턴스는 다음과 같이 씁니다. 아래 컴포넌트는 전체 주소 대신 '/users'만 적어도 baseURL이 앞에 붙어 http://localhost:4000/users로 요청하고, 5초 안에 응답이 없으면 timeout 오류로 끝납니다. axios 자체를 함께 가져온 것은 취소된 요청인지 판별하는 axios.isAxiosError를 쓰기 위해서입니다.

src/components/AxiosInstanceExample.jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import mockApi from '../api/mockApi'; // 인스턴스 임포트

function AxiosInstanceExample() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    let active = true;

    const fetchUsers = async () => {
      try {
        setLoading(true);
        setError(null);
        const response = await mockApi.get('/users', {
          signal: controller.signal,
        });
        if (active) setUsers(Array.isArray(response.data) ? response.data : []);
      } catch (err) {
        if (!active) return;
        if (axios.isAxiosError(err) && err.code === 'ERR_CANCELED') return;
        setError(err instanceof Error ? err : new Error('예상하지 못한 오류가 발생했습니다.'));
      } finally {
        if (active) setLoading(false);
      }
    };

    fetchUsers();

    return () => {
      active = false;
      controller.abort();
    };
  }, []);

  if (loading) {
    return <div style={{ textAlign: 'center', padding: '20px' }}>Axios 인스턴스로 사용자 목록 불러오는 중...</div>;
  }

  if (error) {
    return <div style={{ textAlign: 'center', padding: '20px', color: 'red' }}>Axios 인스턴스 오류: {error.message}</div>;
  }

  return (
    <div style={{ maxWidth: '800px', margin: '20px auto', padding: '20px', border: '1px solid #ddd', borderRadius: '8px', boxShadow: '0 2px 5px rgba(0,0,0,0.05)' }}>
      <h2 style={{ textAlign: 'center', color: '#2c3e50' }}>Axios 인스턴스 사용 예시 (사용자 목록)</h2>
      <ul style={{ listStyle: 'none', padding: 0 }}>
        {users.map(user => (
          <li
            key={user.id}
            style={{
              padding: '15px',
              marginBottom: '10px',
              border: '1px solid #eee',
              borderRadius: '5px',
              backgroundColor: '#fefefe',
              boxShadow: '0 1px 3px rgba(0,0,0,0.02)',
            }}
          >
            <strong style={{ color: '#3498db' }}>{user.firstName} {user.lastName}</strong> ({user.username}) - {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default AxiosInstanceExample;

mockApi.get('/users')는 인스턴스의 baseURL과 합쳐져 http://localhost:4000/users로 요청되며, 5초 안에 응답이 없으면 timeout 오류가 됩니다. 앞에서 쓴 db.json이라면 “민 안 (min) - [email protected]”와 “서 이 (seo) - [email protected]” 두 줄이 보입니다. 취소를 판별할 때는 인스턴스가 아닌 axios 모듈의 isAxiosError를 쓰므로 axios도 함께 가져옵니다.


Axios와 useEffect의 조합

fetch와 마찬가지로 Axios도 useEffect 안에서 async/await와 함께 써서 컴포넌트의 생명주기에 맞춰 데이터를 가져올 수 있습니다. 로딩과 성공, 실패를 나누는 원칙은 fetch와 같지만, Axios의 응답과 오류 객체의 모양에 맞춰 처리합니다. 다음 훅에는 JSX가 없으므로 .js로 저장합니다.

src/hooks/useAxiosFetch.js
import { useState, useEffect } from 'react';
import axios from 'axios';

const useAxiosFetch = (url, options) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    let active = true;

    const fetchData = async () => {
      if (!url) {
        setLoading(false);
        return;
      }

      setLoading(true);
      setError(null);

      try {
        const response = await axios.get(url, {
          ...options,
          signal: controller.signal,
        });
        if (active) setData(response.data);
      } catch (err) {
        if (!active) return;
        if (axios.isAxiosError(err) && err.code === 'ERR_CANCELED') return;
        setError(err);
      } finally {
        if (active) setLoading(false);
      }
    };

    fetchData();

    return () => {
      active = false;
      controller.abort();
    };
  }, [url, options]);

  return { data, loading, error };
};

export default useAxiosFetch;

이 훅을 부르는 쪽은 options를 컴포넌트 바깥의 상수로 두거나 useMemo로 참조를 고정해야 합니다. 객체를 JSON.stringify해 의존성으로 쓰면 함수나 AbortSignal 같은 값이 빠지고 직렬화 순서에 따라 결과가 달라지므로, 같은 요청인지 판정하는 데 알맞지 않습니다.

범용 조회 훅의 한계

useAxiosFetch는 현재 setup이 끝난 뒤의 늦은 쓰기를 막지만, 지금 보이는 데이터가 어느 URL의 것인지 알려 주는 key를 돌려주지 않습니다. url이 바뀐 직후 새 Effect가 실행되기 전의 렌더에서는 이전 data가 남아 있을 수 있고, url이 없어져도 이전의 data와 error를 비우지 않습니다. 서로 다른 사용자나 게시물의 데이터를 나누어 보여 줘야 한다면, 이 단순한 훅에 같은 key의 갱신과 새 key의 요청을 구분하는 설계를 더해야 합니다. 앞 글 useFetch의 visibleRequest와 다음 실습의 activeKey가 그 방법을 보여 주며, 취소 signal만 더한다고 이 표시 정책까지 해결되지는 않습니다.


fetch와 axios 비교

특징fetch (네이티브 API)axios (라이브러리)
설치브라우저에 내장설치 필요 (npm install axios)
응답 본문필요에 따라 .json(), .text(), stream reader 등을 호출기본 변환 결과를 response.data로 제공
HTTP 상태 판정4xx·5xx에도 응답 Promise가 이행되므로 response.ok 등을 확인기본 validateStatus가 2xx 밖을 거부하며 기준을 바꿀 수 있음
요청·응답 공통 처리wrapper를 직접 설계인스턴스와 interceptor API 제공
요청 취소AbortController의 signalAxios 0.22.0부터 같은 AbortController의 signal 지원
타임아웃지원하는 환경의 AbortSignal.timeout(), 또는 controller와 타이머의 조합timeout 설정과 AbortSignal을 함께 쓸 수 있음
진행률Streams API처럼 환경별 기능을 사용어댑터와 런타임이 지원하는 진행 이벤트를 사용
번들 비용애플리케이션 의존성이 늘지 않음라이브러리와 쓰는 기능만큼 늘어나므로 번들 분석으로 확인

요청이 몇 개뿐이고 단순하다면 내장 fetch와 작은 wrapper로 충분할 수 있습니다. 여러 서버의 기본값과 interceptor, timeout, 일관된 오류 객체가 필요하다면 Axios 인스턴스가 중복을 줄여 줍니다. 팀의 실제 요구와 번들 분석을 보고 고릅니다.


정리

  • Axios는 Promise 기반의 HTTP 클라이언트로, 응답을 response.data로 변환해 주고 기본 validateStatus에서 2xx 밖의 응답을 거부합니다.
  • 현행 JSON Server v1에서는 _limit 대신 _page와 _per_page를 쓰며, 페이지 응답은 data 배열을 담은 객체입니다.
  • 취소는 AbortController의 signal로 하고, CancelToken은 새 코드에서 쓰지 않습니다.
  • catch에서는 isAxiosError, ERR_CANCELED, response, request의 순서로 오류를 판별하고, 분류만으로 재시도를 결정하지 않습니다.
  • HTTP 상태와 JSON 파싱, 필드의 모양은 각각 따로 확인합니다.
  • 공통 설정은 인스턴스에, 호출마다 달라지는 값은 요청 config에 둡니다.
  • Axios를 도입할 때는 편한 문법보다 설정의 우선순위와 인터셉터의 반환값, 취소와 늦은 쓰기 방지, 오류 분류가 코드의 어디에 놓이는지를 함께 봅니다.

연습 문제

1. 네 가지 결과의 분류

네 요청은 모두 아직 active인 setup의 요청이라고 가정합니다. 기본 설정에서의 404, validateStatus: status => status < 500을 둔 상태에서의 404, ERR_CANCELED 취소, HTTP 200이지만 게시물의 모양이 잘못된 응답을 각각 생각해 보세요. 어느 것이 AxiosExample의 catch에 들어가고, 어느 것을 사용자에게 정상 게시글이라고 보여도 될까요?

해설 보기

기본 설정의 404는 거부되어 err.response.status로 “서버가 404 상태로 응답했습니다.”를 보여 줍니다. 바꾼 validator는 404도 이행으로 보내므로 catch가 실행되지 않으며, 이를 실제 게시물의 성공으로 다룰지는 API의 명세에 맞는 추가 판정이 필요합니다.

취소는 ERR_CANCELED 검사에서 걸러져 사용자 오류를 저장하지 않습니다. 200은 기본 HTTP 검사를 통과하지만, 필수 필드가 없으면 올바른 게시물 화면을 보장하지 못합니다. 그래서 상태 코드와 파싱, 필드의 모양은 각각 확인해야 하며, 넷 가운데 확인 없이 정상 게시물로 보여도 되는 것은 없습니다.

2. 삭제한 뒤의 조회

DELETE 예제로 /posts/1을 지운 뒤 db.json을 되돌리지 않고 AxiosExample을 다시 열면 화면에 무엇이 나올까요?

해설 보기

json-server가 그 게시물을 찾지 못해 404로 응답하고, Axios의 기본 validateStatus가 이를 거부하므로 catch의 err.response 분기로 갑니다. 화면에는 “Axios 오류: 서버가 404 상태로 응답했습니다.”가 보입니다. db.seed.json으로 db.json을 되돌리면 다시 “첫 게시물”이 보입니다.

3. URL이 바뀐 직후의 렌더

useAxiosFetch에 넘기는 url이 /posts/1의 결과를 받은 뒤 /posts/2로 바뀌었습니다. 새 Effect가 실행되기 전의 렌더와, /posts/2가 404로 끝난 뒤의 렌더에서 data, loading, error는 각각 어떻게 될까요?

해설 보기

url이 바뀐 렌더에서는 아직 상태가 바뀌지 않았으므로 data는 1번 게시물, loading은 false, error는 null입니다. 이 순간 화면은 2번을 요청했는데 1번을 보여 줄 수 있습니다. 새 Effect가 실행되면 loading이 true, error가 null이 되지만 data는 여전히 1번입니다.

404로 끝나면 error에 Axios 오류가 저장되고 loading은 false가 되며, data는 비우지 않으므로 1번 게시물이 그대로 남습니다. 그래서 부르는 쪽이 error를 먼저 확인하지 않으면 이전 게시물이 보일 수 있으며, 이 문제를 막으려면 앞 글의 visibleRequest처럼 지금 보이는 결과의 key를 함께 관리해야 합니다.

Axios의 전체 설정과 인스턴스 사용법은 Axios 문서에서 확인할 수 있습니다.

다음 글 Axios 게시판 조회·추가·삭제에서는 Axios 인스턴스와 요청 훅으로 게시물의 조회와 추가, 삭제를 구현하고 지연과 실패, 재시도 상태를 공통 UI로 처리해 봅니다.