Inicio rápido
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
tRPC combina conceptos de REST y GraphQL. Si no estás familiarizado con ninguno, revisa los Conceptos clave.
Instalación
tRPC está dividido en varios paquetes, permitiéndote instalar solo lo necesario. Asegúrate de instalar los paquetes requeridos en las secciones adecuadas de tu base de código. Para esta guía rápida mantendremos la simplicidad usando solo el cliente vanilla. Para guías específicas de frameworks, consulta uso con React y uso con Next.js.
- tRPC requiere TypeScript >= 4.7.0
- Recomendamos encarecidamente usar
"strict": trueen tutsconfig.jsonya que no admitimos oficialmente el modo no estricto.
Comienza instalando los paquetes @trpc/server y @trpc/client:
- npm
- yarn
- pnpm
- bun
shnpm install @trpc/server @trpc/client
shnpm install @trpc/server @trpc/client
shyarn add @trpc/server @trpc/client
shyarn add @trpc/server @trpc/client
shpnpm add @trpc/server @trpc/client
shpnpm add @trpc/server @trpc/client
shbun add @trpc/server @trpc/client
shbun add @trpc/server @trpc/client
Definir un enrutador backend
Exploraremos los pasos para construir una API con seguridad de tipos usando tRPC. Inicialmente, esta API contendrá tres endpoints con estas firmas de TypeScript:
tstype User = { id: string; name: string; };userList: () => User[];userById: (id: string) => User;userCreate: (data: { name: string }) => User;
tstype User = { id: string; name: string; };userList: () => User[];userById: (id: string) => User;userCreate: (data: { name: string }) => User;
1. Crear una instancia de enrutador
Primero, inicialicemos el backend de tRPC. Es una buena convención hacer esto en un archivo separado y exportar funciones auxiliares reutilizables en lugar del objeto tRPC completo.
server/trpc.tstsimport {initTRPC } from '@trpc/server';/*** Initialization of tRPC backend* Should be done only once per backend!*/constt =initTRPC .create ();/*** Export reusable router and procedure helpers* that can be used throughout the router*/export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/trpc.tstsimport {initTRPC } from '@trpc/server';/*** Initialization of tRPC backend* Should be done only once per backend!*/constt =initTRPC .create ();/*** Export reusable router and procedure helpers* that can be used throughout the router*/export constrouter =t .router ;export constpublicProcedure =t .procedure ;
Luego, inicializaremos nuestra instancia principal del enrutador, comúnmente llamada appRouter, a la que luego añadiremos procedimientos. Finalmente, necesitamos exportar el tipo del enrutador que usaremos más tarde en el lado del cliente.
server/index.tstsimport {router } from './trpc';constappRouter =router ({// ...});// Export type router type signature,// NOT the router itself.export typeAppRouter = typeofappRouter ;
server/index.tstsimport {router } from './trpc';constappRouter =router ({// ...});// Export type router type signature,// NOT the router itself.export typeAppRouter = typeofappRouter ;
2. Añadir un procedimiento de consulta
Usa publicProcedure.query() para añadir un procedimiento de consulta al enrutador.
Lo siguiente crea un procedimiento de consulta llamado userList que devuelve una lista de usuarios desde nuestra base de datos:
server/index.tstsimport {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({userList :publicProcedure .query (async () => {// Retrieve users from a datasource, this is an imaginary databaseconstusers = awaitdb .user .findMany ();returnusers ;}),});
server/index.tstsimport {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({userList :publicProcedure .query (async () => {// Retrieve users from a datasource, this is an imaginary databaseconstusers = awaitdb .user .findMany ();returnusers ;}),});
3. Usar un analizador de entrada para validar inputs
Para implementar el procedimiento userById, necesitamos aceptar entrada del cliente. tRPC te permite definir analizadores de entrada para validar y procesar la entrada. Puedes definir tu propio analizador o usar una biblioteca de validación de tu elección, como zod, yup o superstruct.
Defines tu analizador de entrada en publicProcedure.input(), que luego se puede acceder en la función resolutora como se muestra a continuación:
- Vanilla
- Zod
- Yup
- Valibot
The input parser should be a function that validates and casts the input of this procedure. It should return a strongly typed value when the input is valid or throw an error if the input is invalid.
server/index.tstsconstappRouter =router ({// ...userById :publicProcedure // The input is unknown at this time. A client could have sent// us anything so we won't assume a certain data type..input ((val : unknown) => {// If the value is of type string, return it.// It will now be inferred as a string.if (typeofval === 'string') returnval ;// Uh oh, looks like that input wasn't a string.// We will throw an error instead of running the procedure.throw newError (`Invalid input: ${typeofval }`);}).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server/index.tstsconstappRouter =router ({// ...userById :publicProcedure // The input is unknown at this time. A client could have sent// us anything so we won't assume a certain data type..input ((val : unknown) => {// If the value is of type string, return it.// It will now be inferred as a string.if (typeofval === 'string') returnval ;// Uh oh, looks like that input wasn't a string.// We will throw an error instead of running the procedure.throw newError (`Invalid input: ${typeofval }`);}).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
The input parser can be any ZodType, e.g. z.string() or z.object().
server.tstsimport {z } from 'zod';import {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({// ...userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server.tstsimport {z } from 'zod';import {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({// ...userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
The input parser can be any YupSchema, e.g. yup.string() or yup.object().
server.tstsimport * asyup from 'yup';import {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({// ...userById :publicProcedure .input (yup .string ().required ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server.tstsimport * asyup from 'yup';import {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({// ...userById :publicProcedure .input (yup .string ().required ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
With Valibot, you simply need to wrap your schema with TypeSchema.
server.tsts// @filename: trpc.ts// @include: trpc// @filename: db.ts// @include: db// @filename: server.ts// ---cut---import { wrap } from '@decs/typeschema';import { string } from 'valibot';import { db } from './db';import { publicProcedure, router } from './trpc';const appRouter = router({// ...userById: publicProcedure.input(wrap(string())).query(async (opts) => {const { input } = opts;// ^?// Retrieve the user with the given IDconst user = await db.user.findById(input);// ^?return user;}),});
server.tsts// @filename: trpc.ts// @include: trpc// @filename: db.ts// @include: db// @filename: server.ts// ---cut---import { wrap } from '@decs/typeschema';import { string } from 'valibot';import { db } from './db';import { publicProcedure, router } from './trpc';const appRouter = router({// ...userById: publicProcedure.input(wrap(string())).query(async (opts) => {const { input } = opts;// ^?// Retrieve the user with the given IDconst user = await db.user.findById(input);// ^?return user;}),});
En el resto de esta documentación, usaremos zod como nuestra biblioteca de validación.
4. Añadir un procedimiento de mutación
Similar a GraphQL, tRPC distingue entre procedimientos de consulta y mutación.
El funcionamiento de un procedimiento en el servidor no cambia mucho entre consulta y mutación. El nombre del método es diferente, y la forma en que el cliente usará este procedimiento cambia, ¡pero todo lo demás es igual!
Añadamos una mutación userCreate agregándola como nueva propiedad en nuestro objeto de enrutador:
server.tstsconstappRouter =router ({// ...userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;// Create a new user in the databaseconstuser = awaitdb .user .create (input );returnuser ;}),});
server.tstsconstappRouter =router ({// ...userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;// Create a new user in the databaseconstuser = awaitdb .user .create (input );returnuser ;}),});
Servir la API
Ahora que hemos definido nuestro enrutador, podemos servirlo. tRPC tiene muchos adaptadores para que puedas usar cualquier framework backend de tu elección. Para mantener la simplicidad, usaremos el adaptador standalone.
server/index.tstsimport {createHTTPServer } from '@trpc/server/adapters/standalone';import {router } from './trpc';constappRouter =router ({// ...});constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
server/index.tstsimport {createHTTPServer } from '@trpc/server/adapters/standalone';import {router } from './trpc';constappRouter =router ({// ...});constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
See the full backend code
server/db.tststypeUser = {id : string;name : string };// Imaginary databaseconstusers :User [] = [];export constdb = {user : {findMany : async () =>users ,findById : async (id : string) =>users .find ((user ) =>user .id ===id ),create : async (data : {name : string }) => {constuser = {id :String (users .length + 1), ...data };users .push (user );returnuser ;},},};
server/db.tststypeUser = {id : string;name : string };// Imaginary databaseconstusers :User [] = [];export constdb = {user : {findMany : async () =>users ,findById : async (id : string) =>users .find ((user ) =>user .id ===id ),create : async (data : {name : string }) => {constuser = {id :String (users .length + 1), ...data };users .push (user );returnuser ;},},};
server/trpc.tstsimport {initTRPC } from '@trpc/server';constt =initTRPC .create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/trpc.tstsimport {initTRPC } from '@trpc/server';constt =initTRPC .create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/index.tstsimport {createHTTPServer } from "@trpc/server/adapters/standalone";import {z } from "zod";import {db } from "./db";import {publicProcedure ,router } from "./trpc";constappRouter =router ({userList :publicProcedure .query (async () => {constusers = awaitdb .user .findMany ();returnusers ;}),userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .findById (input );returnuser ;}),userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .create (input );returnuser ;}),});export typeAppRouter = typeofappRouter ;constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
server/index.tstsimport {createHTTPServer } from "@trpc/server/adapters/standalone";import {z } from "zod";import {db } from "./db";import {publicProcedure ,router } from "./trpc";constappRouter =router ({userList :publicProcedure .query (async () => {constusers = awaitdb .user .findMany ();returnusers ;}),userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .findById (input );returnuser ;}),userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .create (input );returnuser ;}),});export typeAppRouter = typeofappRouter ;constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
Usar tu nuevo backend en el cliente
Ahora pasemos al código del lado del cliente y aprovechemos el poder de la seguridad de tipos de extremo a extremo. Cuando importamos el tipo AppRouter para que el cliente lo use, logramos una seguridad de tipos completa en nuestro sistema sin filtrar detalles de implementación al cliente.
1. Configurar el cliente de tRPC
client/index.tstsimport {createTRPCProxyClient ,httpBatchLink } from '@trpc/client';import type {AppRouter } from './server';// 👆 **type-only** import// Pass AppRouter as generic here. 👇 This lets the `trpc` object know// what procedures are available on the server and their input/output types.consttrpc =createTRPCProxyClient <AppRouter >({links : [httpBatchLink ({url : 'http://localhost:3000',}),],});
client/index.tstsimport {createTRPCProxyClient ,httpBatchLink } from '@trpc/client';import type {AppRouter } from './server';// 👆 **type-only** import// Pass AppRouter as generic here. 👇 This lets the `trpc` object know// what procedures are available on the server and their input/output types.consttrpc =createTRPCProxyClient <AppRouter >({links : [httpBatchLink ({url : 'http://localhost:3000',}),],});
Los links en tRPC son similares a los links en GraphQL: nos permiten controlar el flujo de datos antes de enviarse al servidor. En el ejemplo anterior usamos el httpBatchLink, que agrupa automáticamente múltiples llamadas en una sola solicitud HTTP. Para un uso más avanzado de links, consulta la documentación de links.
2. Consultas y mutaciones
Ahora tienes acceso a tus procedimientos de API en el objeto trpc. ¡Pruébalo!
client/index.tsts// Inferred typesconstuser = awaittrpc .userById .query ('1');constcreatedUser = awaittrpc .userCreate .mutate ({name : 'sachinraja' });
client/index.tsts// Inferred typesconstuser = awaittrpc .userById .query ('1');constcreatedUser = awaittrpc .userCreate .mutate ({name : 'sachinraja' });
Autocompletado completo
Puedes abrir tu Intellisense para explorar tu API en el frontend. Encontrarás todas tus rutas de procedimientos esperándote, junto con los métodos para llamarlas.
client/index.tsts// Full autocompletion on your routestrpc .u ;
client/index.tsts// Full autocompletion on your routestrpc .u ;
¡Pruébalo tú mismo!
Próximos pasos
Te recomendamos encarecidamente que revises las aplicaciones de ejemplo para aprender cómo se instala tRPC en tu framework favorito.
Por defecto, tRPC mapeará tipos complejos como Date a su equivalente JSON (string en el caso de Date). Si quieres conservar la integridad de esos tipos, la forma más sencilla es usar superjson como Transformador de Datos.
tRPC incluye herramientas más sofisticadas del lado del cliente diseñadas para proyectos de React y Next.js.