Building Full-Stack TypeScript Applications Using tRPC | by Ashley Peacock | Nov, 2022

Enable typesafe integration between frontend and backend with NeXT and Express adapters

photo by den diner Feather unsplash

When building a full-stack application, chances are you reach out to either REST or GraphQL to provide API capabilities to your frontend. tRPC is seeking to replace this, or at least provide an alternative, by adding the benefits of type sharing between client and server.

Conspiracy happened? So was I when I saw tRPC in my Twitter feed, so I decided to take a closer look. In this article, we’ll look at what tRPC provides, and how it can simplify API development for your frontend applications.

In short, tRPC is a framework for building typesafe APIs using TypeScript. Unlike traditional APIs such as REST and GraphQL, tRPC does not have an API schema. Instead, the types you define in your backend API are accessible on the client-side.

This may sound a bit strange at first, but it actually makes a lot of sense if you consider that the main use case of tRPC is providing APIs in full-stack applications.

The schema is essential when it comes to HTTP calls between services in order to understand the contract between the client and the server. However, in the case of APIs for full-stack applications built with tRPC, types provide contracts.

tRPC itself is very lightweight, with no dependencies and framework-agnostic. Most mainstream frameworks have adapters for tRPC, including Next.js and Express,

Now that we understand what tRPC is, what problems does it solve?

As with any new(ish) technology, chances are it was created because someone had a problem that wasn’t solved by prior technologies.

In this case, the problem was creating the schema for the frontend API as well as all the plumbing associated with it. Even more, on the client side, you need to format requests and make HTTP calls. This mostly goes with tRPC, as it handles the plumbing for you. In short, it frees you, the engineer, to focus on the frontend display and backend logic without worrying about how the two talk to each other too much.

It introduces a sense of coupling between the frontend code and the backend code. However, because it’s TypeScript, your frontend code only knows that it’s going to receive an object that conforms to a certain interface. it means that we are following dependency inversion principleBased on the essence.

Other big selling points for tRPC are speed and security. As we don’t have to worry about schemas as they are implicit through types, integration between frontend and backend is faster.

For security, you can be sure that when you make changes to either the frontend or the backend, nothing is broken in terms of the data being used. If you rename a property User Whatever is returned from your API will be highlighted to update any client-side code that uses that property.

To end this article, I’m going to show a quick example. I’m not going to build anything with NeXT or Express to keep it simple. Instead, I’m going to show how to create an instantiated endpoint in tRPC and use it on the client-side.

Here’s server-side, implementing an endpoint to retrieve an animal from a list:

import  initTRPC  from '@trpc/server';

const t = initTRPC.create();

interface Animals
id: number,
type: string;

const listOfAnimals: Animals[] = [

id: 1,
type: 'Dog'

id: 2,
type: 'Cat'

const appRouter = t.router(
getAnimal: t.procedure
// The input is unknown at this time.
// A client could have sent us anything
// so we won't assume a certain data type.
.input((req: unknown) =>
//Here we should do some validation of the input
.query((req) =>
const input = req;
const animal = listOfAnimals.find((u) => === input);

return animal;

export type AppRouter = typeof appRouter;

Very simple, isn’t it? I know this is using dummy data, but I really enjoy how easy it is to create endpoints with tRPC. This is a simple query, which essentially maps to a GET request. It also supports mutation to create or modify data.

If we now want to call this endpoint on the client-side, the code is even simpler from the server:

import  createTRPCProxyClient, httpBatchLink  from '@trpc/client';
// This is our server definition from above, using import type
// is important, as it only imports type, not code, to avoid
// accidentally using server-side code
import type AppRouter from './server';

//This configures the client to talk to our server
const trpc = createTRPCProxyClient(
links: [
url: 'http://localhost:3000/trpc',

// Call the API
const animal = await trpc.getAnimal.query(1);

The code is well commented so it should explain what’s going on. Links are an interesting feature of tRPC, which you can read about Here, It is worth noting that nothing is running on the specified port, but if we use tRPC with Next.js, it will by default appear locally on that port.

At this point, you might be thinking that tRPC is a new framework. However, it’s a few years old and the most recently released version is 10, so it’s well maintained. However, with an increasing number of stars on GitHub, it is gaining momentum.

If I had to build an entirely new full-stack application, I’d definitely be willing to trial tRPC. It seems like a really simple but solid offering, with great integration with most of the major frameworks. its docs They are very good too.

Have you tried tRPC, or are you going to try it? Leave me a comment and tell me!

My book, Creating Software with Modern Diagramming Techniques, is out now!

click here Learn to draw diagrams to communicate information more directly and clearly than words. Using only text-based markup powered by Mermaid, create meaningful and engaging diagrams to document your domain, visualize user flows, reveal system architecture at any desired level, and much, much more!

Leave a Reply