Files
agentic-dev/packages/api-client/AGENTS.md

167 lines
5.2 KiB
Markdown

# @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`, no `vinxi/`)
- **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>` and `useTRPC()`
- This package depends on `@repo/api` for the `AppRouter` type 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:
```tsx
// 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>;
}
```
```tsx
// 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:
```tsx
// 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
```tsx
"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 `QueryClient` per request (avoids cross-request data leaks)
- **Client-side:** Returns a singleton `QueryClient` (reused across renders)
- Default `staleTime` is 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/` -- see `packages/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`