Accounts API
Route.ts
Hono와 라우터 설정
import { Hono } from "hono";
import accounts from "./accounts";
const app = new Hono().basePath("/api");
- basePath: 모든 라우터의 기본 경로를 /api로 설정한다. 예를들어 accounts라우터는 /api/accounts로 접근 가능하다. 이를통해 일관된 경로 구조를 유지할 수 있다.
라우터 파일 분리 및 통합
import accounts from "./accounts";
const routes = app.route("/accounts", accounts);
- 모듈화된 라우터: accounts라우터를 별도의 파일로 분리했다. 라우터 파일에는 /accounts경로와 관련된 모든 API로직이 포함된다. 이렇게 모듈화하면 각 경로의 기능을 독립적으로 관리할 수 있어 유지보수와 확장이 용이하다.
전역에러 처리
app.onError((err, c) => {
if (err instanceof HTTPException) {
return err.getResponse();
}
return c.json({ error: "Internal server error" }, 500);
});
- 에러 처리 : onError는 애플리케이션에서 발생하는 모든 에러를 전역적으로 처리한다.
- HTTPException: Hono에서 제공하는 에러 클래스로 HTTP상태코드와 함께 클라이언트에 적절한 응답을 보낸다.
- 예상치 못한 에러 : 다른 에러는 500 상태 코드와 함께 internal server error메시지를 반환한다.
- 이방식은 API전체에서 일관성 있는 에러 응답을 보장하고 코드 중복을 줄일 수 있다.
Vercel 과의 통합
import { handle } from "hono/vercel";
export const GET = handle(app);
export const POST = handle(app);
- Vercel핸들러에서 Hono앱을 Vercel의 서버리스 환경에서 실행할 수 있도록 설정한다.
- handle함수는 Hono앱을 요청 처리 하는 방식에 맞게 변환한다.
- Get과 Post요청을 각각 지원하도록 설정되어 있다.
- 이 설정은 Vercel베포를 간소화하며 엣지 환경에서 빠르게 동작하도록 최적화한다.
동작원리
1. 클라이언트가 /api/accounts경로로 요청을 보낸다.
2. 요청이 accounts라우터로 전달된다.
3. 에러가 발생하면 HTTPException일 경우, 정의된 에러 응답이 반환된다. 기타의 에러의 경우 interval servererror와 함께 500상태 코드로 처리된다.
Account.ts
Hono 라우터 설정
const app = new Hono().get("/", clerkMiddleware(), async (c) => {
const auth = getAuth(c);
- 라우터 등록 : Hono().get()은 GET요청을 처리하는 라우트를 등록한다.
여기서 경로는 / 이다.
- 미들웨어 등록: ClerkMiddleware()는 인증 미들웨어로 인증되지 않은 요청을 자동으로 차단한다.
- getAuth(c): 현재 요청의 인증 정보를 가져온다.
예: auth.userID -> 요청한 사용자의 고유 ID
인증 실패 처리
if (!auth?.userId) {
throw new HTTPException(401, {
res: c.json({ error: "Unauthorized" }, 401),
});
}
- 인증 정보가 없거나, auth.userId가 없는 경우 401Unauthorized에러를 반환한다.
- HTTPException은 hono에서 제공하는 HTTP에러 객체로, 응답 코드와 메시지를 포함한다.
데이터베이스 쿼리
const data = await db
.select({
id: accounts.id,
name: accounts.name,
})
.from(accounts)
.where(eq(accounts.userId, auth.userId));
- db.select():데이터베이스에서 데이터를 조회하여 id와 name열만 선택해 반환한다.
- from(accounts): 열만 선택해 반환한다.
- where(eq(accounts.userId, auth.userId)): 테이블의 userId가 인증된 사용자의 userId와 동일한 레코드를 필터링한다.
- 결과반환 : 반환된 데이터는 data객체에 저장된다. 예: [{ id: "123", name: "John Doe" }]
응답반환
return c.json({ data });
조회된 데이터는 JSON형식으로 반환된다.
React Query와 Provider설정
이 코드는 next.jsdml app/providers.tsx 파일 구조를 기반으로 작성된 React Query Provider를 설정하는 코드이다. @tanstack/react-query를 활용해서 클라이언트와 서버 간의 효율적인 데이터 관리를 가능하게 한다. 이를 통해 데이터 요청, 캐싱, 동기화, 그리고 서버 상태 관리가 훨씬 간편해진다.
주요기능
reactQuery와 QueryClientProvider
- QueryClientProvuder는 react query의 핵심 컴포넌트로 앱의 전역 상태 관리를 담당한다.
- 이 provider는 react query의 queryclient를 받아 애플리케이션 전체에서 데이터 캐싱과 동기화를 제공한다.
QueryClient 생성
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1분 동안 데이터를 신선하다고 간주
},
},
});
}
- makeQueryClient함수는 새로운 queryclien객체를 생성한다.
- staleTime옵션
- 데이터를 fresh하다고 간주하는 시간을 설정한다.
- 60초로 설정되어 있어 데이터가 캐시된 후 1분 동안 재요청 없이 사용할 수 있다.
- SSR에서 초기 데이터를 다시 가져오는 불필요한 요청을 방지한다.
클라이언트와 서버 환경에 따른 QueryClient관리
let browserQueryClient: QueryClient | undefined = undefined;
function getQueryClient() {
if (isServer) {
return makeQueryClient(); // 서버에서는 항상 새로운 QueryClient 생성
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient(); // 브라우저에서는 QueryClient 재사용
return browserQueryClient;
}
}
서버환경
- 서버에서는 매 요청마다 새로운 QueryClient를 생성한다.
- 이는 서버가 상태를 재사용하지 않고 각 요청마다 독립적인 처리를 보장하기 위함이다.
브라우저 환경
- 브라우저에서는 queryClient를 한번만 생성하여 재사용한다.
- React의 suspense가 초기 렌더링 중 다시 렌더링을 트리거해도, 새로운 queryClient가 생성되지 않도록 방지한다.
Providers컴포넌트
export default function Providers({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient();
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
}
providers 컴포넌트
- 이 컴포넌트는 queryClientProvider를 사용하여 React Query를 애플리케이션의 컨텍스트로 제공한다.
- 자식 컴포넌트들은 모두 queryClient를 활용할 수 있게된다.
children속성
- Providers컴포넌트의 자식 컴포넌트는 children으로 전달된다.
- 이 구조를 통해 React Query의 데이터 관리 기능을 전체 애플리케이션에서 사용할 수 있다.
사용처
앱 전체 데이터 관리
- provider는 Next.js의 lauout.tsx나 최상위 컴포넌트에서 사용되어, 애플리케이션 전체에 React Qeury의 기능을 제공한다.
클라이언트와 서버 데이터 연동
- SSR및 CSR동시 지원 : next.js의 getserversideProps나 useQuery를 통해 서버와 클라이언트에서 데이터를 가져온다.
- React Query의 캐싱덕분에 서버에서 가져온 데이터를 클라이언트에서 효율적으로 재사용할 수 있다.
주요 특징
1. 서버와 클라이언트 환경에 따라 최적화된 QueryClient관리
- 서버에서는 요청마다 새로운 queryclient를 생헝한다.
- 브라우저에서는 queryclient를 재사용하여 불필요한 리소스 낭비를 방지한다.
2. suspense와의 호환성
- react의 suspense가 초기 렌더링에서 중단되더라도 동일한 queryclient를 재사용하여 데이터 요청 문제를 방지한다.
3. 설정 가능한 Staletiem
- staleTiem을 설정해 데이터 캐싱 기간을 조절 가능하다.
4. 확장성과 모듈화
- providers컴포넌트를 사용하여 애플리케이션의 전역 데이터 관리가 간소화된다.
useGetAccounts 훅
useGetAccounts는 React query를 활용하여 서버에서 계정 데이터를 가져오는 Custom hook이다. 이 훅은 api요청과 관련된 로직을 캡슐화 하여 React컴포넌트에서 쉽게 데이터를 가져오고 상태를 관리할 수 있도록 돕는다.
주요 역할
- API호출관리: 서버의 GET/ Accounts API를 호출하여 데이터를 가져온다.
- React Query를 통한 상태 관리 : 데이터를 캐싱하고 로딩/에러 상태를 관리한다.
- 코드 재사용성 제공: 동일한 API호출 로직을 여러 컴포넌트에서 재사용할 수 있도록 Hook형태로 제공된다.
useQuery사용
const query = useQuery({
queryKey: ["account"],
queryFn: async () => {
const response = await client.api.accounts.$get();
if (!response.ok) {
throw new Error("Failed to fetch accounts");
}
const { data } = await response.json();
return data;
},
});
- useQuery : react query의 핵심 함수로, 서버에서 데이터를 가져오고 상태를 관리한다.
- 주요 옵션으로 queyKey는 캐싱과 상태 관리를 위한 키로 여기서는 ["account"]를 사용한다.
- queryFn: 데이터를 가져오는 비동기 함수로 API호출 로직이 포함된다.
QueryFn 동작
const response = await client.api.accounts.$get();
if (!response.ok) {
throw new Error("Failed to fetch accounts");
}
const { data } = await response.json();
return data;
API호출
- client.api.accounts.$get()는 GET/accounts엔드포인트에 요청을 보낸다.
- 요청이 실패하면 Error를 throw하여 React Query가 이를 에러 상태로 처리하도록 하게 한다.
응답처리
- 성공적인 응답(response.ok)이 확이되면 JSON데이터를 파싱하고, data만 반환한다.
- 이 반환된 데이터는 React Query가 캐싱 및 상태 관리에 활용된다.
hook 반환값
return query;
- 이 hook은 useQuery의 반환값을 그대로 반환한다.
- 이 반환값에는 다음 상태들이 포함된다.
data, isLoading, isError, error
장점
1. 코드 간소화
- API호출 로직이 캡슐화되어, 컴포넌트에서 데이터 로직이 제거된다.
- 컴포넌트는 데이터 상태를 신경 쓰는데 집중할 수 있다.
2. 재사용성
- 동일한 API호출이 필요한 곳에서 useGetAccounts를 재사용할 수 있다.
3. React Query의 강력한 기능 활용
- 캐싱, 자동 리패치, 로딩/에러 상태 관리 등 React Query의 모든 기능을 쉽게 적용가능하다.
4. 유지 보수에 용이하다.
- API로직 변경 시 Hook 내부만 수정하면 된다.
- 컴포넌트에 흩어진 호출 코드를 관리할 필요가 없다.
hono.ts
import { hc } from "hono/client";
import { AppType } from "@/app/api/[[...route]]/route";
export const client = hc<AppType>(process.env.NEXT_PUBLIC_APP_URL!);
이 코드는 Hono프레임워크를 클라이언트 측에서 사용하기 위한 설정 파일이다. API클라이언트를 생성해서 서버와의 통신을 간편하게 수행할 수 있도록 돕는다.
주요역할
1. Hono 클라이언트 생성 : 서버의 API를 클라이언트 측에서 호출할 수 있도록 도와준다.
2. 타입 안전성 제공: 서버에서 정의한 API 타입을 기반으로 클라이언트에서 타입 안전성을 확보한다.
3. 환경 변수 기반 URL설정 : 서버 URL을 동적으로 설정하여 개발 환경과 프로덕션 환경에서 동일한 코드를 사용할 수 있다.
동작원리
이 설정 파일은 클라이언트 측에서 hono로 작성된 API를 호출할 때 사용된다.
1. 서버의 API경로와 메서드 정보가 AppType에 정의되어 있다.
2. 이 정보를 기반으로 타입 안전한 클라이언트를 생성한다.
3. 클라리언트는 client.api.<endpoint> 형태로 API호출을 수행한다.