tRPC provides end-to-end type safety for your APIs. RivetKit integrates seamlessly with tRPC, allowing you to create type-safe procedures that call Rivet Actors.
View Example on GitHub Check out the complete example
Installation
Install tRPC alongside RivetKit:
npm install @trpc/server @trpc/client zod
npm install -D @trpc/next # if using Next.js
Basic Setup
Create Your Registry
Set up your Rivet Actors:
// registry.ts
import { actor , setup } from "@rivetkit/actor" ;
export const counter = actor ({
state: { count: 0 },
actions: {
increment : ( c , amount : number = 1 ) => {
c . state . count += amount ;
c . broadcast ( "countChanged" , c . state . count );
return c . state . count ;
},
getCount : ( c ) => c . state . count ,
reset : ( c ) => {
c . state . count = 0 ;
c . broadcast ( "countChanged" , 0 );
return 0 ;
},
},
});
export const registry = setup ({
use: { counter },
});
Create tRPC Router
Create your tRPC router that uses RivetKit:
// server.ts
import { registry } from "./registry" ;
import { initTRPC } from "@trpc/server" ;
import { createHTTPServer } from "@trpc/server/adapters/standalone" ;
import { z } from "zod" ;
// Start RivetKit
const { client } = registry . createServer ();
// Initialize tRPC
const t = initTRPC . create ();
// Create tRPC router with RivetKit integration
const appRouter = t . router ({
// Counter procedures
counter: t . router ({
increment: t . procedure
. input ( z . object ({
name: z . string (),
amount: z . number (). optional (). default ( 1 )
}))
. mutation ( async ({ input }) => {
const counter = client . counter . getOrCreate ([ input . name ]);
const newCount = await counter . increment ( input . amount );
return { name: input . name , count: newCount };
}),
get: t . procedure
. input ( z . object ({ name: z . string () }))
. query ( async ({ input }) => {
const counter = client . counter . getOrCreate ([ input . name ]);
const count = await counter . getCount ();
return { name: input . name , count };
}),
reset: t . procedure
. input ( z . object ({ name: z . string () }))
. mutation ( async ({ input }) => {
const counter = client . counter . getOrCreate ([ input . name ]);
const count = await counter . reset ();
return { name: input . name , count };
}),
}),
});
// Export type for client
export type AppRouter = typeof appRouter ;
// Create HTTP server
const server = createHTTPServer ({
router: appRouter ,
});
server . listen ( 3001 );
console . log ( "tRPC server listening at http://localhost:3001" );
Frontend Client
Create a type-safe tRPC client:
// client.ts
import { createTRPCProxyClient , httpBatchLink } from "@trpc/client" ;
import type { AppRouter } from "./server" ;
export const trpc = createTRPCProxyClient < AppRouter >({
links: [
httpBatchLink ({
url: "http://localhost:3001" ,
}),
],
});
// Usage examples
async function examples () {
// Increment counter
const result = await trpc . counter . increment . mutate ({
name: "my-counter" ,
amount: 5
});
console . log ( result ); // { name: "my-counter", count: 5 }
// Get counter value
const value = await trpc . counter . get . query ({ name: "my-counter" });
console . log ( value ); // { name: "my-counter", count: 5 }
// Reset counter
const reset = await trpc . counter . reset . mutate ({ name: "my-counter" });
console . log ( reset ); // { name: "my-counter", count: 0 }
}