tRPC setup for NextJs app router

tRPC setup for NextJs app router

What is tRPC?

tRPC stands for Typescript Remote Procedure Call. Instead of producing APIs from backend tRPC can directly infers and applies your typescript endpoint. With the help of tRPC you can build typesafe APIs without schemas or code generation, you have a better way to add statically type API endpoint and share those type between our client and server.

Now let's start our setup in Nextjs app-router. I am using PostgreSQL with Prisma ORM and zod for runtime schema validation, you can add drizzle ORM and other database like mongoDB.

Installing tRPC

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

The first step to create a tRPC server is to initialize a server.

Create a folder in your src directory server/index.ts

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

const t = initTRPC.create();
export const router = t.router;
export const publicProcedure = t.procedure

The second step is to define procedures.

Create a file index.ts in your server folder server/index.ts

import { publicProcedure, router } from "./trpc";

export const appRouter = router({
   getPost: publicProcedure.query(async () => {
    const posts = await prisma.post.findMany();
    return posts;
  }),
});

export type AppRouter = typeof router;

Procedures are the functions used to build our backend. They are composable and can be queries, mutations, or subscriptions. AppRouter contains multiple procedures, which we will understand once our setup is done. At the end of the file, we export the type of the router so we can use it in our frontend code in just a few moments.

The third step is to create a route handler.

Since we are using app-router in Next.js, we aim to create a route handler in our api folder.

The folder structure should be src/app/api/trpc/[trpc]/route.ts. The [trpc] folder is dynamic, as we are adding multiple procedures that Next.js automatically includes in [trpc]. For example, if we create a getPost procedure, you can access it via http://localhost:3000/api/getPost.

import { appRouter } from "@/server";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";

const handler = (req: Request) =>
  fetchRequestHandler({
    endpoint: `api/trpc`,
    req,
    router: appRouter,
    createContext: () => ({}),
  });
export { handler as GET, handler as POST };

We create our HTTP server using our appRouter, thus having a tRPC server running. In simpler terms, we establish a bridge to connect tRPC with the Next.js route handler.

Connect your client component

To facilitate client component interaction with tRPC, we create a client query. Create a folder in the app directory called app/_trpc/client.ts. We use _trpc because we don't add a trpc route directly.

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

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

After creating a client query, we need a provider to supply tRPC to the client component. You can create a provider file in your component folder. I'll create mine in the _trpc folder as Provider.tsx. After setting up the client, we proceed to set up our server component.

"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>
  );
}

And wrap this file in your root layout.tsx

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Provider from "./_trpc/provider";

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

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, we are ready to use tRPC in the client component.

Let's start by initializing a tRPC in your client component. This will provide you with automatic suggestions of your procedures.

"use client";

import { trpc } from "@/app/_trpc/client";

const ShowPost = () => {
  const post = trpc.getPost.useQuery();
  console.log(post.data);
  return (
    <div className="container flex border-2 border-red-300 flex-wrap">
      {post &&
        post.data?.map((posts, indx) => (
          <div key={indx}>
            <h5>{posts.title}</h5>
          </div>
        ))}
    </div>
  );
};

export default ShowPost;

You can check the type of post.

Connect your server component.

Now, let's create a setup for the server component. Create a file named serverClient.ts in the _trpc folder.

import { appRouter } from "@/server";
import { httpBatchLink } from "@trpc/client";

export const serverClient = appRouter.createCaller({
  links: [
    httpBatchLink({
      url: "http://localhost:3000/api/trpc",
    }),
  ],
});

Now, use it in our root page.tsx file. If you are using serverClient, initialize it with serverClient, not with trpc.

import { serverClient } from "./_trpc/serverClient";

export default async function Home() {
  const post = await serverClient.getPost();
  return (
    <main className="">
      {post &&
        post.map((posts, indx) => (
          <div key={indx}>
            <h4>{posts.title}</h4>
          </div>
        ))}
    </main>
  );
}

"Thank you for reading my first blog. If you have any suggestions, please share them with me. It will help me improve my blogging journey."