5.2 KiB
@repo/api-client -- Framework-Agnostic tRPC + React Query Provider
Purpose
This package provides a framework-agnostic tRPC client and React Query provider that any frontend app (Next.js, TanStack Start, or future frameworks) can consume. It exposes <ApiProvider> for initialization and useTRPC() for fully typed data fetching. It contains zero business logic.
Hard Rules
- NEVER import framework-specific code (no
next/, no@tanstack/start, novinxi/) - NEVER put business logic in this package
- NEVER create custom hooks that duplicate what
useTRPC()already provides - Both Next.js and TanStack Start apps use the same
<ApiProvider>anduseTRPC() - This package depends on
@repo/apifor theAppRoutertype only (no runtime import)
File Structure
packages/api-client/
src/
trpc.ts # Creates TRPCProvider + useTRPC via createTRPCContext<AppRouter>()
query-client.ts # Singleton QueryClient factory (SSR-safe)
provider.tsx # <ApiProvider> component: wires tRPC client + QueryClientProvider
index.ts # Package entry: re-exports ApiProvider, useTRPC, getQueryClient
package.json
AGENTS.md
How Apps Consume This Package
Step 1: Wrap your app with <ApiProvider>
The provider needs a trpcUrl pointing to the tRPC HTTP endpoint:
// apps/web-next/src/app/providers.tsx
"use client";
import { ApiProvider } from "@repo/api-client";
export function Providers({ children }: { children: React.ReactNode }) {
return <ApiProvider trpcUrl="/api/trpc">{children}</ApiProvider>;
}
// apps/web-next/src/app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
For TanStack Start, the provider goes in the root route:
// apps/web-tanstack/src/routes/__root.tsx
import { Outlet, createRootRoute } from "@tanstack/react-router";
import { ApiProvider } from "@repo/api-client";
export const Route = createRootRoute({
component: () => (
<ApiProvider trpcUrl="http://localhost:3000/api/trpc">
<Outlet />
</ApiProvider>
),
});
Step 2: Use useTRPC() in components
"use client";
import { useTRPC } from "@repo/api-client";
import { useQuery, useMutation } from "@tanstack/react-query";
export function ArticleList() {
const trpc = useTRPC();
// Query -- reads data
const { data, isLoading, error } = useQuery(
trpc.content.listArticles.queryOptions({ status: "published", limit: 10 })
);
// Mutation -- writes data
const createArticle = useMutation(
trpc.content.createArticle.mutationOptions()
);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<ul>
{data?.map((article) => (
<li key={article.id}>{article.title}</li>
))}
</ul>
<button
onClick={() =>
createArticle.mutate({
title: "New Article",
content: "Hello world",
authorId: "user-1",
})
}
>
Create Article
</button>
</div>
);
}
The useTRPC() Pattern
useTRPC() is created by createTRPCContext<AppRouter>() from @trpc/tanstack-react-query. It returns a proxy object that mirrors the router structure:
useTRPC()
.auth
.signIn.mutationOptions()
.signUp.mutationOptions()
.signOut.mutationOptions()
.content
.listArticles.queryOptions({ ... })
.createArticle.mutationOptions()
You pass .queryOptions() to useQuery() and .mutationOptions() to useMutation() from @tanstack/react-query. This gives you full control over caching, refetching, optimistic updates, and all React Query features.
Custom Hook Wrappers Are Optional
Since useTRPC() gives fully typed access to every procedure, you do not need to create wrapper hooks like useArticles(). Only create a custom hook if you have shared logic (e.g., combining multiple queries, adding retry logic, or transforming results) that would otherwise be duplicated across multiple components.
QueryClient Configuration
The getQueryClient() factory in query-client.ts handles SSR correctly:
- Server-side: Creates a new
QueryClientper request (avoids cross-request data leaks) - Client-side: Returns a singleton
QueryClient(reused across renders) - Default
staleTimeis 30 seconds
Dependencies
| Dependency | Purpose |
|---|---|
@repo/api |
AppRouter type for end-to-end type safety (type-only import) |
@trpc/client |
tRPC client with httpBatchLink |
@trpc/tanstack-react-query |
createTRPCContext for React Query integration |
@tanstack/react-query |
QueryClient, QueryClientProvider |
react |
JSX runtime for provider component |
Cross-References
- Router types come from:
packages/api/-- seepackages/api/AGENTS.md - tRPC HTTP endpoint:
apps/web-next/src/app/api/trpc/[trpc]/route.ts - Provider usage in Next.js:
apps/web-next/src/app/providers.tsx - Provider usage in TanStack:
apps/web-tanstack/src/routes/__root.tsx