Skip to content

Commit cb91d39

Browse files
authored
Merge pull request #67 from SOPT-all/init/ky-instance/#66
[feat/#66] ky 인스턴스 래퍼 함수 구현 및 API 구조 개선
2 parents 58c79d0 + 448ba94 commit cb91d39

File tree

9 files changed

+96
-6
lines changed

9 files changed

+96
-6
lines changed
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@ export const apiClient = ky.create({
1010
},
1111
hooks: {
1212
beforeRequest: [
13-
(request) => {
14-
// TODO: 나중에 공통으로 넣을 헤더가 생기면 여기에서 처리
15-
void request;
13+
() => {
14+
// TODO: 공통으로 넣을 헤더가 생기면 여기에서 처리
1615
},
1716
],
1817
afterResponse: [
1918
async (_request, _options, response) => {
20-
// TODO: 공통 에러 처리가 필요해지면 여기에서 처리
19+
// 성공/실패 응답은 request 함수에서 처리
2120
return response;
2221
},
2322
],
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const API_ENDPOINTS = {
2+
AUTHORS: {
3+
AUTHOR_INFO: (authorId: number) => `/authors/${authorId}`,
4+
AUTHOR_LIKES: (authorId: number) => `/authors/${authorId}/likes`,
5+
},
6+
PRODUCTS: {
7+
PRODUCT_INFO: (productId: number) => `/products/${productId}`,
8+
PRODUCT_REVIEW: (productId: number) => `/products/${productId}/reviews`,
9+
PRODUCT_LIKES: (productId: number) => `/products/${productId}/likes`,
10+
},
11+
} as const;

src/apis/request.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// ky 인스턴스 래퍼함수
2+
3+
import { HTTPError } from "ky";
4+
import { apiClient } from "@/apis/api-client";
5+
6+
export const HTTPMethod = {
7+
GET: "GET",
8+
POST: "POST",
9+
PUT: "PUT",
10+
DELETE: "DELETE",
11+
PATCH: "PATCH",
12+
} as const;
13+
14+
export type HTTPMethodType = (typeof HTTPMethod)[keyof typeof HTTPMethod];
15+
type QueryValue = string | number | boolean;
16+
17+
export interface RequestConfig {
18+
method: HTTPMethodType;
19+
url: string;
20+
query?: Record<string, QueryValue>;
21+
body?: unknown;
22+
}
23+
24+
// Success response: { code, message, data }
25+
interface SuccessResponse<T> {
26+
code: string;
27+
message: string;
28+
data: T;
29+
}
30+
31+
// Error response: { code, message, messageDetail }
32+
interface ErrorResponse {
33+
code: string;
34+
message: string;
35+
messageDetail: string | null;
36+
}
37+
38+
// ky 인스턴스 래퍼 함수
39+
export const request = async <T>(config: RequestConfig): Promise<T> => {
40+
// T: 응답 성공 시 받는 데이터의 타입
41+
const { method, url, query, body } = config;
42+
43+
try {
44+
const response = await apiClient(url, {
45+
method,
46+
searchParams: query as Record<string, string | number | boolean>,
47+
json: method !== HTTPMethod.GET ? body : undefined, // 자동으로 헤더에 Content-Type: application/json 추가
48+
}).json<SuccessResponse<T>>();
49+
50+
return response.data; // 성공 시 data 필드만 추출해서 반환
51+
} catch (error: unknown) {
52+
if (error instanceof HTTPError) {
53+
const errorData = await error.response
54+
.json<ErrorResponse>()
55+
.catch(() => null);
56+
57+
const errorMessage = errorData?.message || "에러 메시지 정의 X";
58+
const errorCode = errorData?.code || "UNKNOWN_ERROR";
59+
const errorMessageDetail = errorData?.messageDetail || "UNKNOWN_ERROR";
60+
61+
if (import.meta.env.DEV) {
62+
console.error(`[${errorCode}] ${url}`);
63+
console.error("errorMessage: ", errorMessage);
64+
console.error("errorMessageDetail", errorMessageDetail);
65+
}
66+
67+
throw error;
68+
}
69+
70+
if (import.meta.env.DEV) {
71+
console.error("네트워크 에러");
72+
}
73+
74+
throw error;
75+
}
76+
};

src/apis/types/author.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// 요청, 응답 타입을 정의합니다

src/apis/types/product.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// 요청, 응답 타입을 정의합니다

src/main.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { StrictMode } from "react";
22
import { createRoot } from "react-dom/client";
33
import App from "@/App.tsx";
4-
import QueryProvider from "./shared/apis/query-client.tsx";
4+
import QueryProvider from "./apis/query-client.tsx";
55
import "./shared/styles/global.css.ts";
66

77
createRoot(document.getElementById("root")!).render(

tsconfig.app.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"@routes/*": ["./src/routes/*"],
2626
"@img/*": ["./src/assets/img/*"],
2727
"@svg/*": ["./src/assets/svg/*"],
28-
"@styles/*": ["./src/shared/styles/*"]
28+
"@styles/*": ["./src/shared/styles/*"],
29+
"@apis/*": ["./src/apis/*"]
2930
},
3031

3132
/* Linting */

vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default defineConfig({
3434
"@svg": path.resolve(dirname, "./src/assets/svg"),
3535
"@img": path.resolve(dirname, "./src/assets/img"),
3636
"@styles": path.resolve(dirname, "./src/shared/styles"),
37+
"@apis": path.resolve(dirname, "./src/apis"),
3738
},
3839
},
3940
test: {

0 commit comments

Comments
 (0)