React에서 tRPC로
Nx Plugin for AWS는 tRPC API를 React 웹사이트와 빠르게 통합할 수 있는 생성기를 제공합니다. AWS IAM 및 Cognito 인증 지원과 적절한 오류 처리를 포함하여 tRPC 백엔드에 연결하는 데 필요한 모든 구성을 설정합니다. 이 통합은 프론트엔드와 tRPC 백엔드 간의 완전한 엔드투엔드 타입 안전성을 제공합니다.
사전 요구 사항
섹션 제목: “사전 요구 사항”이 생성기를 사용하기 전에 React 애플리케이션에 다음이 있어야 합니다:
- 애플리케이션을 렌더링하는
main.tsx파일 - tRPC 프로바이더가 자동으로 주입될
<App/>JSX 요소 - 작동하는 tRPC API (tRPC API 생성기를 사용하여 생성됨)
- Cognito 또는 IAM 인증을 사용하는 API에 연결하는 경우
ts#website#auth생성기를 통해 추가된 Cognito Auth
필요한 main.tsx 구조 예시
import { StrictMode } from 'react';import * as ReactDOM from 'react-dom/client';import App from './app/app';
const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement,);root.render( <StrictMode> <App /> </StrictMode>,);사용법
섹션 제목: “사용법”생성기 실행
섹션 제목: “생성기 실행”pnpm nx g @aws/nx-plugin:connectionyarn nx g @aws/nx-plugin:connectionnpx nx g @aws/nx-plugin:connectionbunx nx g @aws/nx-plugin:connection- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - connection - 필수 매개변수 입력
- 클릭
Generate
| 매개변수 | 타입 | 기본값 | 설명 |
|---|---|---|---|
| sourceProject 필수 | string | - | 소스 프로젝트 |
| targetProject 필수 | string | - | 연결할 대상 프로젝트 |
| sourceComponent | string | - | 연결을 시작할 소스 컴포넌트 (컴포넌트 이름, 소스 프로젝트 루트 기준 상대 경로, 또는 generator id). 프로젝트를 소스로 명시적으로 선택하려면 '.'을 사용하세요. |
| targetComponent | string | - | 연결할 대상 컴포넌트 (컴포넌트 이름, 대상 프로젝트 루트 기준 상대 경로, 또는 generator id). 프로젝트를 대상으로 명시적으로 선택하려면 '.'을 사용하세요. |
| preferInstallDependencies | boolean | true | 생성기 실행 후 의존성 설치를 선호할지 여부입니다. 여러 생성기를 일괄 처리할 때 설치를 연기하려면 false로 설정하세요 (후속 생성기가 Nx 프로젝트 그래프를 계산할 수 있도록 필요한 경우 설치는 여전히 실행됩니다); 마지막에 한 번만 설치합니다. |
생성기 출력
섹션 제목: “생성기 출력”생성기는 React 애플리케이션에 다음 구조를 생성합니다:
디렉터리src
디렉터리components
- <ApiName>ClientProvider.tsx Sets up the tRPC clients and bindings to your backend schema(s). ApiName will resolve to the name of the API
- QueryClientProvider.tsx TanStack React Query client provider
디렉터리hooks
- useSigV4.tsx Hook for signing HTTP requests with SigV4 (IAM only)
- use<ApiName>.tsx A hook returning the tRPC options proxy for TanStack Query integration
- use<ApiName>Client.tsx A hook returning the vanilla tRPC client for direct API calls
또한 필요한 종속성을 설치합니다:
@trpc/client@trpc/tanstack-react-query@tanstack/react-queryaws4fetch(IAM 인증 사용 시)event-source-polyfill(REST API 사용 시, 구독 지원용)
생성된 코드 사용하기
섹션 제목: “생성된 코드 사용하기”tRPC 옵션 프록시 훅 사용하기
섹션 제목: “tRPC 옵션 프록시 훅 사용하기”생성기는 useQuery 및 useMutation과 같은 TanStack Query 훅과 함께 사용할 수 있는 tRPC 옵션 프록시를 반환하는 use<ApiName> 훅을 제공합니다:
import { useQuery, useMutation } from '@tanstack/react-query';import { useMyApi } from './hooks/useMyApi';
function MyComponent() { const trpc = useMyApi();
// Example query const { data, isLoading, error } = useQuery(trpc.users.list.queryOptions());
// Example mutation const mutation = useMutation(trpc.users.create.mutationOptions());
const handleCreate = () => { mutation.mutate({ name: 'John Doe', email: 'john@example.com', }); };
if (isLoading) return <div>Loading...</div>;
return ( <ul> {data.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );}Vanilla tRPC 클라이언트 사용하기
섹션 제목: “Vanilla tRPC 클라이언트 사용하기”use<ApiName>Client 훅은 명령형 API 호출 및 구독에 유용한 vanilla tRPC 클라이언트에 대한 액세스를 제공합니다:
import { useState } from 'react';import { useMyApiClient } from './hooks/useMyApi';
function MyComponent() { const client = useMyApiClient();
const handleClick = async () => { const result = await client.echo.query({ message: 'Hello!' }); console.log(result);
const mutationResult = await client.users.create.mutate({ name: 'Jane' }); console.log(mutationResult); };
return <button onClick={handleClick}>Call API</button>;}오류 처리
섹션 제목: “오류 처리”통합에는 tRPC 오류를 적절하게 처리하는 내장 오류 처리가 포함되어 있습니다:
function MyComponent() { const trpc = useMyApi();
const { data, error } = useQuery(trpc.users.list.queryOptions());
if (error) { return ( <div> <h2>Error occurred:</h2> <p>{error.message}</p> {error.data?.code && <p>Code: {error.data.code}</p>} </div> ); }
return ( <ul> {data.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );}구독 (스트리밍)
섹션 제목: “구독 (스트리밍)”REST API tRPC 백엔드에 연결할 때, 생성된 클라이언트는 구독 작업을 httpSubscriptionLink (SSE 사용)를 통해 라우팅하고 일반 쿼리/뮤테이션을 httpLink를 통해 라우팅하는 splitLink로 자동 구성됩니다. 즉, 추가 구성 없이 구독이 즉시 작동합니다.
백엔드에서 구독 프로시저를 정의하는 방법에 대한 자세한 내용은 tRPC API 생성기 가이드를 참조하세요.
useSubscription 훅 사용하기
섹션 제목: “useSubscription 훅 사용하기”옵션 프록시의 subscriptionOptions와 함께 useSubscription 훅을 사용하여 구독을 사용할 수 있습니다:
import { useSubscription } from '@trpc/tanstack-react-query';import { useMyApi } from './hooks/useMyApi';
function StreamingComponent() { const trpc = useMyApi();
const subscription = useSubscription( trpc.myStream.subscriptionOptions( { query: 'hello' }, { enabled: true, onStarted: () => { console.log('Subscription started'); }, onData: (data) => { console.log('Received:', data.text); }, onError: (error) => { console.error('Subscription error:', error); }, }, ), );
return ( <div> <p>Status: {subscription.status}</p> {subscription.data && <p>Latest: {subscription.data.text}</p>} {subscription.error && <p>Error: {subscription.error.message}</p>} <button onClick={() => subscription.reset()}>Reset</button> </div> );}subscription 객체는 다음을 제공합니다:
subscription.data— 가장 최근에 수신된 데이터subscription.error— 가장 최근에 수신된 오류subscription.status—'idle','connecting','pending'또는'error'중 하나subscription.reset()— 구독을 재설정합니다 (오류 복구에 유용)
Vanilla 클라이언트 사용하기
섹션 제목: “Vanilla 클라이언트 사용하기”또는 구독 수명 주기를 더 세밀하게 제어하기 위해 use<ApiName>Client 훅을 통해 vanilla tRPC 클라이언트를 사용할 수 있습니다:
import { useState, useEffect } from 'react';import { useMyApiClient } from './hooks/useMyApi';
function StreamingComponent() { const client = useMyApiClient(); const [messages, setMessages] = useState<string[]>([]);
useEffect(() => { const subscription = client.myStream.subscribe( { query: 'hello' }, { onData: (data) => { setMessages((prev) => [...prev, data.text]); }, onComplete: () => { console.log('Stream complete'); }, onError: (error) => { console.error('Stream error:', error); }, }, );
// Clean up the subscription on unmount return () => subscription.unsubscribe(); }, [client]);
return ( <ul> {messages.map((msg, i) => ( <li key={i}>{msg}</li> ))} </ul> );}모범 사례
섹션 제목: “모범 사례”로딩 상태 처리
섹션 제목: “로딩 상태 처리”더 나은 사용자 경험을 위해 항상 로딩 및 오류 상태를 처리하세요:
function UserList() { const trpc = useMyApi();
const users = useQuery(trpc.users.list.queryOptions());
if (users.isLoading) { return <LoadingSpinner />; }
if (users.error) { return <ErrorMessage error={users.error} />; }
return ( <ul> {users.data.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );}낙관적 업데이트
섹션 제목: “낙관적 업데이트”더 나은 사용자 경험을 위해 낙관적 업데이트를 사용하세요:
import { useQueryClient, useQuery, useMutation } from '@tanstack/react-query';
function UserList() { const trpc = useMyApi(); const users = useQuery(trpc.users.list.queryOptions()); const queryClient = useQueryClient();
const deleteMutation = useMutation( trpc.users.delete.mutationOptions({ onMutate: async (userId) => { // Cancel outgoing fetches await queryClient.cancelQueries(trpc.users.list.queryFilter());
// Get snapshot of current data const previousUsers = queryClient.getQueryData( trpc.users.list.queryKey(), );
// Optimistically remove the user queryClient.setQueryData(trpc.users.list.queryKey(), (old) => old?.filter((user) => user.id !== userId), );
return { previousUsers }; }, onError: (err, userId, context) => { // Restore previous data on error queryClient.setQueryData( trpc.users.list.queryKey(), context?.previousUsers, ); }, }), );
return ( <ul> {users.map((user) => ( <li key={user.id}> {user.name} <button onClick={() => deleteMutation.mutate(user.id)}>Delete</button> </li> ))} </ul> );}데이터 프리페칭
섹션 제목: “데이터 프리페칭”더 나은 성능을 위해 데이터를 프리페치하세요:
function UserList() { const trpc = useMyApi(); const users = useQuery(trpc.users.list.queryOptions()); const queryClient = useQueryClient();
// Prefetch user details on hover const prefetchUser = async (userId: string) => { await queryClient.prefetchQuery(trpc.users.getById.queryOptions(userId)); };
return ( <ul> {users.map((user) => ( <li key={user.id} onMouseEnter={() => prefetchUser(user.id)}> <Link to={`/users/${user.id}`}>{user.name}</Link> </li> ))} </ul> );}무한 쿼리
섹션 제목: “무한 쿼리”무한 쿼리로 페이지네이션을 처리하세요:
function UserList() { const trpc = useMyApi();
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery( trpc.users.list.infiniteQueryOptions( { limit: 10 }, { getNextPageParam: (lastPage) => lastPage.nextCursor, }, ), );
return ( <div> {data?.pages.map((page) => page.users.map((user) => <UserCard key={user.id} user={user} />), )}
{hasNextPage && ( <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}> {isFetchingNextPage ? 'Loading...' : 'Load More'} </button> )} </div> );}무한 쿼리는 cursor라는 이름의 입력 속성이 있는 프로시저에만 사용할 수 있다는 점에 유의해야 합니다.
타입 안전성
섹션 제목: “타입 안전성”통합은 완전한 엔드투엔드 타입 안전성을 제공합니다. IDE는 모든 API 호출에 대해 완전한 자동 완성 및 타입 검사를 제공합니다:
function UserForm() { const trpc = useMyApi();
// ✅ Input is fully typed const createUser = trpc.users.create.useMutation();
const handleSubmit = (data: CreateUserInput) => { // ✅ Type error if input doesn't match schema createUser.mutate(data); };
return <form onSubmit={handleSubmit}>{/* ... */}</form>;}타입은 백엔드의 라우터 및 스키마 정의에서 자동으로 추론되므로 API의 변경 사항이 빌드 없이 즉시 프론트엔드 코드에 반영됩니다.
커스텀 인증
섹션 제목: “커스텀 인증”tRPC API가 Custom 인증(Lambda Authorizer)을 사용하는 경우, 생성된 클라이언트 프로바이더에는 인증자가 예상하는 인증 헤더를 추가해야 하는 플레이스홀더 headers가 포함됩니다. 생성된 <ApiName>ClientProvider.tsx에서 // TODO: Add headers required by your custom authorizer 주석을 찾아 토큰 또는 API 키 로직으로 교체하세요.
추가 정보
섹션 제목: “추가 정보”자세한 내용은 다음을 참조하세요: