El conocimiento es el nuevo dinero.
Aprender es la nueva manera en la que inviertes
Acceso Cursos

Construir APIs seguras de extremo a extremo con tRPC

tRPC le permite construir y consumir fácilmente APIs totalmente seguras, sin esquemas ni generación de código.

· 7 min de lectura
Construir APIs seguras de extremo a extremo con tRPC

A medida que TypeScript y la tipificación estática se convierten cada vez más en una práctica recomendada en la programación web, la API presenta un importante punto de dolor. Necesitamos mejores formas de tipar estáticamente nuestros puntos finales de la API y compartir esos tipos entre nuestro cliente y el servidor (o de servidor a servidor).

Si tu proyecto está construido con TypeScript, puedes compartir tipos directamente entre el cliente y el servidor, sin depender de la generación de código.

tRPC es una herramienta que proporciona seguridad de tipo entre el front y el backend, por lo que hace muy fácil construir backends escalables y robustos rápidamente para tus aplicaciones Next.js y Node.

Una alternativa a la tradicional REST o GraphQL


Actualmente GraphQL es la forma dominante de implementar APIs seguras en TypeScript (¡y es increíble!). Dado que GraphQL está diseñado como una especificación agnóstica al lenguaje para implementar APIs, no aprovecha toda la potencia de un lenguaje como TypeScript,  viene con un exceso de código repetitivo y requiere mucha configuración inicial.

¿Qué es tRPC?


tRPC es una librería muy ligera que permite construir APIs totalmente seguras sin necesidad de esquemas o generación de código. Permite compartir tipos entre el cliente y el servidor y sólo importa los tipos y no el código real del servidor, por lo que nada del código del servidor está expuesto en el frontend.

Con la seguridad de tipos de extremo a extremo, se pueden detectar errores entre el frontend y el backend en tiempo de compilación y construcción.

Si tu quieres reforzar este blog, puedes pasarte a ver el siguiente video

Instalación de tRPC en Next.js


Primero vamos a crear un proyecto Next.js con TypeScript.

npx create-next-app next-with-trpc --ts

Utilicemos ahora la estructura de carpetas recomendada por tRPC. Abre el proyecto en tu editor favorito, y haz un src y mover  pages y styles dentro del directorio. Tu estructura de carpetas debería ser algo así.

.
├── src
│   ├── pages
│   │   ├── _app.tsx
│   │   ├── api
│   │   │   
│   │   └── [..]
└── [..]

Una vez hecho esto, vamos a instalar tRPC.

npm i @trpc/client @trpc/server @trpc/react @trpc/next zod react-query

tRPC está construido sobre react-query, que es un paquete para obtener, almacenar en caché y actualizar datos sin necesidad de ningún estado global. También estamos utilizando zod para el esquema y las validaciones de entrada. También puedes utilizar otras librerías de validación como yup, Superstruct, entre otros.

Asegúrate también de que en tu tsconfig.json, en modo estricto está activado.

// tsconfig.json
{
  // ...
  "compilerOptions": {
    // ...
    "strict": true
  }
}

Esto es para que zod pueda funcionar correctamente, y en general, tener el modo estricto activado es simplemente bueno.

Creando nuestro servidor


Nuestro servidor tRPC se desplegará como una ruta API de Next.js. Este código sólo se ejecuta en el servidor por lo que no afecta a los tamaños de los paquetes de ninguna manera.

Creando nuestro contexto

Primero vamos a crear un directorio dentro de src llamado server. Aquí, primero necesitamos crear un contexto. Creamos un archivo llamado context.ts y añadimos el siguiente código.

// src/server/context.ts

import { CreateNextContextOptions } from "@trpc/server/adapters/next";
import { inferAsyncReturnType } from "@trpc/server";

export async function createContext(ctxOptions?: CreateNextContextOptions) {
  const req = ctxOptions?.req;
  const res = ctxOptions?.res;

  return {
    req,
    res,
  };
}

export type MyContextType = inferAsyncReturnType<typeof createContext>;

Esto estará disponible como ctx en todos nuestros resolvers que escribiremos en un momento. Ahora mismo, sólo estamos pasando la solicitud y la respuesta a nuestras rutas, pero puedes añadir otras cosas como tokens JWT, cookies o incluso código Prisma Client.

Creando nuestro router


Ahora vamos a crear un archivo llamado createRouter.ts en el server Vamos a configurar un simple router. Copia el siguiente código en él.

/ src/server/createRouter.ts

import * as trpc from "@trpc/server";

import type { MyContextType } from "./context";

export function createRouter() {
  return trpc.router<MyContextType>();
}

Creando nuestras rutas


Vamos a crear un nuevo directorio dentro de src/server llamamos routers. Haz un archivo llamado app.ts dentro de ella. Esta será la ruta raíz.

// src/server/routers/app.ts

import { createRouter } from "../createRouter";

export const appRouter = createRouter();

export type AppRouter = typeof appRouter;

Ahora vamos a crear un enrutador que toma un nombre como entrada y lo devuelve al cliente. Agrega un archivo llamado name.ts.

// src/server/routers/name.ts

import { z } from "zod";

import { createRouter } from "../createRouter";

export const nameRouter = createRouter().query("getName", {
  input: z
    .object({
      name: z.string().nullish(),
    })
    .nullish(),
  resolve({ input }) {
    return { greeting: `Hello ${input?.name ?? "world"}!` };
  },
});

Al igual que GraphQL, tRPC utiliza consultas y mutaciones. Un query se utiliza para obtener datos y mutations se utilizan para crear, actualizar y eliminar datos. Aquí estamos creando un  query  para obtener un nombre. El nombre de nuestra  query  es getName.  

Aquí, input toma la entrada del usuario que es validada usando zod. Cuando se solicita este punto final, se llama a la función resolve y devuelve el returns hola mundo.

Ahora vamos a fusionar esta ruta en nuestra ruta raíz. Volvamos a app.ts y añadir el siguiente código.

// src/server/routers/app.ts

import { createRouter } from "../createRouter";
import { nameRouter } from "./name";

export const appRouter = createRouter().merge("names.", nameRouter);

export type AppRouter = typeof appRouter;

Que t . al final del names. es intencional por razones que verás pronto.

Creación de nuestra ruta API Next.js


Vamos a crear una ruta trpc dentro de src/pages/api. Dentro de él crea un archivo llamado [trpc].ts. Sólo un recordatorio de nuestra estructura de carpetas.

.
├── src
│   ├── pages
│   │   ├── _app.tsx 
│   │   ├── api
│   │   │   └── trpc
│   │   │       └── [trpc].ts 
│   │   └── [..]
│   ├── server
│   │   ├── routers
│   │   │   ├── app.ts   
│   │   │   ├── name.ts  
│   │   │   └── [..]
│   │   ├── context.ts      
│   │   └── createRouter.ts 
└── [..]

Aquí implementaremos nuestro router tRPC. Como había dicho antes, nuestro servidor se desplegará como una ruta API de Next.js.

// src/pages/api/trpc/[trpc].ts

import { createNextApiHandler } from "@trpc/server/adapters/next";

import { appRouter } from "../../../server/routers/app";
import { createContext } from "../../../server/context";

export default createNextApiHandler({
  router: appRouter,
  createContext,
  batching: {
    enabled: true,
  },
  onError({ error }) {
    if (error.code === "INTERNAL_SERVER_ERROR") {
      console.error("Something went wrong", error);
    }
  },
});

Lo pasamos por nuestro router, y creamos la función createConext habilitación de lotes y registro de errores.

Con esto hemos terminado con el backend. Ahora vamos a trabajar en nuestro frontend.

via GIPHY

Llamando a nuestras rutas API tRPC


Ahora vamos a conectar nuestro backend con nuestro frontend. Vamos a src/pages/_app.tsx. Aquí vamos a configurar tRPC y React Query. Copia el siguiente código.

// src/pages/_app.ts

import { AppType } from "next/dist/shared/lib/utils";
import { withTRPC } from "@trpc/next";

import { AppRouter } from "./api/trpc/[trpc]";
import "../styles/globals.css";

const MyApp: AppType = ({ Component, pageProps }) => {
  return <Component {...pageProps} />;
};

const getBaseUrl = () => {
  if (process.browser) return "";
  if (process.env.NEXT_PUBLIC_VERCEL_URL)
    return `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`;

  return `http://localhost:${process.env.PORT ?? 3000}`;
};

export default withTRPC<AppRouter>({
  config({ ctx }) {
    const url = `${getBaseUrl()}/api/trpc`;

    return {
      url,
    };
  },
  ssr: false,
})(MyApp);

También estamos estableciendo ssr (Server Side Rendering) Para ser falso por ahora.

Next, create un  utils dentro de src Dentro de utils, crea un archivo llamado trpc.ts. Estructura de carpetas de referencia.

.
├── src
│   ├── pages
│   │   ├── _app.tsx 
│   │   ├── api
│   │   │   └── trpc
│   │   │       └── [trpc].ts 
│   │   └── [..]
│   ├── server
│   │   ├── routers
│   │   │   ├── app.ts   
│   │   │   ├── name.ts  
│   │   │   └── [..]
│   │   ├── context.ts      
│   │   └── createRouter.ts 
|   └── utils
│       └── trpc.ts
└── [..]

Aquí vamos a crear un hook para usar tRPC en el cliente.

// src/utils/trpc.ts

import { createReactQueryHooks } from "@trpc/react";
import { inferProcedureOutput } from "@trpc/server";

import { AppRouter } from "../server/routers/app";

export const trpc = createReactQueryHooks<AppRouter>();

export type inferQueryOutput<
  TRouteKey extends keyof AppRouter["_def"]["queries"]
> = inferProcedureOutput<AppRouter["_def"]["queries"][TRouteKey]>;

Este hook está fuertemente tipado usando nuestra firma de tipo API. Esto es lo que permite la seguridad de tipos de extremo a extremo en nuestro código.

Por ejemplo, si cambiamos el nombre de un router's en el backend, se mostrará un error en el frontend donde estamos llamando a la ruta. Nos permite llamar a nuestro backend y obtener entradas y salidas totalmente tipificadas de él. También nos da un maravilloso autocompletado.

Ahora vamos a utilizar nuestra consulta que hemos definido antes. Ve a la página de índice y en lugar de copiar y pegar este bloque de código, escríbelo tú mismo para experimentar la magia de tRPC. El autocompletado es una locura.

// src/pages/index.tsx

import { trpc } from "../utils/trpc";

export default function Name() {
  const nameQuery = trpc.useQuery(["names.getName", { name: "nexxel" }]);

  return (
    <>
      {nameQuery.data ? (
        <h1>{nameQuery.data.greeting}</h1>
      ) : (
        <span>Loading...</span>
      )}
    </>
  );
}

¿Has visto la magia? Por si te da pereza y te limitas a copiar el código, te tengo cubierto ;) Mira esto 😱.

El autocompletado es muy bueno. Y atrapa cualquier tipo de error sin la necesidad de declarar cualquier tipo o interfaz manualmente. Es una locura de bueno.

¡Ahora, inicia el servidor de desarrollo y ve el saludo en la pantalla!

via GIPHY

Fíjate en el estado de carga. Esto se debe a que hemos establecido ssr a false cuando configuramos tRPC en _app.tsx. Así que se está renderizando del lado del cliente. Ir a src/pages/_app.tsx y establece  ssr. Vemos que pasa!!

Ahora no hay estado de carga porque los datos se obtienen en el lado del servidor y luego se renderizan. Puedes usar lo que más te convenga.

Conclusión


En este artículo, hemos visto lo que es tRPC y cómo usarlo con una aplicación Next.JS. tRPC hace que la construcción de APIs de tipo seguro sea increíblemente fácil y mejora el DX un millón de veces.

Puedes usarlo no sólo con Next.js, sino también con React, Node y también se están desarrollando adaptadores para Vue y Svelte. Recomiendo usarlo para casi cualquier proyecto que hagas ahora. Para más información, consulta la página web de tRPC

Fuente

Código

Plataforma de cursos gratis sobre programación