Jagadhiswaran Devaraj

Feb 14, 2025 • 5 min read

tRPC: A Simple and Type-Safe Way to Build APIs in TypeScript

Eliminate API Boilerplate and Build Faster with End-to-End Type Safety

If you’ve ever built an application, you know how tedious it can be to set up APIs. With REST or GraphQL, you have to define API routes, handle requests, manually validate inputs, and ensure the frontend and backend stay in sync. Over time, this can lead to a lot of unnecessary boilerplate and maintenance headaches.

tRPC offers a different approach—one that removes most of the overhead while keeping everything fully type-safe. In this article, we’ll break down how tRPC works, why it’s better than traditional methods, and how you can use it to build APIs faster and with fewer bugs.


What is tRPC?

tRPC stands for TypeScript Remote Procedure Call. Instead of manually defining API endpoints and sending HTTP requests, tRPC lets you call backend functions directly from your frontend—just like calling a local function in your code.

The best part? Everything is fully type-safe, from the backend to the frontend, without requiring additional tools like GraphQL or OpenAPI.

With tRPC, you don’t need to:

• Manually define API endpoints

• Write extra validation logic for request/response types

• Maintain separate frontend and backend types

Instead, your frontend automatically gets correct types from the backend, making development much faster and reducing the chances of errors.


How tRPC Works

At a high level, tRPC works by defining remote functions on the backend that the frontend can call just like normal TypeScript functions. These functions, called procedures, are grouped into a router, which acts as the API layer.

When the frontend calls one of these functions, it doesn’t need to make a manual API request. Instead, it interacts with the function as if it were part of the same codebase. Under the hood, tRPC handles the request-response cycle, ensuring everything remains type-safe.


Setting Up tRPC in a Next.js App

1. Install Dependencies

npm install @trpc/server @trpc/client @trpc/react-query @tanstack/react-query zod

@trpc/server: Backend support for tRPC

@trpc/client: Allows the frontend to call tRPC functions

@trpc/react-query: Integration with React Query for caching and state management

@tanstack/react-query: Handles data fetching and caching

zod: A validation library for defining input schemas


2. Create a tRPC Router

In the server/trpc.ts file, define a simple API

import { initTRPC } from "@trpc/server";
import { z } from "zod";

const t = initTRPC.create();

export const appRouter = t.router({
 getUser: t.procedure  
     .input(z.object({ id: z.string() }))
     .query(({ input }) => {
           return { id: input.id, name: "Alice" };    
       }),
});

export type AppRouter = typeof appRouter;

Here’s what’s happening:

• We initialize tRPC with initTRPC.create().

• We define a router (appRouter) that holds API functions.

• We create a procedure (getUser) that takes an ID as input and returns user data.

• We use Zod for input validation, ensuring the ID is always a string.

• The API function can be called directly from the frontend without worrying about types.


3. Expose the API in Next.js

To make this router accessible, create pages/api/trpc/[trpc].ts

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

export default createNextApiHandler({
   router: appRouter,
});

This automatically creates an API handler that allows the frontend to call tRPC functions without manually defining endpoints.


4. Set Up the Frontend Client

Inside utils/trpc.ts, create a tRPC client

import { createTRPCReact } from "@trpc/react-query";
import type { AppRouter } from "@/server/trpc";

export const trpc = createTRPCReact<AppRouter>();

Then, wrap the app with a provider in _app.tsx

import { trpc } from "@/utils/trpc";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

function MyApp({ Component, pageProps }) {
    return (
       <trpc.Provider client={queryClient}>
            <QueryClientProvider client={queryClient}>
                <Component {...pageProps} />
            </QueryClientProvider>
        </trpc.Provider>
    );
}

export default MyApp;

This makes tRPC available throughout the application.


5. Call the API from a component

Now, instead of using fetch or axios, we can tell the backend function like a regular function

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

export default function Home() {
    const { data, isLoading } = trpc.getUser.useQuery({ id: "123" });
     
     if (isLoading) return <p>Loading...</p>;
     return <p>User: {data?.name}</p>;
}

No need to manually define API calls - tRPC automatically infers the types and handles everything under the hood.


Why tRPC is Better Than REST or GraphQL

1. Type Safety Without Extra Setup

With REST or GraphQL, you have to manually define types for requests and responses. If something changes in the backend, you might not notice until you get a runtime error.

With tRPC, TypeScript enforces type safety end-to-end, so you get errors at compile time instead of runtime.

2. No Need to Write API Endpoints

With REST, you write API endpoints like /api/getUser, then handle requests manually.

With GraphQL, you define schemas, resolvers, and queries.

tRPC eliminates this overhead by letting you define functions directly in TypeScript—no separate API setup required.

3. Better Performance Than GraphQL

GraphQL is powerful but can be overkill for small projects. It requires an entire query engine, extra processing, and sometimes multiple database queries per request.

tRPC is much lighter—it only fetches what’s needed and doesn’t introduce extra complexity.

4. Works Seamlessly with React and Next.js

Since tRPC is built with TypeScript in mind, it integrates well with modern frontend frameworks. It works natively with Next.js API routes, making it easy to build full-stack applications.


How tRPC Improves Performance

  • Less Network Overhead – tRPC sends minimal data over the network, unlike GraphQL, which often fetches unnecessary fields.

  • No Need for Extra API Processing – Since there’s no intermediary schema like GraphQL, requests are processed faster.

  • Optimized Data Fetching – Built-in support for React Query allows efficient caching and background updates.

For most applications, tRPC performs better than GraphQL and requires far less setup than REST.


Conclusion

tRPC makes API development simpler, faster, and safer for TypeScript projects. Instead of writing separate API endpoints, tRPC allows you to call backend functions directly from the frontend—without extra schemas, type definitions, or validation logic.

If you’re building a TypeScript-based project, using tRPC can save time and reduce errors while improving performance.

If you haven’t tried tRPC yet, now is the perfect time to integrate it into your Next.js or React projects. It’s one of the easiest ways to build APIs without unnecessary complexity.

- Jagadhiswaran devaraj

Join Jagadhiswaran on Peerlist!

Join amazing folks like Jagadhiswaran and thousands of other people in tech.

Create Profile

Join with Jagadhiswaran’s personal invite link.

0

5

0