The Talent500 Blog
Getting Started with TRPC: A Type-safe API Layer for TypeScript 1

Getting Started with TRPC: A Type-safe API Layer for TypeScript

TypeScript has completely changed how you write JavaScript code by introducing type safety. It’s a robust tool that helps you detect and prevent potential errors on the fly by enforcing type information when interacting with functions and objects. But what about your APIs? Can they be made type-safe as well?

TypeScript makes your JavaScript code type-safe. Now, you might wish for something that makes your API type-safe too. tRPC has you covered. With tRPC, you can enjoy the benefits of type-safety not just in your code, but also while dealing with APIs. 

In this beginner-friendly blog on tRPC, we will learn how to get started with tRPC and make your APIs type-safe.

So let’s get started!

What is tRPC

The tRPC, or TypeScript Relay Remote Procedure Call, is a TypeScript-based tool for building type-safe APIs. tRPC makes it easy to create endpoints that you can safely use in both your front end and back end. It acts as an end-to-end type-safe API layer, ensuring that the data you send and receive through your APIs is of the correct type.

Some of the key features of tRPC are as follows:

  • Framework agnostic: tRPC is a useful tool for building APIs that seamlessly integrate with your favorite JavaScript frameworks like React, Angular, and Vue. You can easily consume API endpoints in your front-end applications, no matter which framework you choose.
  • Error handling: tRPC also makes handling errors a breeze. It offers robust error-handling mechanisms that simplify communication between your client and server. The tRPC also leverages TypeScript types to ensure type safety, catching potential errors during development rather than at runtime.
  • Autocompletion:  One of the biggest time-savers with tRPC is its autocompletion feature. Based on your API schema, tRPC generates code for you, reducing development time and making your codebase more maintainable.
  • API complexity: tRPC simplifies API development by providing a clear and intuitive API design. This means you can focus on building your application logic without getting bogged down in complex API structures.
  • Boilerplate code: tRPC helps you write less code. It minimizes boilerplate, allowing you to build APIs faster and more efficiently.

Getting Started with TRPC

Let’s now look at how we can set up tRPC in our project. To get started with tRPC, you’ll need to install the required dependencies and set up a new project.

npm install @trpc/server@next @trpc/client@next

Creating a TRPC API

tRPC consists of three main components: Routers, Procedures, and Inputs. Let’s now discuss each of them in more detail.

Routers

Routers are like the backbone of your tRPC API. They organize and group related procedures together, similar to how routes work in traditional web frameworks. Routers define the structure of your API endpoints.

Example:

import { initTRPC } from “@trpc/server”;

const t = initTRPC.create();

const userRouter = t.router({

  getUser: t.procedure.query(() => {

    // Logic to get a user

    return { id: 1, name: ‘John Doe’ };

  }),

});

const appRouter = t.router({

  user: userRouter,

});

export type AppRouter = typeof appRouter;

You can have multiple routers to group related procedures and nest routers to create a hierarchical structure. In this example, appRouter includes a nested userRouter which contains a procedure getUser.

Procedures

Procedures are the functions that handle API requests and return responses. These procedures can be categorized into two types: queries and mutations.  Queries are designed for read-only operations, allowing you to retrieve data from your server without making any changes. Mutations, on the other hand, are used for modifying data on the server. 

Example

import { initTRPC } from “@trpc/server”;

const t = initTRPC.create();

const userRouter = t.router({

  getUser: t.procedure.query(() => {

    // Logic to get a user

    return { id: 1, name: ‘John Doe’ };

  }),

  createUser: t.procedure.mutation((input) => {

    // Logic to create a user

    return { id: 2, name: input.name };

  }),

});

export const appRouter = t.router({

  user: userRouter,

});

export type AppRouter = typeof appRouter;

Procedures handle the logic for API endpoints, processing inputs and returning outputs. In the example above, we have both queries and mutations.

Inputs

Inputs define the types of data that procedures expect. By specifying inputs, you ensure that your procedures receive the correct type of data, leveraging TypeScript’s type safety.

Often, tools like Zod are used along with tRPC to further validate the structure and validity of incoming inputs, adding an extra layer of protection against unexpected data.

Example:

import { initTRPC } from “@trpc/server”;

import { z } from ‘zod’;

const t = initTRPC.create();

const userRouter = t.router({

  getUser: t.procedure

    .input(z.object({ id: z.number() }))

    .query(({ input }) => {

      // Logic to get a user by id

      return { id: input.id, name: ‘John Doe’ };

    }),

  createUser: t.procedure

    .input(z.object({ name: z.string() }))

    .mutation(({ input }) => {

      // Logic to create a user with input.name

      return { id: 2, name: input.name };

    }),

});

export const appRouter = t.router({

  user: userRouter,

});

export type AppRouter = typeof appRouter;

In this example, getUser expects an input with an id of type number, and createUser expects an input with a name of type string. With the help of Zod, we can guarantee that the type we expect is the type we receive.

By using these three components, tRPC allows you to build a fully type-safe API, by making use of TypeScript’s strengths across both the client and server sides of your application.

Let’s now take a look at how to implement tRPC in a Next.js project.

Using tRPC with Next.js

Next.js is a React-based framework for building web applications. To use tRPC with Next.js, you need to first install the necessary dependencies.

Install dependencies 

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

Initializing tRPC 

Inside the src folder, create a new directory and name it as server. Inside the server directory, create a file trpc.ts and the following code there

import { initTRPC } from “@trpc/server”;

const t = initTRPC.create();

export const router = t.router;

export const publicProcedure = t.procedure;

Create a file named appRouter.ts in the server directory:

 

import { router, publicProcedure } from ‘./trpc’;

export const appRouter = router({

  hello: publicProcedure.query(async () => ‘Hello, world!’),

});

// Export type definition of API

export type AppRouter = typeof appRouter;

This creates a router instance called appRouter using functions imported from tRPC. It defines a single public query procedure hello, which asynchronously returns the string ‘Hello, world!’.

Now create a file named route.ts in the /api/trpc/[trpc] directory:

Getting Started with TRPC: A Type-safe API Layer for TypeScript 2

Add the following code in there, which exports a handler function that serves GET and POST requests.

import { fetchRequestHandler } from “@trpc/server/adapters/fetch”;

import { appRouter } from “@/app/server/appRouter”;

const handler = async (req: Request) => {

  return fetchRequestHandler({

    endpoint: “/api/trpc”,

    req,

    router: appRouter,

    createContext: () => ({}),

  });

};

export { handler as GET, handler as POST };

Now you can go to http://localhost:3000/api/trpc/hello and see if you can get the response.

Getting Started with TRPC: A Type-safe API Layer for TypeScript 3

Now, we need to connect this to our client. You can create a tRPC client for that. Note that our folder name is _trpc so the Next.js router will ignore it when routing from the client side. This is just a Next.js convention.

Getting Started with TRPC: A Type-safe API Layer for TypeScript 4

Paste the following code on the client.ts file

import { createTRPCReact } from “@trpc/react-query”;

import { type AppRouter } from “@/app/server/appRouter”;

export const trpc = createTRPCReact<AppRouter>({});

You can hover over the Approuter and see the hello procedure there, meaning your backend APIs and frontend can interact

Now, we can create provider.tsx in the _trpc directory.

Getting Started with TRPC: A Type-safe API Layer for TypeScript 5

“use client”;

import { QueryClient, QueryClientProvider } from “@tanstack/react-query”;

import { httpBatchLink } from “@trpc/client”;

import React, { useState } from “react”;

import { trpc } from “./client”;

export default function Provider({ children }: { children: React.ReactNode }) {

  const [queryClient] = useState(() => new QueryClient({}));

  const [trpcClient] = useState(() =>

    trpc.createClient({

      links: [

        httpBatchLink({

          url: “http://localhost:3000/api/trpc”,

        }),

      ],

    })

  );

  return (

    <trpc.Provider client={trpcClient} queryClient={queryClient}>

      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>

    </trpc.Provider>

  );

}

The provider.tsx file has both the queryClient and trpcClient. And then we wrap the children inside the provider. Now we can add our provider to the main app. On the layout.js file, wrap the children with the provider.

import type { Metadata } from “next”;

import { Inter } from “next/font/google”;

import “./globals.css”;

const inter = Inter({ subsets: [“latin”] });

import Provider from “./_trpc/provider”;

export const metadata: Metadata = {

  title: “Create Next App”,

  description: “Generated by create next app”,

};

export default function RootLayout({

  children,

}: Readonly<{

  children: React.ReactNode;

}>) {

  return (

    <html lang=“en”>

      <body className={inter.className}>

        <Provider>{children}</Provider>

      </body>

    </html>

  );

}

Now, you can access your endpoint from the frontend and retrieve data in a type-safe manner.

“use client”;

import Image from “next/image”;

import { trpc } from “@/app/_trpc/client”;

export default function Home() {

  const getHelloMessage = trpc.hello.useQuery();

  return (

    <main className=“flex min-h-screen flex-col items-center justify-between p-24”>

      <h1 className=“text-4xl font-bold”>

        {JSON.stringify(getHelloMessage.data)}

      </h1>

    </main>

  );

}

 

Output

Getting Started with TRPC: A Type-safe API Layer for TypeScript 6

Output on localhost:3000

Wrapping it up.

Thanks for checking out this blog. In this detailed blog on tRPC, we have looked into the important aspects of tRPC that you should know while building typesafe APIs.

Using tRPC, developers can enjoy a better developer experience and seamless API integration between the front end and back end of their applications. This reduces the likelihood of runtime errors and enhances the overall development process.

So, give tRPC a try and see how it can transform your approach to building APIs. 

Happy coding!

0
Taniya Pan

Taniya Pan

Add comment