route.ts로 api 생성, 이때 app 폴더 하위에 원하는 api 요청 경로를 폴더 구조로 구조화 할 수 있다.
예를 들어 app/api/videos/search 라면, BASE_URL/api/vidoes/search 라는 엔드포인트로 api 요청을 보내는 걸 해당 폴더 안에 route.ts로 정의하면 된다. 대략 아래와 같다.
export const GET = async (request: NextRequest) => {
try {
const queryParams = parseQueryParams(request.nextUrl.searchParams)
const { data } = await youtubeServerInstance.search.list({
part: ["snippet"],
type: ["video"],
regionCode: "KR",
maxResults: 20,
...queryParams
})
const mappedData = mappingResponse(data)
return Response.json(mappedData)
} catch {
return new Response(JSON.stringify({ message: "Internal Server Error" }), {
status: 500
})
}
}
const parseQueryParams = (
params: URLSearchParams
): GetSearchVideosListRequestParams => {
return {
q: params.get("q") ?? "",
order: (params.get("order") ?? "relevance") as SearchOrder,
pageToken: params.get("pageToken") ?? undefined
}
}
const mappingResponse = (
data: youtube_v3.Schema$SearchListResponse
): GetSearchVideosListResponse => {
const lists =
data?.items?.map(({ id, snippet }) => {
const publishedAt = snippet?.publishedAt ?? ""
return {
videoId: id?.videoId ?? "",
title: snippet?.title ?? "",
description: snippet?.description ?? "",
channelId: snippet?.channelId ?? "",
channelTitle: snippet?.channelTitle ?? "",
thumbnail: {
url: snippet?.thumbnails?.medium?.url ?? "",
width: snippet?.thumbnails?.medium?.width ?? undefined,
height: snippet?.thumbnails?.medium?.height ?? undefined
},
publishedAt,
publishedAtDisplayText: formatKoreanTextCompareDatesFromNow(publishedAt)
}
}) ?? []
return {
lists,
prevPageToken: data.prevPageToken ?? undefined,
nextPageToken: data.nextPageToken ?? undefined,
totalResults: data.pageInfo?.totalResults ?? 0
}
}
그리고 해당 api를 사용할 클라이언트 함수를 정의한다.
export const getSearchVideosList = async (
params: GetSearchVideosListRequestParams
): Promise<GetSearchVideosListResponse> => {
const queryParams = queryString.stringify(params)
const url = `${getSearchVideosListURL}?${queryParams}`
const response = await fetch(url)
return await response.json()
}
그리고 그걸 사용할 리액트 쿼리 훅을 만들어서 컴포넌트에서 사용한다.
export const useGetSearchVideosList = ({
q,
order,
initPageToken
}: Params): UseSuspenseInfiniteQueryResult<
InfiniteData<GetSearchVideosListResponse, Error>
> => {
return useSuspenseInfiniteQuery({
queryKey: ["search", q, order, getSearchVideosListURL],
queryFn: ({ pageParam = initPageToken }) => {
return getSearchVideosList({ q, order, pageToken: pageParam })
},
initialPageParam: initPageToken,
getNextPageParam: (lastPage) => lastPage.nextPageToken
})
}