tRPC Overview
GTM Clarity uses tRPC 11 for end-to-end type-safe API communication. All client-server calls go through tRPC procedures with Zod input validation and automatic TypeScript inference.
Architecture
React Component
-> useTRPC() hook (type-safe client)
-> HTTP batch link (/api/trpc)
-> tRPC router (Zod validation + middleware)
-> Handler (DB queries, business logic)
-> JSON response (type-inferred)
Routers
The appRouter composes 9 domain-specific routers defined in src/server/trpc/router.ts:
export const appRouter = t.router({
tenant: tenantRouter,
audit: auditRouter,
user: userRouter,
connector: connectorRouter,
fieldMapping: fieldMappingRouter,
writeback: writebackRouter,
scoring: scoringRouter,
scoringConfig: scoringConfigRouter,
buyingGroup: buyingGroupRouter,
});
export type AppRouter = typeof appRouter;
| Router | Namespace | Key Procedures | Access |
|---|---|---|---|
| Tenant | tenant | getCurrent, updateSettings | tenant / admin |
| Audit | audit | list, getByResource | admin |
| User | user | list, getRole | admin / tenant |
| Connector | connector | list, getStatus, getAuthUrl, triggerSync | tenant / admin |
| Field Mappings | fieldMapping | listSourceFields, getMappings, saveMappings | tenant / admin |
| Writeback | writeback | getConfig, saveConfig, triggerWriteback | admin |
| Scoring | scoring | getMatrixData, getEntityList, runScoring | tenant / admin |
| Scoring Config | scoringConfig | getConfig, updateConfig, resetConfig | tenant / admin |
| Buying Groups | buyingGroup | listByAccount, getDetail, getCandidates | tenant / admin |
Procedure Types
tRPC procedures are either queries (read-only, GET semantics) or mutations (write, POST semantics):
// Query -- fetches data, cacheable
getMatrixData: tenantProcedure
.input(z.object({ limit: z.number().default(500) }))
.query(async ({ ctx, input }) => { ... });
// Mutation -- modifies state
runScoring: adminProcedure
.input(z.object({ entityTypes: z.array(entityTypeSchema).optional() }))
.mutation(async ({ ctx, input }) => { ... });
| Type | HTTP Method | Use For | Caching |
|---|---|---|---|
query | GET (batched) | Reading data | TanStack Query cache |
mutation | POST | Creating, updating, deleting | Invalidates related queries |
Zod Input Validation
Every procedure validates its input with Zod 4 schemas. Invalid input returns a BAD_REQUEST error with structured validation details:
// Common schemas used across routers
const entityTypeSchema = z.enum(["person", "account", "opportunity"]);
const scoreTierSchema = z.enum(["hot", "warm", "cool", "cold"]);
const configTypeSchema = z.enum(["fit", "engagement", "combined"]);
When Zod validation fails, tRPC returns a BAD_REQUEST error with the Zod issue array in the error cause. See Error Handling for details.
Client Setup
The tRPC client is initialized in src/lib/trpc.tsx using TanStack React Query:
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createTRPCClient, httpBatchLink } from "@trpc/client";
import { createTRPCContext } from "@trpc/tanstack-react-query";
import { useState } from "react";
import type { AppRouter } from "@/server/trpc/router";
export const { TRPCProvider, useTRPC } = createTRPCContext<AppRouter>();
export function TRPCReactProvider({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
}),
);
return (
<QueryClientProvider client={queryClient}>
<TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
{children}
</TRPCProvider>
</QueryClientProvider>
);
}
Calling Procedures in React
Use the useTRPC hook to get type-safe access to all routers:
Queries
import { useTRPC } from "@/lib/trpc";
import { useQuery } from "@tanstack/react-query";
function EntityList() {
const trpc = useTRPC();
const { data, isLoading, error } = useQuery(
trpc.scoring.getEntityList.queryOptions({
entityType: "account",
page: 1,
pageSize: 25,
sortBy: "combinedScore",
sortDir: "desc",
})
);
if (error) return <ErrorDisplay error={error} />;
if (isLoading) return <Spinner />;
return <Table items={data.items} total={data.total} />;
}
Mutations
import { useTRPC } from "@/lib/trpc";
import { useMutation, useQueryClient } from "@tanstack/react-query";
function RunScoringButton() {
const trpc = useTRPC();
const queryClient = useQueryClient();
const mutation = useMutation(
trpc.scoring.runScoring.mutationOptions({
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: trpc.scoring.getMatrixData.queryKey() });
},
})
);
return (
<button
onClick={() => mutation.mutate({ entityTypes: ["person", "account"] })}
disabled={mutation.isPending}
>
{mutation.isPending ? "Scoring..." : "Run Scoring"}
</button>
);
}
The httpBatchLink automatically batches multiple concurrent tRPC calls into a single HTTP request. No additional configuration is needed.
Related Pages
- Authentication -- Middleware chain and RBAC roles
- Error Handling -- Error codes and Zod validation errors
- Scoring API -- Full scoring router reference