[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 😃
Top comments (3)
No worries, sorry for the late reply :)
You wouldn't be able to add tRPC natively to your Flutter app, but you could spin up an Express or Fastify API and add tRPC that way. Works just as well. The only downside is that you're using Flutter, which can't take advantage of TypeScript. Another solution is to create a React Native app with TypeScript and add tRPC in there.
Hope that helps :)
Yes! The TypeScript support is only supported if you're using TS. Otherwise, you can just use the package as you normally would :)
hey @allobrox how did this configuration go?
please can you point me to resources on how to connect your trpc server to flutter?
Or how to get an external platform get data from your trpc server
@herbievine I'd also love your input.
Thank you