Axios는 브라우저와 Node.js 환경 모두를 지원하는 Promise 기반 HTTP 클라이언트입니다. 내장 fetch에 비해 요청·응답 인터셉터, 자동 JSON 직렬화·역직렬화, 오류 처리 등 편의 기능을 기본으로 제공합니다.
npm install axios
Instance: baseURL, timeout, 공통 헤더 등을 미리 설정해 두고 재사용하는 Axios 객체
동일한 API 서버에 여러 요청을 보낼 때, 매 요청마다 baseURL을 반복하는 대신 인스턴스에 한 번 설정합니다.
// src/api/client.ts
import axios from "axios";
const client = axios.create({
baseURL: "https://api.example.com",
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
});
export default client;
// 사용 시 — baseURL이 자동으로 앞에 붙음
import client from "./client";
const response = await client.get("/users"); // GET https://api.example.com/users
인스턴스를 기능별로 분리하는 것도 가능합니다. 예를 들어 인증이 필요한 API용 인스턴스와 공개 API용 인스턴스를 별도로 만들 수 있습니다.
Axios는 각 HTTP 메서드에 대응하는 단축 메서드를 제공합니다.
// GET — 데이터 조회
const { data: users } = await client.get("/users");
const { data: user } = await client.get("/users/1", {
params: { include: "profile" }, // ?include=profile 쿼리스트링으로 변환
});
// POST — 데이터 생성
const { data: newUser } = await client.post("/users", {
name: "홍길동",
email: "hong@example.com",
});
// PUT — 데이터 전체 교체
await client.put("/users/1", { name: "홍길동", email: "new@example.com" });
// PATCH — 데이터 일부 수정
await client.patch("/users/1", { name: "홍길순" });
// DELETE — 데이터 삭제
await client.delete("/users/1");
params 옵션은 객체를 URL 쿼리스트링으로 자동 변환합니다.
Axios는 서버 응답을 다음 구조로 래핑합니다.
interface AxiosResponse<T> {
data: T; // 서버가 응답한 실제 데이터 (JSON 자동 파싱)
status: number; // HTTP 상태 코드 (200, 201, 404 등)
statusText: string; // 상태 메시지 ("OK", "Not Found" 등)
headers: object; // 응답 헤더
config: object; // 요청 시 사용된 설정
request: object; // 실제 요청 객체
}
const response = await client.get<User>("/users/1");
console.log(response.data); // User 객체
console.log(response.status); // 200
console.log(response.headers["content-type"]); // "application/json"
구조 분해 할당으로 data만 바로 꺼내는 패턴이 자주 쓰입니다.
const { data } = await client.get<User[]>("/users");
Header: 요청과 응답에 포함되는 메타데이터로, 인증 토큰·콘텐츠 타입·캐시 정책 등을 전달하는 방법
헤더는 인스턴스 기본값, 메서드별 기본값, 요청별 설정 세 단계로 우선순위가 결정됩니다.
// 1. 인스턴스 생성 시 기본 헤더 설정
const client = axios.create({
headers: { "Content-Type": "application/json" },
});
// 2. 특정 메서드의 기본 헤더 설정
client.defaults.headers.post["X-Custom-Header"] = "value";
// 3. 요청별 헤더 설정 (가장 높은 우선순위)
await client.get("/secure", {
headers: { Authorization: `Bearer ${token}` },
});
Authorization 헤더처럼 매 요청에 공통으로 붙어야 하는 헤더는 인터셉터로 자동화하는 것이 일반적입니다.
각 요청에 다양한 옵션을 객체 형태로 전달할 수 있습니다.
const config: AxiosRequestConfig = {
params: { page: 1, limit: 20 }, // URL 쿼리스트링
headers: { Authorization: "Bearer ..." }, // 요청 헤더
timeout: 5000, // 타임아웃(ms)
responseType: "blob", // 응답 타입 (json/blob/arraybuffer 등)
signal: abortController.signal, // 요청 취소 시그널
onUploadProgress: (e) => { // 업로드 진행률 콜백
console.log(`${Math.round(e.progress! * 100)}%`);
},
};
await client.post("/files", formData, config);
인스턴스의 기본 설정(defaults)과 요청별 config가 병합되며, 요청별 설정이 우선합니다.
Interceptor: 요청이 전송되기 전 또는 응답이 반환되기 전에 가로채 공통 처리를 삽입하는 미들웨어
인터셉터는 인증 토큰 자동 삽입, 에러 코드 공통 처리, 로딩 상태 관리 등에 사용됩니다.
// 요청 인터셉터 — 토큰 자동 삽입
client.interceptors.request.use(
(config) => {
const token = localStorage.getItem("accessToken");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 응답 인터셉터 — 토큰 만료 처리
client.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const newToken = await refreshAccessToken();
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return client(originalRequest); // 원래 요청 재시도
}
return Promise.reject(error);
}
);
인터셉터는 등록한 순서대로 실행됩니다. 더 이상 필요 없을 때는 eject로 제거합니다.
const id = client.interceptors.request.use(handler);
client.interceptors.request.eject(id);
Timeout: 지정한 시간 내에 서버 응답이 오지 않으면 자동으로 요청을 중단하고 에러를 발생시키는 설정
타임아웃은 인스턴스 기본값 또는 요청별로 설정합니다.
// 인스턴스에 기본 타임아웃 설정
const client = axios.create({
baseURL: "https://api.example.com",
timeout: 10000, // 10초
});
// 요청별 타임아웃 오버라이드
await client.get("/slow-endpoint", { timeout: 30000 }); // 30초
// 타임아웃 에러 처리
try {
await client.get("/data");
} catch (error) {
if (axios.isAxiosError(error) && error.code === "ECONNABORTED") {
console.error("요청 시간이 초과되었습니다.");
}
}
타임아웃 에러의 code는 ECONNABORTED입니다.
Cancel Request: 이미 전송된 요청을 중간에 취소하는 방법, 주로 AbortController를 사용
컴포넌트 언마운트 또는 사용자가 검색어를 빠르게 변경할 때 이전 요청이 완료되기 전에 취소하여 불필요한 처리를 줄입니다.
import { useEffect, useState } from "react";
function SearchResults({ query }: { query: string }) {
const [results, setResults] = useState([]);
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
try {
const { data } = await client.get("/search", {
params: { q: query },
signal: controller.signal,
});
setResults(data);
} catch (error) {
if (axios.isCancel(error)) {
// 취소된 요청은 에러로 처리하지 않음
return;
}
console.error(error);
}
};
fetchData();
return () => {
// 컴포넌트 언마운트 또는 query 변경 시 이전 요청 취소
controller.abort();
};
}, [query]);
return <ul>{results.map((r) => <li>{r}</li>)}</ul>;
}
axios.isCancel로 취소로 인한 에러인지 판별합니다.
Error Handling: Axios 에러 객체의 구조를 파악하고 HTTP 상태 코드 및 상황별로 분기하여 처리하는 방법
Axios는 4xx·5xx 응답을 자동으로 에러로 처리합니다. axios.isAxiosError로 Axios 에러인지 판별하고, error.response에서 서버가 반환한 상세 정보를 읽습니다.
try {
const { data } = await client.get("/users/999");
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response) {
// 서버가 응답을 반환했으나 2xx 범위 밖
const { status, data } = error.response;
switch (status) {
case 400:
console.error("잘못된 요청:", data.message);
break;
case 401:
console.error("인증이 필요합니다.");
break;
case 403:
console.error("접근 권한이 없습니다.");
break;
case 404:
console.error("리소스를 찾을 수 없습니다.");
break;
case 500:
console.error("서버 오류가 발생했습니다.");
break;
default:
console.error(`알 수 없는 오류: ${status}`);
}
} else if (error.request) {
// 요청은 전송되었으나 응답 없음 (네트워크 오류)
console.error("서버에 연결할 수 없습니다.");
} else {
// 요청 설정 중 오류
console.error("요청 설정 오류:", error.message);
}
}
}
공통 에러 처리는 응답 인터셉터에 두고, 컴포넌트에서는 비즈니스 로직에 맞는 처리만 담당하도록 분리하는 것이 유지보수에 유리합니다.