Define Procedures
A procedure is a function which is exposed to the client, it can be one of:
- a
Query
- used to fetch data, generally does not change any data - a
Mutation
- used to send data, often for create/update/delete purposes - a
Subscription
- you might not need this, and we have dedicated documentation
Procedures in tRPC are very flexible primitives to create backend functions. They use an immutable builder pattern, which means you can create reusable base procedures that share functionality among multiple procedures.
Writing procedures
The t
object you create during tRPC setup returns an initial t.procedure
which all other procedures are built on:
ts
import {initTRPC } from '@trpc/server';import {z } from 'zod';constt =initTRPC .context <{signGuestBook : () =>Promise <void> }>().create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;constappRouter =router ({// Queries are the best place to fetch datahello :publicProcedure .query (() => {return {message : 'hello world',};}),// Mutations are the best place to do things like updating a databasegoodbye :publicProcedure .mutation (async (opts ) => {awaitopts .ctx .signGuestBook ();return {message : 'goodbye!',};}),});
ts
import {initTRPC } from '@trpc/server';import {z } from 'zod';constt =initTRPC .context <{signGuestBook : () =>Promise <void> }>().create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;constappRouter =router ({// Queries are the best place to fetch datahello :publicProcedure .query (() => {return {message : 'hello world',};}),// Mutations are the best place to do things like updating a databasegoodbye :publicProcedure .mutation (async (opts ) => {awaitopts .ctx .signGuestBook ();return {message : 'goodbye!',};}),});
Reusable "Base Procedures"
As a general pattern we recommend you rename and export t.procedure
as publicProcedure
, which then makes room for you to create other named procedures for specific use cases and export those too. This pattern is called "base procedures" and is a key pattern for code and behaviour re-use in tRPC; every application is likely to need it.
The below example takes a user input and authorizes them like protective towns-people. This is obviously a contrived example for simplicity, and not an appropriate way to securely authorize an application user, so in practice you may want to use some combination of Headers, Context, Middleware, and Metadata, to authenticate and authorize your users.
ts
export constauthorizedProcedure =publicProcedure .input (z .object ({townName :z .string () })).use ((opts ) => {if (opts .input .townName !== 'Pucklechurch') {throw newTRPCError ({code : 'FORBIDDEN',message : "We don't take kindly to out-of-town folk",});}returnopts .next ();});export constappRouter =t .router ({hello :authorizedProcedure .query (() => {return {message : 'hello world',};}),goodbye :authorizedProcedure .mutation (async (opts ) => {awaitopts .ctx .signGuestBook ();return {message : 'goodbye!',};}),});
ts
export constauthorizedProcedure =publicProcedure .input (z .object ({townName :z .string () })).use ((opts ) => {if (opts .input .townName !== 'Pucklechurch') {throw newTRPCError ({code : 'FORBIDDEN',message : "We don't take kindly to out-of-town folk",});}returnopts .next ();});export constappRouter =t .router ({hello :authorizedProcedure .query (() => {return {message : 'hello world',};}),goodbye :authorizedProcedure .mutation (async (opts ) => {awaitopts .ctx .signGuestBook ();return {message : 'goodbye!',};}),});