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

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, 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:

// 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 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