tRPC API Overview
Relevant source files
The following files were used as context for generating this wiki page:
The Adfluens application utilizes tRPC to facilitate type-safe, end-to-end communication between the React frontend (apps/web) and the Express backend (apps/api). This architecture eliminates the need for manual API schema synchronization and provides developers with autocompletion and compile-time error checking for API calls. The tRPC setup is a core component of the monorepo's full-stack strategy, enabling rapid and robust feature development.
The API is structured around feature-based modules, where each feature on the backend exposes its own tRPC router. These are then merged into a single appRouter that the client consumes. This modular approach keeps the API organized and scalable.
Backend Architecture
The backend is an Express server that integrates tRPC using the @trpc/server/adapters/express adapter. The main server entry point configures middleware for CORS, authentication, and finally, the tRPC handler.
Sources: apps/api/src/index.ts
Server Request Flow
All tRPC requests are routed to the /trpc endpoint. The Express server processes these requests through a series of middleware before they reach the tRPC router.
The diagram below illustrates the high-level request flow on the API server.
Sources: apps/api/src/index.ts:20-41
The tRPC middleware is configured to use the appRouter and the createContext function for every incoming request.
// File: apps/api/src/index.ts:37-41
app.use(
"/trpc",
trpcExpress.createExpressMiddleware({
router: appRouter,
createContext,
})
);
tRPC Initialization and Context
The tRPC instance is initialized in apps/api/src/trpc/init.ts. This file is central to the backend's tRPC configuration, defining the context, middleware, and reusable procedure helpers.
Context Creation
For each request, a Context object is created by the createContext function. This function uses the better-auth library to extract session information from the request headers. The resulting context provides authentication data (user, session, organization) to all tRPC procedures.
The diagram below shows the process of creating the tRPC context.
Sources: apps/api/src/trpc/init.ts:46-64
The Context type is defined as follows:
| Property | Type | Description |
|---|---|---|
user | User | null | The authenticated user object from Better Auth. |
session | Session | null | The user's session object. |
organization | Org | null | The user's active organization from Better Auth. |
Sources: apps/api/src/trpc/init.ts:35-40
Procedures and Middleware
The application defines several types of tRPC procedures to handle different authentication requirements. These are built using tRPC's middleware system.
| Procedure Name | Authentication Required | Organization Required | Description |
|---|---|---|---|
publicProcedure | No | No | For endpoints accessible to anyone. |
protectedProcedure | Yes | No | For endpoints that require a logged-in user. |
organizationProcedure | Yes | Yes | For endpoints that require a user to have an active organization selected. |
Sources: apps/api/src/trpc/init.ts:76-127
Authentication Middleware
Two key middleware functions enforce access control:
isAuthenticated: Checks ifctx.sessionandctx.userexist. If not, it throws aTRPCErrorwith the codeUNAUTHORIZED.hasOrganization: This middleware is piped afterisAuthenticated. It checks forctx.organizationand throws aBAD_REQUESTerror if it's missing.
The following flowchart shows the logic for an organizationProcedure call.
Sources: apps/api/src/trpc/init.ts:81-121
API Router Structure
The API follows a feature-based router structure. Each feature (e.g., ai, youtube) defines its own router file, which is then merged into the main appRouter.
This diagram shows the router hierarchy.
An example of a feature router is the aiRouter, which defines mutations for AI-related tasks.
// File: apps/api/src/features/ai/ai.router.ts:15-26
export const aiRouter = router({
/**
* Analyze a video using Gemini AI
* Requires authentication
*/
analyze: protectedProcedure
.input(
z.object({
video: youtubeVideoSchema,
comments: z.array(youtubeCommentSchema).optional().default([]),
})
)
.mutation(async ({ input }) => {
const { video, comments } = input;
return await analyzeVideo(video, comments);
}),
// ...
});
Client-Side Integration
The frontend application (apps/web) integrates with the tRPC API using @trpc/client and @trpc/tanstack-react-query.
Client Configuration
A vanilla tRPC client is created in apps/web/src/lib/trpc.ts. It uses httpBatchLink to batch multiple API calls into a single HTTP request for efficiency.
// File: apps/web/src/lib/trpc.ts:18-24
export const trpcClient = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: `${getBaseUrl()}/trpc`,
}),
],
});
During development, Vite is configured to proxy requests from /trpc on the frontend dev server (localhost:3000) to the backend API server (localhost:3001).
Sources: apps/web/vite.config.ts:8-12
React Integration
The root of the React application is wrapped with TRPCProvider, which connects the tRPC client to TanStack Query's QueryClient. This enables fetching data using hooks like trpc.youtube.getComments.useQuery().
Sources: apps/web/src/app.tsx:37-39
Calling Procedures
Procedures can be called from React components either through TanStack Query hooks for queries or by using the imperative trpcClient for mutations inside event handlers.
The following sequence diagram illustrates a component calling a tRPC mutation.
Summary
The tRPC implementation in Adfluens provides a robust and developer-friendly foundation for the API. It ensures type safety from the database to the UI, simplifies data fetching with TanStack Query integration, and maintains a clean, scalable structure through feature-based routers and authentication middleware. This setup is critical for building and maintaining a complex full-stack application efficiently.