tRPC: The best way to build APIs in 2022?

tRPC: The best way to build APIs in 2022?

[originally published here]

We all know the hassle of building APIs. You install Express, or Nest if you’re developing for a big project, maybe GraphQL, and then you have to make everything yourself. From relational mapping to TypeScript support across your server and client. But what if I told you there was an all-in-one solution for that?

Too good to be true you might be thinking; well I’m writing this to tell you it’s not.

What is tRPC anyway?

tRPC is essentially just a way to call code. Think of it like an API endpoint, but the tRPC lingo is a procedure.

“So we can create API with this new tool; what’s different?”

tRPC isn’t just a way to create procedures, it has much more under the hood.

  • ⚡️ Fast - No code generation or run-time bloat
  • 🧙‍♂️ Typesafe - From DB to client
  • ✅ Safe - Input and output validation with Zod
  • 🍃 Light - tRPC has zero deps (@trpc/client@9.27.0 is only 4.9kb gzipped)
  • 🔋 Batteries included - Available for React, Next, Express and Fastify (can be used with Nuxt & SvelteKit but API may not be complete)
  • ⛑ React Query support - All queries, mutations and subscriptions use React Query

How does it compare?

REST

The caveat with REST is the lack of standardization. Simply put, REST is an architectural style of mapping URL paths to entities, and then exposing different HTTP methods to get or mutate said entities.

This approach is intuitive to most developers but it lacks standardization across the server and the client. Think of it as an imaginary obscured line between both parties. Not great for DX…

GraphQL

GraphQL on the other hand is standardized and was made for the exact problem cited above. It removes the line between server and client and provides an exact schema of what the server is intending to respond with.

The caveats however are the fact you have to learn GraphQL syntax and there’s a lot of overhead when setting up a project.

A look at some code

Defining a procedure

First of all, it’s best to define the schemas. In my case, I’ll be querying PokéAPI, which is using REST, so I’ll manually define the expected response using Zod.

const pokemonSchema = z.object({
  count: z.number(),
  next: z.string().nullish(),
  previous: z.string().nullish(),
  results: z.array(
    z.object({
      name: z.string(),
      url: z.string(),
    })
  ),
});

Then we can define a procedure to fetch pokemon, with an offset input for pagination.

export const pokemonRouter = createRouter().query("findAll", {
  // Create input schema
  input: z.object({
    offset: z.number(),
  }),
  async resolve({ input }) {
    const res = await (
      await fetch(`https://pokeapi.co/api/v2/pokemon?offset=${input.offset}`)
    ).json();

    // Validate the response
    return pokemonSchema.parse(res);
  },
});

In the above example, if the input or output criteria are not met, an error is thrown. All in just 30 lines of code!

We then have to link the pokemonRouter to the main router. This is also done with the tRPC function createRoute, and can be easily added with .merge().

import { pokemonRouter } from "./pokemon";

export const appRouter = createRouter()
  .transformer(superjson)
  .merge("pokemon.", pokemonRouter);

export type AppRouter = typeof appRouter;

As you can see, we’re exporting the type of appRouter, we need to do this so TypeScript can infer our procedures, and for autocomplete! We can easily create hooks to call our code with the line below (this is only necessary if using in Next or React):

export const trpc = createReactQueryHooks<AppRouter>();

We can then consume the data in a component like so:

const Home: NextPage = () => {
  // ...

  const { data, refetch } = trpc.useQuery(["pokemon.findAll", { offset }]);

  // ...
};

In my case, as I added pagination, I’ve got some extra functionality to make it work. As most of that stuff is React Query, I won’t include it here, but feel free to check out the source code here.

Thoughts

Overall, I strongly believe that tRPC is here to stay. As more and more businesses adopt this technology, we are going to see a stronger ecosystem and wider support across front-end and back-end frameworks.

If you want to give it a go, you can head over to their website to check out more about it.

Hope this helped 😃