axios 라이브러리 소개
Axios로 GET·POST·수정·삭제 요청을 보내고 공통 설정 인스턴스를 만들어 Fetch API와 사용 방식을 비교합니다.
프론트엔드 개발에서 매우 널리 쓰이는 HTTP 클라이언트 라이브러리 Axios를 알아보겠습니다.
fetch는 브라우저에 내장된 표준 API이고 AbortController를 포함해 요청에 필요한 기본 기능을 제공합니다. 다만 HTTP 오류 판정과 JSON 변환을 호출 코드에서 직접 처리해야 합니다.
Axios는 인스턴스 기본값, 요청·응답 인터셉터, timeout, 응답 변환을 한 클라이언트 계층에 모으고 싶을 때 유용합니다. 어느 쪽이 적합한지는 프로젝트의 정책 복잡도와 번들 비용에 따라 결정합니다.
실습 예시는 외부 공개 API 대신 로컬 Mock API(json-server, http://localhost:4000) 기준으로 맞춥니다.
포트 정책은 4000=Mock API, 4100=별도 백엔드/실시간 서버로 분리하면 실습 환경 충돌을 줄일 수 있습니다.
아래 다이어그램은 호출 의도부터 설정 병합, 실제 전송, 응답 처리와 React 상태 커밋까지의 경계를 보여 줍니다.
Intent → transport → UI commit
Axios 인스턴스는 호출 의도와 전송 정책 사이의 경계입니다. 설정의 출처와 인터셉터 반환 계약을 분명히 하고, 네트워크 취소와 오래된 UI 커밋 방지를 함께 설계합니다.
-
API 함수가 호출 의도를 표현
getUser(id, { signal })처럼 method·URL·params/data와 취소 신호를 전달합니다. 컴포넌트는 전송 구현보다 필요한 작업을 말합니다. -
설정을 출처별 우선순위로 병합
library defaults<instance.defaults<request config. 뒤의 요청별 값이 앞의 공통값을 덮어쓰므로 예외 정책을 호출 지점에서 명시할 수 있습니다. -
선택한 요청 인터셉터가 config를 보강
인증·추적처럼 여러 요청에 공통인 관심사만 다룹니다. fulfilled handler는 config를 반환하고, 실패를 계속 전달하려면 reject를 반환합니다.
-
어댑터가 실제 HTTP 전송을 수행
브라우저·Node 환경에 맞는 adapter가 최종 config를 전송합니다.
AbortSignal은 이 전송에 연결되어 대기 중인 요청을 실제로 중단합니다. -
응답 인터셉터가 계약을 명시적으로 이어감
성공 handler는 원래
response또는 의도적으로 정규화한 값을 반환합니다. 오류 handler는 복구하지 않는 한Promise.reject(error)로 거절 상태를 보존합니다. -
호출자가 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에서 명시합니다.
Axios란 무엇인가?
Axios는 브라우저와 Node.js 환경에서 모두 사용할 수 있는 Promise 기반의 HTTP 클라이언트 라이브러리입니다.
즉, 서버와의 통신(API 호출)을 더 쉽고 편리하게 할 수 있도록 도와주는 도구입니다.
주요 특징 및 장점- Promise 기반:
async/await와 함께 사용하여 비동기 코드를 매우 간결하게 작성할 수 있습니다. - 간편한 API:
fetch보다 더 직관적이고 사용하기 쉬운 API를 제공합니다. - 자동 JSON 변환: 응답 데이터를 자동으로 JSON으로 파싱해주므로
response.json()을 별도로 호출할 필요가 없습니다. - 에러 처리 정책: 기본
validateStatus에서는 2xx 밖의 HTTP 응답을 reject합니다. 기준을 바꾸면 같은 상태 코드가 fulfilled 경로로 갈 수 있습니다. 기본fetch는 HTTP 상태와 관계없이 응답 Promise를 resolve하므로response.ok등을 직접 확인합니다. - 요청/응답 인터셉터: adapter가 요청을 보내기 전과 응답·오류가 돌아온 뒤 공통 로직을 적용할 수 있습니다.
- 요청 취소 기능: Axios 0.22.0부터 표준
AbortController의signal을 지원합니다.CancelToken은 deprecated API이므로 새 코드에서는 사용하지 않습니다. - 요청 타임아웃 설정: 특정 시간 내에 응답이 없으면 요청을 자동으로 취소하는 기능을 제공합니다.
- 업로드 진행률 추적: 파일 업로드 시 진행률을 모니터링할 수 있습니다.
Axios 설치
Axios를 사용하려면 먼저 프로젝트에 설치해야 합니다.
npm install axios
# 또는
yarn add axiosAxios의 기본 사용법
설치 후에는 다음과 같이 Axios를 임포트하여 사용할 수 있습니다.
import axios from 'axios';GET 요청
데이터를 조회할 때 사용합니다.
// 모든 게시물 가져오기
axios.get('http://localhost:4000/posts?_limit=10')
.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);
});POST 요청
새로운 데이터를 생성할 때 사용합니다.
두 번째 인자로 전송할 데이터를 객체 형태로 전달합니다.
axios.post('http://localhost:4000/posts', {
title: '새로운 게시물',
body: '이것은 Axios로 작성된 새 게시물입니다.',
userId: 1,
})
.then(response => {
console.log('게시물 생성 성공:', response.data);
})
.catch(error => {
console.error('게시물 생성 오류:', error);
});PUT/PATCH 요청
기존 데이터를 업데이트할 때 사용합니다.
PUT: 일반적으로 대상 리소스를 요청 표현으로 교체하는 의미로 사용합니다. 필수 필드와 실제 동작은 서버 API 계약을 따라야 합니다.PATCH: 리소스의 일부 변경을 표현합니다. 지원하는 patch 문서 형식은 서버 계약을 확인합니다.
// 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);
});DELETE 요청
데이터를 삭제할 때 사용합니다.
axios.delete('http://localhost:4000/posts/1')
.then(response => {
console.log('게시물 삭제 성공:', response.status); // 200 OK, 204 No Content 등
})
.catch(error => {
console.error('게시물 삭제 오류:', error);
});async/await와 함께 사용
Axios는 Promise를 반환하므로, async/await 문법과 함께 사용하면 비동기 코드를 동기 코드처럼 깔끔하게 작성할 수 있습니다.
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.js에 AxiosExample 컴포넌트를 추가하여 테스트해 보세요.
Axios의 catch 블록에서는 오류 객체의 어느 필드가 채워졌는지에 따라 사용자 메시지와 복구 방식을 다르게 잡아야 합니다.
Error evidence · recovery choice
오류 메시지보다 먼저 Axios가 남긴 증거를 봅니다. 취소인지, 서버 응답이 있는지, 요청은 만들어졌지만 응답이 없는지에 따라 사용자 안내와 재시도 가능성이 달라집니다.
-
axios.isAxiosError(error)로 경계를 확인false라면 렌더 코드나 응답 처리 코드 등에서 생긴 예상 밖 오류일 수 있습니다. Axios 전송 오류처럼 필드를 가정하지 말고 별도 오류 경로로 보냅니다. -
error.code === 'ERR_CANCELED'화면 이탈·요청 교체 같은 의도적 취소를 먼저 분리합니다. 보통 오류 배너나 자동 재시도의 대상이 아니며 stale state도 커밋하지 않습니다.
-
error.response가 있으면 서버가 응답status,data,headers를 바탕으로 인증·검증·서버 실패를 구분합니다. 상태 코드만으로 사용자 메시지를 단정하지 말고 API 오류 계약을 읽습니다. -
error.request만 있으면 응답을 받지 못한 요청네트워크·CORS·연결 실패를 조사합니다. timeout은 보통
ECONNABORTED, 설정에 따라ETIMEDOUT코드가 될 수 있으므로code도 함께 확인합니다. -
Axios 오류지만 response와 request가 없으면 설정 단계
잘못된 config나 interceptor에서 던진 오류처럼 전송 전에 실패한 경우입니다. Axios 오류가 아니면 이 분기와 섞지 말고 예상 밖 프로그래밍 오류로 보고합니다.
validateStatus가 resolve와 reject를 가른다
기본 설정은 2xx를 성공으로 봅니다. 기준을 바꾸면 같은 HTTP 상태가 fulfilled 응답 인터셉터로 갈 수 있으므로 호출자 오류 정책과 함께 관리합니다.
분류는 자동 재시도 허가가 아니다
멱등성, 사용자 의도, 서버 힌트, 요청 body 재전송 가능성을 확인합니다. 특히 취소·검증 실패·설정 오류는 무조건 재시도하지 않습니다.
응답 인터셉터는 adapter가 상태를 판정한 뒤 실행됩니다. 오류 handler가 값을 반환하면 체인은 복구된 fulfilled 상태가 되므로, 복구 의도가 없으면 reject를 그대로 반환합니다.
Axios 인스턴스 (Instance)
여러 API 요청에서 공통적으로 적용해야 할 설정(예: 기본 URL, 타임아웃)이 있다면, Axios 인스턴스를 생성하여 관리할 수 있습니다.
이는 코드 중복을 줄이고 유지보수성을 높입니다.
import axios from 'axios';
const mockApi = axios.create({
baseURL: 'http://localhost:4000', // 로컬 mock 서버 기본 URL
timeout: 5000, // 5초 동안 응답이 없으면 타임아웃
});
export default mockApi;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;Axios와 useEffect의 조합
fetch API와 마찬가지로, Axios도 useEffect 훅 내부에서 async/await와 함께 사용하여 컴포넌트 생명주기에 맞춰 데이터를 가져올 수 있습니다.
로딩·성공·실패를 구분하는 원칙은 fetch와 같지만, Axios의 응답과 오류 객체 형태에 맞춰 처리해야 합니다.
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 같은 값이 빠지고 직렬화 순서에 결합되므로 요청 동일성 판정에 적합하지 않습니다.
fetch vs axios 정리
| 특징 | fetch (네이티브 API) | axios (라이브러리) |
|---|---|---|
| 설치 필요 여부 | 브라우저에 내장 | 설치 필요 (npm install axios) |
| 응답 body | 필요에 따라 .json(), .text(), stream reader 등을 호출 | 기본 변환 결과를 response.data로 제공 |
| HTTP 상태 판정 | 4xx·5xx도 응답 Promise는 resolve되므로 response.ok 등을 확인 | 기본 validateStatus는 2xx 밖을 reject하며 기준 변경 가능 |
| 요청/응답 공통 처리 | wrapper를 직접 설계 | 인스턴스와 interceptor API 제공 |
| 요청 취소 | AbortController의 signal | Axios 0.22.0부터 같은 AbortController의 signal 지원 |
| 타임아웃 | 지원 환경의 AbortSignal.timeout() 또는 controller와 timer 조합 | timeout 설정과 AbortSignal을 함께 사용할 수 있음 |
| 진행률 처리 | Streams API 등 환경별 기능 사용 | adapter·런타임이 지원하는 진행 이벤트 사용 |
| 번들 비용 | 애플리케이션 의존성 추가 없음 | 라이브러리와 사용 기능에 따른 비용이 추가되므로 현재 번들 분석으로 확인 |
단순한 몇 개 요청이라면 내장 fetch와 작은 wrapper가 충분할 수 있습니다. 여러 서버의 기본값, interceptor, timeout과 일관된 오류 객체가 필요하다면 Axios 인스턴스가 중복을 줄일 수 있습니다. 팀의 실제 요구와 번들 분석을 기준으로 선택합니다.
Axios 라이브러리 소개는 여기까지입니다.
이 장에서는 Axios가 무엇인지, fetch와 어떤 계약 차이가 있는지 알아보았습니다.
GET, POST, PUT, DELETE와 같은 기본적인 HTTP 요청을 async/await와 함께 사용하는 방법과, Axios 인스턴스를 생성하여 공통 설정을 관리하는 방법까지 학습했습니다.
Axios를 도입할 때는 편리한 문법보다 설정 우선순위, interceptor의 반환 계약, 취소와 stale commit 방지, 오류 분류가 실제 코드에서 어디에 놓이는지 함께 봐야 합니다.