[TYPESCRIPT] Axios와 함께 쓰는 TypeScript — API 호출을 타입으로 통제하는 실무 패턴

 

TypeScript 환경에서 HTTP 통신 라이브러리로 Axios를 사용하는 경우가 매우 많습니다. Axios는 사용법이 간단하고 기능이 풍부하지만, 타입을 제대로 설계하지 않으면 결국 any 기반 코드로 전락하기 쉽습니다.

 

Axios 기본 타입 구조 이해하기

Axios의 핵심 반환 타입은 AxiosResponse<T>입니다.

import axios from "axios";

axios.get("/api/user");

아무 타입도 지정하지 않으면 응답 데이터는 any로 취급됩니다.

const res = await axios.get("/api/user");
// res.data: any

즉, TypeScript의 보호를 전혀 받지 못하는 상태입니다.

 

제네릭으로 응답 타입 지정하기

Axios는 제네릭을 통해 응답 타입을 명확하게 지정할 수 있습니다.

type User = {
  id: number;
  name: string;
};

const res = await axios.get<User>("/api/user");

이제 res.data는 다음과 같이 추론됩니다.

User

이 패턴은 Axios + TypeScript 사용의 출발점입니다.

 

AxiosResponse를 직접 사용하지 않는 이유

많은 초보 코드에서 다음과 같은 패턴을 볼 수 있습니다.

const res: AxiosResponse<User> = await axios.get("/api/user");

이 방식은 권장되지 않습니다.

  • Axios 내부 구현 타입에 강하게 의존
  • 불필요한 정보(headers, config)에 노출

실무에서는 data만 추출해 반환하는 구조가 훨씬 낫습니다.

 

공통 API 함수 패턴

Axios를 직접 사용하지 않고, 공통 API 레이어를 두는 것이 실무 표준입니다.

const api = axios.create({
  baseURL: "/api",
  timeout: 5000,
});
async function get<T>(url: string) {
  const res = await api.get<T>(url);
  return res.data;
}
const user = await get<User>("/user");
// user: User

이 패턴의 장점:

  • Axios 의존성 최소화
  • 타입 전파가 단순
  • 테스트 및 교체 용이

 

타입 안전한 API 응답 구조와 결합

Axios 제네릭은 응답 전체 타입과 함께 사용하는 것이 이상적입니다.

type ApiResponse<T> =
  | { ok: true; data: T }
  | { ok: false; errorCode: string; message: string };
async function fetchUser() {
  return get<ApiResponse<User>>("/user");
}
const res = await fetchUser();

if (res.ok) {
  res.data.name;
} else {
  console.error(res.message);
}

Axios + Discriminated Union 조합은 실무에서 가장 안정적인 API 처리 방식입니다.

 

POST 요청과 요청 데이터 타입

응답뿐 아니라 요청 데이터도 타입으로 통제해야 합니다.

type CreateUserRequest = {
  name: string;
  age: number;
};

type CreateUserResponse = {
  id: number;
};
async function createUser(
  body: CreateUserRequest
) {
  return get<CreateUserResponse>("/user/create", body);
}

요청/응답 모두 타입으로 관리하면 API 계약이 코드로 명확히 드러납니다.

 

Axios 인터셉터와 타입

Axios 인터셉터에서도 타입 안정성은 중요합니다.

api.interceptors.response.use(
  response => response,
  error => {
    if (axios.isAxiosError(error)) {
      console.error(error.response?.data);
    }
    return Promise.reject(error);
  }
);

axios.isAxiosError는 에러 타입을 안전하게 좁히는 공식적인 방법입니다.

 

Axios 사용 시 흔한 실수

  • 응답 타입을 지정하지 않음
  • AxiosResponse를 서비스 로직까지 전파
  • any 기반 에러 처리
  • API 응답 구조를 타입으로 정의하지 않음

이 실수들은 모두 타입 설계 부족에서 시작됩니다.

 

실무 권장 구조 요약

  • Axios 인스턴스 분리
  • 공통 API 함수로 data만 반환
  • 응답은 Discriminated Union으로 모델링
  • 요청/응답 모두 타입 정의
  • AxiosError는 공식 타입 가드 사용

이 구조는 프론트엔드·백엔드 모두에서 유지보수성과 안정성을 크게 향상시킵니다.

 


 

Axios와 TypeScript를 함께 사용한다는 것은 단순히 “에러 없이 호출한다”는 의미가 아니라, API 통신을 타입으로 계약화한다는 의미입니다.

  • Axios 제네릭은 필수
  • data만 반환하는 구조가 이상적
  • 응답은 반드시 타입으로 모델링