이 포스팅은 axios 1.7.9 버전을 기준으로 작성되었습니다.
📦src
┣ 📂app
┃ ┣ 📂test // 테스트 페이지(/test)
┃ ┃ ┗ 📜page.tsx
┃ ┣ 📜layout.tsx
┃ ┗ 📜page.tsx // 홈 페이지(/)
┗ 📂util
┃ ┣ 📂apis
┃ ┃ ┣ 📂@types // 타입 폴더
┃ ┃ ┃ ┣ 📜shared.ts
┃ ┃ ┃ ┣ 📜test.ts
┃ ┃ ┣ 📂service // api 서비스 폴더
┃ ┃ ┃ ┣ 📜test.ts
┃ ┃ ┣ 📜axios.ts // axios 설정 파일
┗ ┗ ┗ 📜index.ts
npm install axios
.env
등의 환경변수 파일을 생성하고 api의 엔드포인트 url을 추가해줍니다.NEXT_PUBLIC_SERVER_URL=https://jsonplaceholder.typicode.com
axios.ts
파일을 생성하고 다음을 import 합니다.import axios, {
AxiosError,
AxiosResponse,
InternalAxiosRequestConfig,
} from "axios";
Content-Type
을 설정합니다.import axios from 'axios';
export const API = axios.create({
baseURL: process.env.NEXT_PUBLIC_SERVER_URL,
headers: {
"Content-Type": "application/json",
},
});
export const AuthStorage = {
async setToken( accessToken: string ) {
await localStorage.setItem("accessToken", accessToken);
},
async getToken(): Promise<string | null> {
return await localStorage.getItem("accessToken");
},
async clear() {
await localStorage.removeItem("accessToken");
},
};
API.interceptors.request.use(
async (config: InternalAxiosRequestConfig) => {
const token = await AuthStorage.getToken(); // 토큰 미사용시 무시
if (token) { // 토큰 사용시 헤더에 토큰 추가
config.headers.Authorization = `Bearer ${token}`;
}
console.log({
headers: config.headers,
method: config.method,
url: config.url,
baseUrl: config.baseURL,
data: config.data,
params: config.params,
});
return config;
},
(error: AxiosError) => {
console.log("API request error", error.config);
return Promise.reject(error);
}
);
Shared.ErrorResponse
타입은 아래에서 설명하도록 하겠습니다.API.interceptors.response.use(
(response: AxiosResponse) => {
console.log({
status: response.status,
statusText: response.statusText,
data: response.data,
});
return response.data; // 서버에서 받는 데이터가 data 속성에 들어있는 경우
// return response.data.data; // 서버에서 받는 데이터가 data.data 속성에 들어있는 경우
},
async (error: AxiosError) => {
console.warn(error.config?.url + " API response error", {
response_data: error.response?.data,
status: error.response?.status,
request_info: {
method: error.config?.method,
url: error.config?.url,
baseUrl: error.config?.baseURL,
headers: error.config?.headers,
params: error.config?.params,
data: error.config?.data,
},
});
const errorData: Shared.ErrorResponse = error.response?.data as Shared.ErrorResponse;
alert(`${errorData.error.code}: ${errorData.error.message}`);
return Promise.reject(error);
}
);
{
"data": null,
"error": {
"code": 400,
"message": "Bad Request"
},
"success": false
}
/* eslint-disable @typescript-eslint/no-unused-vars */
namespace Shared {
export interface ErrorResponse {
data: null;
error: {
code: number;
message: string;
};
success: boolean;
}
}
/* eslint-disable @typescript-eslint/no-unused-vars */
namespace Shared {
export interface ErrorResponse {
data: null;
error: {
code: number;
message: string;
};
success: boolean;
}
export interface messageResponse {
message: string;
}
export interface Pagenation {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
content: any;
pageable: Pageable;
totalPages: number;
totalElements: number;
last: boolean;
size: number;
number: number;
sort: Sort;
first: boolean;
numberOfElements: number;
empty: boolean;
}
interface Pageable {
sort: Sort;
pageNumber: number;
pageSize: number;
offset: number;
paged: boolean;
unpaged: boolean;
}
interface Sort {
sorted: boolean;
unsorted: boolean;
empty: boolean;
}
}
GET - https://jsonplaceholder.typicode.com/posts/{id}
와 POST - https://jsonplaceholder.typicode.com/posts
에서 요청을 보내는 경우 아래와 같이 요청 타입을 설정합니다.
제가 주로 사용하는 네이밍 방식입니다. namespace를 통해 요청 타입을 선언한 후 {메소드}{API 이름}{요쳥형태}
형식으로 네이밍합니다.declare namespace TestRequest {
export interface GetPostsParams {
id: number;
}
export interface GetPostsQuery {
userId: number;
}
export interface PostPostsBody {
title: string;
body: string;
userId: number;
}
}
{메소드}{API 이름}{설명}
형식으로 네이밍합니다.declare namespace TestResponse {
export interface GetPosts {
id: number;
title: string;
body: string;
userId: number;
}
}
apis/service
폴더에 저장합니다.
저는 각 함수에 대한 설명을 주석으로 달아 둡니다. 이 때 각 함수의 설명은 해당 함수의가 사용되는 기능을 설명하는 것이 아니라, 해당 함수가 사용하는 API의 설명을 달아야 합니다.import { API } from "../axios";
export const TestService = () => {
const url = "/posts";
/**
* 포스트 상세 조회 - id 별 조회
* @api-doc: https://jsonplaceholder.typicode.com/guide/
*/
const getPosts = async (
params: TestRequest.GetPostsParams,
{ userId }: TestRequest.GetPostsQuery,
) => {
const response = (await API.get(`${url}/${params.id}`, {
params: { userId },
})) as TestResponse.GetPosts;
return response;
};
/**
* 포스트 전체 조회
* @api-doc: https://jsonplaceholder.typicode.com/guide/
*/
const getAllPosts = async () => {
const response = (await API.get(`${url}`)) as TestResponse.GetPosts[];
return response;
};
/**
* 포스트 생성
* @api-doc: https://jsonplaceholder.typicode.com/guide/
*/
const postPosts = async (body: TestRequest.PostPostsBody) => {
const response = (await API.post(`${url}`, body)) as TestResponse.GetPosts;
return response;
};
return {
getPosts,
getAllPosts,
postPosts,
};
};
index.ts
파일을 작성합니다.export { TestService } from "./service/test";
app/test
폴더에 저장합니다."use client";
import { TestService } from "@/util/apis";
import { useEffect, useState } from "react";
export default function TestPage() {
const [posts, setPosts] = useState<TestResponse.GetPosts>();
const [getAllPosts, setGetAllPosts] = useState<TestResponse.GetPosts[]>();
const [postPosts, setPostPosts] = useState<TestResponse.GetPosts>();
useEffect(() => {
const fetchPosts = async () => { // 포스트 상세 조회
const response = await TestService().getPosts({ id: 1 }, { userId: 1 });
setPosts(response);
};
const fetchAllPosts = async () => { // 포스트 전체 조회
const response = await TestService().getAllPosts();
setGetAllPosts(response);
};
const postPosts = async () => { // 포스트 생성
const response = await TestService().postPosts({ title: "test123", body: "test123", userId: 1 });
setPostPosts(response);
};
fetchPosts();
fetchAllPosts();
postPosts();
}, []);
return (
<div>
<div>Test</div>
<div>{posts?.title}</div>
{getAllPosts?.map((post) => (
<div key={post.id}>{post.title}</div>
))}
<div>{postPosts?.title}</div>
</div>
);
}