
Next.js에서 API 클라이언트 만들기
소개
웹 애플리케이션에서 API 통신은 필수적인데, 이 글에서는 Next.js에서 TypeScript와 Axios를 사용하여 내가 구현한 API 클라이언트 만드는 것을 복습해보겠다.
주요 기능
- 토큰 기반 인증 처리
- 토큰 만료 시 자동 갱신
- 요청 쓰로틀링
- 타입 안전성
- 에러 처리
구현 상세
1. API 클라이언트 기본 구조
interface ApiOptions {
params?: Record<string, string | number | boolean>;
throttleKey?: string;
}
export interface ApiResponse<T> {
data: T;
response?: string;
message?: string;
success?: boolean;
}
throttleKey는 같은 통신이 동시에 2번 이상 요청되는 것을 방지하기 위해서 넣었다. body가 필요한 분은 body를 더 넣어도 된다. 내가 만드는 서비스에선 일단 처음에는 body를 넣을 일이 없어서 일단 넣지 않았다.
2. 쓰로틀링 구현하기
서버 부하를 줄이고 중복 요청을 방지하기 위한 쓰로틀링 구현 예시.
class ThrottleManager {
private requests = new Map();
private readonly throttleTime = 1000; // 1초
async throttle<T>(key: string, requestFn: () => Promise<T>): Promise<T> {
const now = Date.now();
const existingRequest = this.requests.get(key);
if (
existingRequest &&
now - existingRequest.timestamp < this.throttleTime
) {
return existingRequest.promise as Promise<T>;
}
}
}
3. 토큰 관리와 인터셉터
우리 어드민에서 사용하는 것이다. 로그인 시에 토큰을 받아오고 그걸 저장해뒀따가 요청 인터셉터에 항상 담아서 보낼꺼다.
const createAxiosInstance = (): AxiosInstance => {
const instance = axios.create({
baseURL: API_BASE_URL,
headers: {
"Content-Type": "application/json",
},
});
// 요청 인터셉터
instance.interceptors.request.use((config) => {
const token = auth.getAdminToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 응답 인터셉터
instance.interceptors.response.use(
(response) => response,
async (error) => {
// 토큰 만료 처리
if (error.response?.status === 401) {
// ... 토큰 갱신 로직
}
// ... 에러 처리
}
);
return instance;
};
4. HTTP 메서드 구현
GET, POST, PUT, DELETE, PATCH 요청을 이제 본격적으로 구현한다.
export const api = {
async get<T>(endpoint: string, options: ApiOptions = {}): Promise<T> {
const throttleKey =
options.throttleKey ||
`GET:${endpoint}:${JSON.stringify(options.params)}`;
return throttleManager.throttle(throttleKey, async () => {
// ...
});
},
// POST, PUT, DELETE, PATCH 메서드도 유사하게 구현
};
사용예시
interface User {
id: number;
name: string;
}
// GET 요청
const user = await api.get<User>("/users/1");
// POST 요청
const newUser = await api.post<User>("/users", { name: "John" });
사실 이렇게 구현한건 flutter에서도 이미 비슷하게 구현을 했다. flutter에서는 dio 라이브러리와 riverpod을 조합하여 사용하였다.