Definir procedimientos
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Un procedimiento es una función expuesta al cliente, que puede ser uno de:
-
una
Query(consulta): se utiliza para obtener datos y generalmente no modifica información -
una
Mutation(mutación): se emplea para enviar datos, frecuentemente para crear/actualizar/eliminar -
una
Subscription(suscripción): posiblemente no la necesites, y tenemos documentación dedicada
Los procedimientos en tRPC son primitivas muy flexibles para crear funciones backend. Utilizan un patrón de construcción inmutable, lo que permite crear procedimientos base reutilizables que comparten funcionalidad entre múltiples procedimientos.
Escritura de procedimientos
El objeto t creado durante la configuración de tRPC devuelve un t.procedure inicial sobre el que se construyen todos los demás procedimientos:
tsimport {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!',};}),});
tsimport {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!',};}),});
Procedimientos base reutilizables
Como patrón general, recomendamos renombrar y exportar t.procedure como publicProcedure, lo que permite crear otros procedimientos con nombre para casos de uso específicos y exportarlos también. Este patrón se llama "procedimientos base" y es fundamental para reutilizar código y comportamiento en tRPC; es probable que cada aplicación lo necesite.
En el siguiente código, usamos procedimientos base reutilizables para construir casos de uso comunes: creamos uno para usuarios autenticados (authedProcedure) y otro que toma un organizationId y valida que el usuario pertenezca a esa organización.
Este es un ejemplo simplificado; en la práctica conviene usar combinaciones de Headers, Contexto, Middleware y Metadatos para autenticar y autorizar usuarios.
tsimport {initTRPC ,TRPCError } from '@trpc/server';import {z } from 'zod';typeOrganization = {id : string;name : string;};typeMembership = {role : 'ADMIN' | 'MEMBER';Organization :Organization ;};typeUser = {id : string;memberships :Membership [];};typeContext = {/*** User is nullable*/user :User | null;};constt =initTRPC .context <Context >().create ();export constpublicProcedure =t .procedure ;// procedure that asserts that the user is logged inexport constauthedProcedure =t .procedure .use (async functionisAuthed (opts ) {const {ctx } =opts ;// `ctx.user` is nullableif (!ctx .user ) {throw newTRPCError ({code : 'UNAUTHORIZED' });}returnopts .next ({ctx : {// ✅ user value is known to be non-null nowuser :ctx .user ,},});});// procedure that a user is a member of a specific organizationexport constorganizationProcedure =authedProcedure .input (z .object ({organizationId :z .string () })).use (functionisMemberOfOrganization (opts ) {constmembership =opts .ctx .user .memberships .find ((m ) =>m .Organization .id ===opts .input .organizationId ,);if (!membership ) {throw newTRPCError ({code : 'FORBIDDEN',});}returnopts .next ({ctx : {Organization :membership .Organization ,},});});export constappRouter =t .router ({whoami :authedProcedure .query (async (opts ) => {// user is non-nullable hereconst {ctx } =opts ;returnctx .user ;}),addMember :organizationProcedure .input (z .object ({z .string ().}),).mutation ((opts ) => {// ctx contains the non-nullable user & the organization being queriedconst {ctx } =opts ;// input includes the validated email of the user being invited & the validated organizationIdconst {input } =opts ;return '...';}),});
tsimport {initTRPC ,TRPCError } from '@trpc/server';import {z } from 'zod';typeOrganization = {id : string;name : string;};typeMembership = {role : 'ADMIN' | 'MEMBER';Organization :Organization ;};typeUser = {id : string;memberships :Membership [];};typeContext = {/*** User is nullable*/user :User | null;};constt =initTRPC .context <Context >().create ();export constpublicProcedure =t .procedure ;// procedure that asserts that the user is logged inexport constauthedProcedure =t .procedure .use (async functionisAuthed (opts ) {const {ctx } =opts ;// `ctx.user` is nullableif (!ctx .user ) {throw newTRPCError ({code : 'UNAUTHORIZED' });}returnopts .next ({ctx : {// ✅ user value is known to be non-null nowuser :ctx .user ,},});});// procedure that a user is a member of a specific organizationexport constorganizationProcedure =authedProcedure .input (z .object ({organizationId :z .string () })).use (functionisMemberOfOrganization (opts ) {constmembership =opts .ctx .user .memberships .find ((m ) =>m .Organization .id ===opts .input .organizationId ,);if (!membership ) {throw newTRPCError ({code : 'FORBIDDEN',});}returnopts .next ({ctx : {Organization :membership .Organization ,},});});export constappRouter =t .router ({whoami :authedProcedure .query (async (opts ) => {// user is non-nullable hereconst {ctx } =opts ;returnctx .user ;}),addMember :organizationProcedure .input (z .object ({z .string ().}),).mutation ((opts ) => {// ctx contains the non-nullable user & the organization being queriedconst {ctx } =opts ;// input includes the validated email of the user being invited & the validated organizationIdconst {input } =opts ;return '...';}),});
Inferencia del tipo de opciones de un "Procedimiento Base"
Además de poder inferir tipos de entrada y salida de un procedimiento, también puedes inferir el tipo de opciones de un constructor de procedimientos específico (o procedimiento base) usando inferProcedureBuilderResolverOptions.
Este ayudante de tipos es útil para declarar tipos en parámetros de funciones. Por ejemplo, para separar el manejador del procedimiento (código de ejecución principal) de su definición en el router, o crear funciones auxiliares que trabajen con múltiples procedimientos.
tsasync function getMembersOfOrganization(opts: inferProcedureBuilderResolverOptions<typeof organizationProcedure>,) {// input and ctx are now correctly typed! const { ctx, input } = opts;return await prisma.user.findMany({where: {membership: { organizationId: ctx.Organization.id, },},});}export const appRouter = t.router({listMembers: organizationProcedure.query(async (opts) => { // use helper function! const members = await getMembersOfOrganization(opts);return members;}),});
tsasync function getMembersOfOrganization(opts: inferProcedureBuilderResolverOptions<typeof organizationProcedure>,) {// input and ctx are now correctly typed! const { ctx, input } = opts;return await prisma.user.findMany({where: {membership: { organizationId: ctx.Organization.id, },},});}export const appRouter = t.router({listMembers: organizationProcedure.query(async (opts) => { // use helper function! const members = await getMembersOfOrganization(opts);return members;}),});
Suscripciones
Para información sobre suscripciones, consulta nuestra guía dedicada.