Adiós a la documentación de API: aplicaciones con seguridad de tipos de extremo a extremo mediante tRPC

4 min

1. El problema del «contrato» entre frontend y backend

En el desarrollo tradicional con frontend y backend separados, la API es el «contrato» entre ambos. Normalmente mantenemos ese contrato de estas maneras:

  • API RESTful: mediante herramientas como OpenAPI (Swagger), que generan y mantienen documentación detallada de la API.
  • GraphQL: mediante un estricto Schema Definition Language (SDL), que define las estructuras de datos y las operaciones.

Estas soluciones funcionan, pero comparten un problema: el contrato y la implementación están separados. Cuando alguien del frontend llama a una API, confía en que devolverá la estructura descrita en la documentación o el esquema. Si cambia la implementación del backend —por ejemplo, se renombra un campo— y la documentación o el esquema no se actualizan a tiempo, la discrepancia solo aparece en tiempo de ejecución y provoca un error.

¿Existe una forma de mantener este «contrato» sincronizado automáticamente con la implementación e incluso descubrir discrepancias durante la compilación?

2. La idea central de tRPC: compartir tipos, no esquemas

tRPC (TypeScript Remote Procedure Call) propone una solución radical pero muy sencilla: si tanto el frontend como el backend usan TypeScript, ¿por qué no compartir directamente los tipos?

tRPC permite escribir funciones TypeScript normales como API del backend y llamarlas directamente desde el frontend, con inferencia de tipos y autocompletado completos, como si fueran funciones de un módulo local.

No depende de ningún esquema ni de generación de código. El único «contrato» son los propios tipos de TypeScript.

3. ¿Cómo funciona?

La magia de tRPC procede de la inferencia de tipos y de una pequeña capa de encapsulación ingeniosa.

a. Backend: definir el router de la API

En el backend —normalmente un servicio Node.js— se utilizan las funciones de tRPC para crear uno o varios «routers». Cada router reúne un conjunto de «procedures» invocables, es decir, los endpoints de la API.

// server/router.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod'; // 使用 Zod 进行运行时校验

const t = initTRPC.create();

export const appRouter = t.router({
  // 定义一个名为 `getUser` 的查询 procedure
  getUser: t.procedure
    .input(z.object({ userId: z.string() }))
    .query(({ input }) => {
      // 在这里查询数据库或执行其他逻辑
      const user = { id: input.userId, name: 'Alex' };
      return user;
    }),

  // 定义一个名为 `createUser` 的变更 procedure
  createUser: t.procedure
    .input(z.object({ name: z.string() }))
    .mutation(({ input }) => {
      const user = { id: `${Math.random()}`, name: input.name };
      return user;
    }),
});

// 导出 router 的类型定义
export type AppRouter = typeof appRouter;

b. Frontend: crear el cliente y llamar a la API

En el frontend solo hay que importar del backend AppRouter como tipo. Es importante observar que solo se importa un tipo, no código del servidor.

// client/trpc.ts
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server/router'; // 只导入类型

export const trpc = createTRPCReact<AppRouter>();

Ahora puedes llamar a la API desde un componente React como si fuera una función local, con seguridad de tipos y autocompletado completos.

// client/components/UserInfo.tsx
import { trpc } from '../trpc';

function UserInfo({ userId }: { userId: string }) {
  // `useQuery` 的第一个参数是 procedure 的路径
  // 你输入 `trpc.` 时,IDE 会自动提示 `getUser` 和 `createUser`
  const userQuery = trpc.getUser.useQuery({ userId });

  if (userQuery.isLoading) {
    return <div>Loading...</div>;
  }

  // `userQuery.data` 的类型被自动推断为 { id: string; name: string }
  return <div>User: {userQuery.data?.name}</div>;
}

Si ahora quien desarrolla el backend cambia el campo que devuelve getUser de name a fullName, userQuery.data?.name producirá inmediatamente un error de compilación de TypeScript en el frontend, en lugar de descubrirse en tiempo de ejecución.

4. ¿Por qué elegir tRPC?

  • Seguridad de tipos absoluta de extremo a extremo: es su principal valor. Elimina toda una clase de errores causados por contratos de API que no coinciden.
  • Excelente experiencia de desarrollo: el autocompletado del IDE evita consultar documentación o adivinar la estructura de la API. Refactorizar se vuelve extraordinariamente sencillo y seguro.
  • Sin generación de código: no hay pasos adicionales de compilación, por lo que el ciclo de respuesta es muy rápido.
  • Ligero y flexible: tRPC es muy pequeño y puede integrarse con cualquier framework frontend y servicio backend.

Conclusión

tRPC aporta una fluidez sin precedentes al desarrollo TypeScript full-stack. Al eliminar la dependencia de la documentación de API y los esquemas, hace que la colaboración entre frontend y backend sea directa y extremadamente segura. Sus ventajas son aún mayores en una arquitectura monorepo. Si estás construyendo una aplicación TypeScript full-stack, tRPC es una herramienta revolucionaria a la que merece la pena dedicar tiempo.