오늘의 명언
“ 당신은 소프트웨어 품질을 추구할 수도 있고, 포인터 연산을 할 수도 있다. 그러나 두 개를 동시에 할 수는 없다. ”
-
베르트랑 마이어 (Bertrand Meyer)
300x250
화창하지만 황사로 인해 미세먼지 많은 날씨
다들 마스크 쓰며 열일하셨나요?
이번 포스팅에서는 Nuxt와 React에서 서버로 HTTP 통신을 위해 사용되는
Axios에 대해서 TypeScript 적용하여 인스턴스 생성 및 사용하는 방법을
설명해 볼까 합니다.
23.07월 기준으로 포스팅 내용이 수정되었습니다.
Axios 설치하기
npm install axios@1.4.0 // 23.07월 기준 1.4.0 최신버전입니다.
만약에 Nuxt2에서 axios 1.4.0버전을 사용한다면 Es Module 에러가 나오는데
nuxt.config.js에 아래와 같이 추가해주면됩니다.
// nuxt.config.js
build: {
transpile: [
'axios'
],
}
Axios 인스턴스 타입 확인
node_modules에 axios폴더 index.d.ts 파일을 확인해 보면 Axios관련 클래스와 메서드 그리고 타입, 인터페이스 등을 확인해 볼 수 있습니다. 그래서 정의된 Axios 타입을 바탕으로 개인 및 회사 서버 데이터와 맞게 구성합니다. 아래는 Axios index.d.ts에 대한 클래스 구조와 정의된 타입 인터페이스 내용을 간략하여 발췌했습니다. 참고하시면 됩니다.
//node_modules/axios/index.d.ts
// Axios 클래스
declare class Axios {
constructor(config?: axios.AxiosRequestConfig);
defaults: axios.AxiosDefaults;
interceptors: {
request: axios.AxiosInterceptorManager<axios.InternalAxiosRequestConfig>;
response: axios.AxiosInterceptorManager<axios.AxiosResponse>;
};
getUri(config?: axios.AxiosRequestConfig): string;
request<T = any, R = axios.AxiosResponse<T>, D = any>(config: axios.AxiosRequestConfig<D>): Promise<R>;
get<T = any, R = axios.AxiosResponse<T>, D = any>(url: string, config?: axios.AxiosRequestConfig<D>): Promise<R>;
delete<T = any, R = axios.AxiosResponse<T>, D = any>(url: string, config?: axios.AxiosRequestConfig<D>): Promise<R>;
head<T = any, R = axios.AxiosResponse<T>, D = any>(url: string, config?: axios.AxiosRequestConfig<D>): Promise<R>;
options<T = any, R = axios.AxiosResponse<T>, D = any>(url: string, config?: axios.AxiosRequestConfig<D>): Promise<R>;
post<T = any, R = axios.AxiosResponse<T>, D = any>(url: string, data?: D, config?: axios.AxiosRequestConfig<D>): Promise<R>;
put<T = any, R = axios.AxiosResponse<T>, D = any>(url: string, data?: D, config?: axios.AxiosRequestConfig<D>): Promise<R>;
patch<T = any, R = axios.AxiosResponse<T>, D = any>(url: string, data?: D, config?: axios.AxiosRequestConfig<D>): Promise<R>;
postForm<T = any, R = axios.AxiosResponse<T>, D = any>(url: string, data?: D, config?: axios.AxiosRequestConfig<D>): Promise<R>;
putForm<T = any, R = axios.AxiosResponse<T>, D = any>(url: string, data?: D, config?: axios.AxiosRequestConfig<D>): Promise<R>;
patchForm<T = any, R = axios.AxiosResponse<T>, D = any>(url: string, data?: D, config?: axios.AxiosRequestConfig<D>): Promise<R>;
}
...
// Axios 응답 타입 인터페이스
export interface AxiosResponse<T = any, D = any> {
data: T;
status: number;
statusText: string;
headers: RawAxiosResponseHeaders | AxiosResponseHeaders;
config: InternalAxiosRequestConfig<D>;
request?: any;
}
// HTTP 상태 코드
export enum HttpStatusCode {
Continue = 100,
SwitchingProtocols = 101,
Processing = 102,
EarlyHints = 103,
Ok = 200,
Created = 201,
Accepted = 202,
NonAuthoritativeInformation = 203,
NoContent = 204,
ResetContent = 205,
PartialContent = 206,
MultiStatus = 207,
AlreadyReported = 208,
ImUsed = 226,
MultipleChoices = 300,
MovedPermanently = 301,
Found = 302,
SeeOther = 303,
NotModified = 304,
UseProxy = 305,
Unused = 306,
TemporaryRedirect = 307,
PermanentRedirect = 308,
BadRequest = 400,
Unauthorized = 401,
PaymentRequired = 402,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
NotAcceptable = 406,
ProxyAuthenticationRequired = 407,
RequestTimeout = 408,
Conflict = 409,
Gone = 410,
LengthRequired = 411,
PreconditionFailed = 412,
PayloadTooLarge = 413,
UriTooLong = 414,
UnsupportedMediaType = 415,
RangeNotSatisfiable = 416,
ExpectationFailed = 417,
ImATeapot = 418,
MisdirectedRequest = 421,
UnprocessableEntity = 422,
Locked = 423,
FailedDependency = 424,
TooEarly = 425,
UpgradeRequired = 426,
PreconditionRequired = 428,
TooManyRequests = 429,
RequestHeaderFieldsTooLarge = 431,
UnavailableForLegalReasons = 451,
InternalServerError = 500,
NotImplemented = 501,
BadGateway = 502,
ServiceUnavailable = 503,
GatewayTimeout = 504,
HttpVersionNotSupported = 505,
VariantAlsoNegotiates = 506,
InsufficientStorage = 507,
LoopDetected = 508,
NotExtended = 510,
NetworkAuthenticationRequired = 511,
}
Axios 인스턴스 생성
위에서 Axios 클래스와 타입들이 어떤 것이 있는지 한번 확인해 보셨으면, 본인이 어떻게 구현할지 생각하고 필요한 것만 추출해서 사용해 봅니다. 아래는 Axios 클래스를 타입을 지정하여 Axios 인스턴스를 생성합니다. 그리고 create 메서드에 속성으로 dataURL , header 등 여러 속성이 있는데 필요한 속성이 있으면 추가로 작성하시면 됩니다. 그리고 image나 영상 같은 거를 formData로 보낼 때에는 multipart/form-data를 메서드 정의할 때 header에 작성하면 됩니다.
// api/index.ts
import axios, {Axios, AxiosRequestConfig} from 'axios'
const client: Axios = axios.create({
baseURL: 'http://localhost:3000', // 프론트 URL
headers: {
'Content-Type': 'application/json',
},
});
API Server Response 데이터 구조
프론트 ( React, Vue, Next, Nuxt )에서 서버로 Axios 요청을 할 때 응답받는 데이터 구조를 한번 구현해 보겠습니다. 응답 데이터 구조는 개인 및 회사 서버에서 Response로 내려주는 Key와 Value에 대한 타입정의를 해주면 됩니다. 아래 코드를 보면 이해하기 편하실겁니다. 본인 서버에서 정한 데이터와 에러코드, 상태코드등 데이터에 대해서 타입을 정의합니다.
interface/response.ts를 아래와 같이 작성합니다.
// types/commonResponse.ts
import axios, {AxiosResponse,} from 'axios'
/** Axios Response 데이터 형식
* config : 요청에 대한 axios 구성 설정
* data 서버가 제공한 응답 데이터
* headers : 헤더 정보
* request : 요청
* status : 응답 HTTP 상태 코드
* statusText : 응답 HTTP 상태 메시지
*/
// 본인 서버에서 내려주는 응답 구조
interface APIResponse<T> {
statusCode: number // 상태코드 (보인 서버상태코드)
errorCode: number // 에러코드 (본인 서버에러코드)
message: string // 메시지
result: T // 데이터 내용
timestamp: Date // 시간
}
Axios 메서드 타입 정의
위와 같이 서버 Response에 대해서 타입 인터페이스 작업이 끝났다면, api/index.ts 파일로 돌아와 axios client 객체 뒤에 아래와 같이 메서드를 작성합니다.
api/index.ts를 아래와 같이 작성합니다.
import axios, { AxiosRequestConfig } from 'axios' // 추가
import { APIResponse } from '@/interface/response'
// axios 인스턴스 생성
const client: Axios = axios.create({
baseURL: 'http://localhost:3000',
headers: {
'Content-Type': 'application/json',
}
})
//TODO: GET 메서드
export const getData = async <T>(url: string, config?: AxiosRequestConfig): Promise<APIResponse<T>> => {
try {
const response = await client.get<APIResponse<T>>(url, config);
return response.data;
} catch (error) {
throw new Error(error.message);
}
};
//TODO: POST 메서드
export const postData = async <T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<APIResponse<T>> => {
try {
const response = await client.post<APIResponse<T>>(url, data, config);
return response.data;
} catch (error) {
throw new Error(error.message);
}
};
//TODO: PUT 메서드
export const putData = async <T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<APIResponse<T>> => {
try {
const response = await client.put<APIResponse<T>>(url, data, config);
return response.data;
} catch (error) {
throw new Error(error.message);
}
};
//TODO: Delete 메서드
export const deleteData = async <T>(url: string, config?: AxiosRequestConfig): Promise<APIResponse<T>> => {
try {
const response = await client.delete<APIResponse<T>>(url, config);
return response.data;
} catch (error) {
throw new Error(error.message);
}
};
Axios 메서드 사용 방법
위에 커스텀하게 Response를 정의하여 GET, POST, PUT, DELETE 메서드를 구현해 보았습니다. 이제 아래에서 간단하게 사용하는 방법에 대해 설명하겠습니다.
- 사용자 조회
// userService.ts
import {getData,postData,putData,deleteData} from '@/api'
// User 데이터 티입
interface GetUser {
id: number;
name: string;
}
// User 조회 -> GET 요청
const getUser = async (): Promise<GetUser> => {
try {
const response = await getData<GetUser>('/users/1')
const { name, id } = response.result
console.log(name, id)
return response.result
} catch (error) {
console.error(error)
throw new Error('Failed to get user')
}
}
- 사용자 생성
interface User {
name: string;
email: string;
}
// User 생성 -> POST 요청
const createUser = async (newUser: User): Promise<User> => {
try {
const response = await postData<User>('/users', newUser);
const createdUser = response.result;
console.log(createdUser);
return createdUser;
} catch (error) {
console.error(error);
throw new Error('Failed to create user');
}
};
- 이미지 보내기
const ImageUpload = async (data: Object) => {
try {
const formData = new FormData();
const values = Object.values(data);
Object.keys(data).forEach((key, index) =>
formData.append(key, values[index])
);
const response = await postData('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data', // 헤더 설정
},
});
console.log(response.result);
} catch (error) {
console.error(error);
}
};
정리
interface APIResponse<T> {
statusCode: number
errorCode: number
message: string
result: T
timestamp: Date
}
// TODO: Axios 라이브러리에서 import한 Response 인터페이스
interface AxiosResponse<T = any, D = any> {
data: T
status: number
statusText: string
headers: RawAxiosResponseHeaders | AxiosResponseHeaders
config: InternalAxiosRequestConfig<D>
request?: any
}
//TODO: 사용예제 -> User Response 타입을 삽입
const response = await getData<GetUser>('/users/1')
본인 서버 API Response와 AxiosResponse에 대해서 다시 정리해보자면 APIResponse 인터페이스 내용은 서버에서 내려주는 Key와 Value에 대한 타입정의입니다. 위에 코드 보면 AxiosResponse는 import 해서 사용하는데 따로 발췌해서 보여드렸습니다. 그래서 설명하면, 제네릭으로 data: T 부분에 APIResponse가 들어가게 됩니다. 예를 들어 GET 요청 후 데이터를 확인하려면 아래처럼 데이터를 확인할 수 있습니다.
export const getData = async <T>(
url: string,
config?: AxiosRequestConfig
): Promise<APIResponse<T>> => {
try {
const response = await client.get<APIResponse<T>>(url, config)
return response.data
} catch (error) {
throw new Error(error.message)
}
}
//TODO: 예제
interface User {
id: number
name: string
}
const response = await getData<User>('/users/1')
//TODO: response.data -> AxiosResponse 데이터구조
//TODO: response.data.result -> APIResponse 데이터 구조 (id,name)
Axios 정리하다 보니까 너무 예전 버전을 사용하고 있더라구요. 또 제가 이전에 작성한 거 보니 인스턴스에 대해서 따로 타입을 지정할 필요가 없었습니다. Axios 라이브러리 클래스에 다 정의되어있다 보니까 그거를 토대로 서버 측 데이터 타입만 정해서 구현하니 깔끔하게 작성할 수 있었습니다. 다음 중으로 interceptors 부분도 업데이트해서 수정된 내용으로 올려보겠습니다 (23.07 기준)
[Nuxt] Axios interceptors 사용하기
반응형
잘못된 내용이 있으면 댓글 부탁드립니다. 감사합니다.