Loading
Loading
블로그 개발 일지 #6 - API 연동
2024년 11월 21일
Axios를 사용하여 API를 연동하기 전에, 먼저 Axios의 instance
를 제작하였습니다. 이렇게 생성한 instance
는 반복되는 설정을 쉽게 재사용할 수 있도록 해줍니다.
// src/services/axios.ts import axios from "axios"; import { getCookie } from "cookies-next"; const instance = axios.create({ baseURL: process.env.NEXT_PUBLIC_BASE_URL, headers: { "Content-Type": "application/json", }, }); // 요청 인터셉터 설정 - 모든 요청에 Authorization 헤더에 accessToken을 포함 instance.interceptors.request.use((config) => { const token = getCookie("accessToken"); if (token) { config.headers = config.headers || {}; config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error)); export default instance;
Axios의 instance
를 사용함으로써 얻는 장점은 다음과 같습니다:
코드 중복 감소: 매 API 요청마다 반복해서 설정할 필요 없이, 공통된 설정을 한 번에 정의하여 재사용할 수 있습니다. 예를 들어, baseURL
이나 Content-Type
같은 기본 설정을 한 번에 지정할 수 있어 코드의 간결성과 유지보수가 용이합니다.
인증 헤더 자동 처리: 요청을 보낼 때마다 인증 토큰을 수동으로 추가하는 대신, 요청 인터셉터를 이용해 모든 요청에 Authorization
헤더를 자동으로 포함시킬 수 있습니다. 이로 인해 인증 관련 처리가 더 간편하고 실수를 줄일 수 있습니다.
위 코드에서 interceptors.request
를 사용하여 요청을 인터셉트하고 있습니다. 인터셉터는 요청이 서버로 전송되기 전에 이를 가로채서 추가 작업을 수행할 수 있도록 해줍니다. 여기서는 쿠키에서 accessToken
을 가져와 모든 요청의 Authorization
헤더에 추가하는 작업을 하고 있습니다. 이를 통해 사용자 인증이 필요한 API 요청이 매끄럽게 처리될 수 있습니다.
먼저 로그인과 회원가입 API를 연동하였습니다.
// src/services/auth.api.ts import instance from "./axios"; import { SignInForm, SignInResponse, SignUpForm } from "@/types/authType"; export const SignUp = async (formData: SignUpForm) => { return await instance.post(`/auth/signup`, formData); }; export const SignIn = async (formData: SignInForm): Promise<SignInResponse> => { return (await instance.post<SignInResponse>(`/auth/login`, formData)).data; };
Auth API는 회원가입과 로그인 기능을 제공합니다.
SignUp
): 사용자의 이메일, 이름, 비밀번호 등의 정보를 서버에 전달하여 새로운 사용자를 등록합니다.SignIn
): 사용자의 이메일과 비밀번호를 사용해 인증을 요청하고, 성공 시 accessToken
과 refreshToken
을 반환하여 이후 API 요청에 사용할 수 있도록 합니다.이를 통해 사용자 인증을 간편하게 처리하고, 보호된 리소스에 접근할 수 있도록 합니다.
유저 정보를 조회하고 수정하기 위해 User API를 추가로 연동하였습니다.
import getInstance from "./axios"; import { User } from "@/types/authType"; const instance = getInstance(); export const getUser = async (): Promise<User> => { return (await instance.get<User>("/users/me")).data; }; export const patchUser = async (id: string, data: patchUserType) => { return await instance.patch(`/users/${id}`, data); }; interface patchUserType { name?: string; isAdmin?: boolean; }
getUser
): 현재 로그인된 사용자의 정보를 조회합니다. 이 API를 통해 사용자 이름, 이메일 등의 정보를 얻을 수 있습니다.patchUser
): 특정 유저의 정보를 수정합니다. 사용자의 이름이나 관리자 권한(isAdmin
) 등을 업데이트할 수 있습니다.이러한 User API를 통해 사용자의 프로필 정보를 관리하고, 필요한 경우 수정할 수 있는 기능을 제공하였습니다.
다음으로 게시글 관련 API를 연동하였습니다.
// src/services/post.api.ts import instance from "./axios"; import { PostRequest } from "@/types/blogType"; export const getPostList = async ({ pageParam = 0 }) => { const response = await instance.get(`/posts`, { params: { offset: pageParam, limit: 10 } }); return response.data; }; export const getPost = async (title: string) => { return (await instance.get(`/posts/${title}`)).data; }; export const writePost = async ({ postData, userId }: { postData: PostRequest; userId: string }) => { return (await instance.post(`/posts`, { ...postData, userId })).data; };
getPostList
): 페이지네이션을 적용하여 특정 페이지의 게시글을 조회합니다. 한 번에 10개의 게시글을 가져오며, 더 이상 가져올 게시글이 없으면 마지막 페이지로 간주합니다.getPost
): 특정 게시글의 제목을 이용해 해당 게시글의 상세 정보를 가져옵니다.writePost
): 사용자가 새로운 게시글을 작성할 수 있도록 합니다. 작성된 게시글의 데이터와 사용자 ID를 서버에 전달하여 새로운 게시글을 생성합니다.이러한 구조를 통해 게시글 관련 기능들을 일관성 있게 구현하고, 재사용성을 높일 수 있었습니다.
게시글에 대한 댓글 기능을 제공하기 위해 댓글 관련 API를 연동하였습니다.
import instance from "./axios"; import { CommentRequest } from "@/types/blogType"; export const getComments = async (title: string) => { return (await instance.get(`/comments/${title}`)).data; }; export const writeComment = async (body: CommentRequest) => { return (await instance.post(`/comments`, body)).data; }; export const editComment = async (id: number, content: string) => { return (await instance.patch(`/comments/${id}`, { content })).data; }; export const deleteComment = async (id: number) => { return (await instance.delete(`/comments/${id}`)).data; };
getComments
): 특정 게시글의 제목을 이용해 해당 게시글에 달린 모든 댓글을 조회합니다.writeComment
): 사용자가 새로운 댓글을 작성할 수 있도록 합니다.editComment
): 특정 댓글의 내용을 수정할 수 있도록 합니다.deleteComment
): 특정 댓글을 삭제할 수 있도록 합니다.이러한 댓글 관련 기능들을 통해 게시글에 대한 사용자 상호작용을 더욱 풍부하게 만들 수 있었습니다.
유저의 로그인 상태 등과 같은 전역 상태 관리는 Zustand를 활용하여 관리하였습니다.
// src/stores/UserStore.ts import { getCookie } from "cookies-next"; import { create } from "zustand"; const useUserStore = create((set) => ({ isLoggedIn: !!getCookie("accessToken"), setIsLoggedIn: (token) => set({ isLoggedIn: token }), })); export default useUserStore;
일관된 인증 상태 유지: 유저의 로그인 상태를 전역에서 관리함으로써, 애플리케이션 전반에서 일관된 인증 상태를 유지할 수 있습니다. 이를 통해 페이지 이동이나 새로고침 이후에도 로그인 상태를 쉽게 확인하고 유지할 수 있습니다.
코드의 간결성: 개별 컴포넌트에서 로그인 여부를 확인하거나 인증 정보를 처리하는 코드를 반복할 필요 없이, 전역 상태에서 이를 통합적으로 관리할 수 있습니다. 이를 통해 코드의 중복을 줄이고 유지보수성을 높일 수 있습니다.
효율적인 상태 접근: 유저의 로그인 상태와 같은 중요한 정보를 여러 컴포넌트에서 사용할 때, 전역 상태를 통해 간편하게 접근할 수 있습니다. 이를 통해 상태 전달을 위한 props drilling을 피할 수 있어, 코드의 복잡도를 줄일 수 있습니다.
이렇게 Zustand를 활용하여 유저의 로그인 상태와 같은 전역 상태를 관리함으로써, 코드의 간결성과 유지보수성을 높이고, 상태 관리의 복잡도를 줄일 수 있었습니다.
로그인 상태에 따라 특정 페이지로의 접근을 제어하기 위해 Next.js의 미들웨어를 사용하여 리다이렉션 처리를 구현하였습니다.
// src/middleware.ts import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; const authMap = new Map([[ /^\/(login|signup)/, "/" ]]); const guestMap = new Map([[ /^\/(get-started|create-team|join-team)/, "/login" ]]); export const middleware = (request: NextRequest) => { const { pathname } = request.nextUrl; const accessToken = request.cookies.get("accessToken"); const map = accessToken ? authMap : guestMap; for (const [regex, redirectUrl] of map.entries()) { if (regex.test(pathname)) { return NextResponse.redirect(new URL(redirectUrl, request.url)); } } return NextResponse.next(); }; export const config = { matcher: ["/((?!api|_next/static|_next/image|favicon.ico|images|icons).*)"], };
로그인 상태에 따른 접근 제어: 사용자가 로그인 상태인지 아닌지에 따라 특정 경로에 대한 접근을 제어합니다. 로그인 상태일 경우, /login
이나 /signup
페이지로 접근하려고 하면 홈 페이지(/
)로 리다이렉션됩니다. 반대로, 비로그인 상태일 경우 팀 관련 페이지(/get-started
, /create-team
, /join-team
)로 접근하려고 하면 /login
페이지로 리다이렉션됩니다.
간편한 접근 제어: 미들웨어를 통해 요청을 가로채고 필요한 경우 리다이렉션을 처리함으로써, 각 페이지에서 개별적으로 접근 권한을 검사할 필요 없이 전체적인 접근 제어를 일관성 있게 유지할 수 있습니다. 이를 통해 코드의 중복을 줄이고 유지보수성을 높일 수 있습니다.
이러한 미들웨어 설정을 통해 애플리케이션의 보안을 강화하고 사용자 경험을 개선할 수 있었습니다.