콘텐츠로 이동

React에서 tRPC로

Nx Plugin for AWS는 tRPC API를 React 웹사이트와 빠르게 통합할 수 있는 생성기를 제공합니다. AWS IAM 및 Cognito 인증 지원과 적절한 오류 처리를 포함하여 tRPC 백엔드에 연결하는 데 필요한 모든 구성을 설정합니다. 이 통합은 프론트엔드와 tRPC 백엔드 간의 완전한 엔드투엔드 타입 안전성을 제공합니다.

이 생성기를 사용하기 전에 React 애플리케이션에 다음이 있어야 합니다:

  1. 애플리케이션을 렌더링하는 main.tsx 파일
  2. tRPC 프로바이더가 자동으로 주입될 <App/> JSX 요소
  3. 작동하는 tRPC API (tRPC API 생성기를 사용하여 생성됨)
  4. 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>,
);

이 제너레이터 실행@aws/nx-plugin:connection

pnpm nx g @aws/nx-plugin:connection
명령 구성하기5

필수

필수

제너레이터 옵션5 옵션
sourceProject필수string

소스 프로젝트

targetProject필수string

연결할 대상 프로젝트

sourceComponentstring

연결할 소스 컴포넌트 (컴포넌트 이름, 소스 프로젝트 루트 기준 상대 경로, 또는 generator id). 프로젝트를 소스로 명시적으로 선택하려면 '.'을 사용하세요.

targetComponentstring

연결할 대상 컴포넌트 (컴포넌트 이름, 대상 프로젝트 루트 기준 상대 경로, 또는 generator id). 프로젝트를 대상으로 명시적으로 선택하려면 '.'을 사용하세요.

preferInstallDependenciesboolean기본값: 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 Exports both use<ApiName>, returning the tRPC options proxy for TanStack Query integration, and use<ApiName>Client, returning the vanilla tRPC client for direct API calls

또한 필요한 종속성을 설치합니다:

  • @trpc/client
  • @trpc/tanstack-react-query
  • @tanstack/react-query
  • aws4fetch (IAM 인증 사용 시)
  • event-source-polyfill (REST API 사용 시, 구독 지원용)

생성기는 useQueryuseMutation과 같은 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>;
if (error) return <div>Error: {error.message}</div>;
if (!data) return null;
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}

data는 쿼리가 해결될 때까지 undefined이므로 역참조하기 전에 가드해야 합니다.

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>
);
}
if (!data) return null;
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}

REST API tRPC 백엔드에 연결할 때, 생성된 클라이언트는 구독 작업을 httpSubscriptionLink (SSE 사용)를 통해 라우팅하고 일반 쿼리/뮤테이션을 httpLink를 통해 라우팅하는 splitLink로 자동 구성됩니다. 즉, 추가 구성 없이 구독이 즉시 작동합니다.

백엔드에서 구독 프로시저를 정의하는 방법에 대한 자세한 내용은 tRPC API 생성기 가이드를 참조하세요.

옵션 프록시의 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() — 구독을 재설정합니다 (오류 복구에 유용)

또는 구독 수명 주기를 더 세밀하게 제어하기 위해 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.isPending) {
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.data?.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.data?.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 호출에 대해 완전한 자동 완성 및 타입 검사를 제공합니다:

import { useMutation } from '@tanstack/react-query';
function UserForm() {
const trpc = useMyApi();
// ✅ Input is fully typed
const createUser = useMutation(trpc.users.create.mutationOptions());
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 키 로직으로 교체하세요.

자세한 내용은 다음을 참조하세요: