들어가면서
React + TypeScript에서 API를 다룰 때, 가장 흔히 생기는 문제는 다음과 같다:
- res.data가 어떤 구조인지 매번 console.log로 확인해야 함
- 백엔드에서 응답 구조 바뀌면 알아채지 못함
- 반복되는 response.data.foo.bar.baz에 타입 없음
- 에러 처리할 때 구조가 다 달라 혼란
이 문제는 "타입 안전한 API 응답 구조 정의" 로 상당 부분 해결할 수 있다.
이번 포스팅에서는 Axios 기반 실무 코드에서 사용하는 API 타입 설계 패턴을 알아보고자 한다.
✅ 1. 기본적인 Axios 응답 타입 지정
interface User {
id: number;
name: string;
email: string;
}
axios.get<User>('/api/user/1').then((res) => {
console.log(res.data.name); // 타입 안전하게 접근 가능
});
→ axios.get<User>라고 쓰면 내부의 res.data가 User 타입이 된다.
하지만 실무에서는 이보다 조금 더 구조화된 방식이 필요하다.
✅ 2. 공통 API 응답 타입 정의하기
실제 백엔드 API는 종종 다음과 같은 형태로 응답한다:
{
"success": true,
"data": {
"id": 1,
"name": "박지우"
},
"message": "성공"
}
→ 이를 기반으로 공통 응답 타입을 정의해두면 재사용 가능하다.
interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
}
사용 예시:
interface User {
id: number;
name: string;
}
axios.get<ApiResponse<User>>('/api/user/1').then((res) => {
if (res.data.success) {
console.log(res.data.data.name); // 안전하게 접근
}
});
✅ 3. 요청(Request) Body 타입도 명시해두기
interface LoginRequest {
email: string;
password: string;
}
interface LoginResponse {
token: string;
user: {
id: number;
name: string;
};
}
axios.post<ApiResponse<LoginResponse>, AxiosResponse<ApiResponse<LoginResponse>>, LoginRequest>(
'/api/login',
{
email: 'test@dev.com',
password: '1234',
}
);
→ 요청 바디 타입은 세 번째 제네릭 파라미터로 명시 가능 (보통은 자동 추론되긴 함)
✅ 4. 에러 응답 타입도 정의해두기
백엔드에서 다음과 같은 에러를 보낼 수 있다면:
{
"success": false,
"message": "유저를 찾을 수 없습니다.",
"code": "USER_NOT_FOUND"
}
에러 타입 정의:
interface ApiErrorResponse {
success: false;
message: string;
code: string;
}
에러 처리 코드:
axios
.get<ApiResponse<User>>('/api/user/999')
.catch((err: AxiosError<ApiErrorResponse>) => {
console.error(err.response?.data.message); // 타입 안전하게 에러 메시지 확인 가능
});
✅ 5. API 함수 분리 & 통합 패턴
// services/userApi.ts
import axios from 'axios';
interface User {
id: number;
name: string;
}
interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
}
export const fetchUser = (id: number) => {
return axios.get<ApiResponse<User>>(`/api/user/${id}`);
};
사용부 (컴포넌트):
fetchUser(1).then((res) => {
console.log(res.data.data.name); // 타입 안전
});
✅ 6. API 타입 파일 구조 예시
src/
├─ types/
│ ├─ api.ts # ApiResponse, ApiErrorResponse
│ └─ user.ts # User, LoginRequest, LoginResponse
└─ services/
└─ userApi.ts # axios 요청 함수
✨ 마무리
타입스크립트를 쓰면서도 res.data.뭐였더라...? 하고 console 찍고 있다면,
지금 바로 API 타입을 모듈화해서 정리해보자.
기본 응답 포맷만 통일해도 협업, 디버깅, 유지보수가 훨씬 편해진다.
근데 솔직히 나만 해도 주로 1,2번만을 주로 하기는 하지만 그래도 나머지 포맷들로도 적용해보면 좋을거 같다 ㅎㅎ
'TypeScript' 카테고리의 다른 글
| 🧩 Props에 타입 주는 법 완전 정리 (React + TypeScript) (0) | 2025.06.16 |
|---|---|
| 🧠 useState에 타입 주는 가장 깔끔한 패턴 정리 (React + TypeScript) (0) | 2025.06.16 |