This document describes the end-to-end type safety implementation using tRPC in the Very-Prince Turborepo.
tRPC provides end-to-end type safety between the Next.js frontend and Fastify backend, eliminating API boundary errors and ensuring that changing a backend return type immediately causes TypeScript compilation errors in the frontend.
- Location:
packages/backend/src/trpc/ - Router:
packages/backend/src/trpc/router.ts- Defines all tRPC procedures - Server:
packages/backend/src/trpc/server.ts- Fastify integration - Integration: Added to
packages/backend/src/index.ts
- Location:
packages/frontend/src/trpc/ - Client:
packages/frontend/src/trpc/client.ts- tRPC client configuration - Provider:
packages/frontend/src/trpc/provider.tsx- React Query provider - Hooks:
packages/frontend/src/hooks/useTRPCQuery.ts- React Query hooks - Integration: Added to
packages/frontend/src/app/layout.tsx
organization.get- Get organization details by IDorganization.list- List organizations (placeholder)organization.create- Create organization (placeholder)
contract.getStatus- Get contract statuscontract.getDetails- Get contract details
stats.getOverview- Get statistics overview
When you modify a return type in the backend router, TypeScript immediately shows errors in the frontend:
// Backend: packages/backend/src/trpc/router.ts
export const appRouter = t.router({
organization: {
get: t.procedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
// Change this return type
return {
id: input.id,
name: "Test Org",
admin: "0x123...",
// Remove budgetStroops field
// budgetStroops: "1000000",
};
}),
},
});The frontend will immediately show a TypeScript error:
// Frontend: packages/frontend/src/components/OrganizationCardTRPC.tsx
{org.budgetStroops && (
<p>Budget: {org.budgetStroops}</p>
)}
// Error: Property 'budgetStroops' does not exist on type...import { trpcClient } from '../trpc/client';
const org = await trpcClient.organization.get.query({ id: 'my-org' });import { useOrganization } from '../hooks/useTRPCQuery';
function MyComponent() {
const { data: org, isLoading } = useOrganization('my-org');
if (isLoading) return <div>Loading...</div>;
return <div>{org.name}</div>;
}The implementation provides both tRPC and REST APIs for backward compatibility:
- Phase 1: tRPC endpoints available alongside existing REST endpoints
- Phase 2: Gradually migrate frontend components to use tRPC
- Phase 3: Decommission REST endpoints once fully migrated
To verify type safety works:
- Run
npm run buildin both packages - Modify a backend return type in
packages/backend/src/trpc/router.ts - Run
npm run buildagain - TypeScript errors should appear in the frontend - Check the test file:
packages/frontend/src/test-type-safety.ts
@trpc/server: ^10.45.0@trpc/client: ^10.45.0
@trpc/client: ^10.45.0@trpc/react-query: ^10.45.0@trpc/server: ^10.45.0@tanstack/react-query: ^5.0.0
- End-to-end type safety: No runtime type errors
- Autocomplete: Full IDE support for API methods
- Refactoring safety: Changes propagate automatically
- Reduced boilerplate: No need for API response type definitions
- Better developer experience: Instant feedback on API changes
- Implement remaining REST endpoints as tRPC procedures
- Add authentication context to tRPC procedures
- Implement proper error handling and validation
- Add tRPC middleware for logging and metrics
- Migrate all frontend components to use tRPC hooks