DEV Community

CodeWithDhanian
CodeWithDhanian

Posted on

How I Built Full-Stack TypeScript Apps Faster with tRPC and Next.js 15

When building full-stack apps, the constant juggling between backend and frontend types can slow you down. That’s why I started using tRPC—a powerful TypeScript library that lets you build end-to-end type-safe APIs without schemas like REST or GraphQL.

In this article, I’ll show you how I used tRPC with Next.js 15 to build a fully functional full-stack app faster and smarter.


Why tRPC?

Before we dive into the code, here’s why I love tRPC:

  • Full Type Safety: Your backend types automatically reflect on the frontend
  • No API Schemas: Forget about REST endpoints or GraphQL queries
  • Faster Dev Workflow: Just define functions and use them on the client

Project Stack

  • Framework: Next.js 15 (App Router Ready)
  • Language: TypeScript
  • Styling: TailwindCSS
  • Validation: Zod
  • Data Fetching: React Query

Let’s get started.


1. Create Your Next.js + TypeScript Project

npx create-next-app@latest trpc-fullstack-app --typescript
cd trpc-fullstack-app
Enter fullscreen mode Exit fullscreen mode

2. Install Required Packages

npm install @trpc/server @trpc/client @trpc/react-query @trpc/next zod
npm install @tanstack/react-query
Enter fullscreen mode Exit fullscreen mode

3. Setup Your tRPC Backend

Create a simple tRPC server that we can expand later.

src/server/api/trpc.ts

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

const t = initTRPC.create();

export const router = t.router;
export const publicProcedure = t.procedure;
Enter fullscreen mode Exit fullscreen mode

4. Define Example Router

src/server/api/routers/example.ts

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

export const appRouter = router({
  hello: publicProcedure
    .input(z.object({ name: z.string().optional() }))
    .query(({ input }) => {
      return {
        greeting: `Hello, ${input.name ?? "world"}!`,
      };
    }),
});
Enter fullscreen mode Exit fullscreen mode

5. Create the Root Router

src/server/api/root.ts

import { router } from "./trpc";
import { appRouter as exampleRouter } from "./routers/example";

export const appRouter = router({
  example: exampleRouter,
});

export type AppRouter = typeof appRouter;
Enter fullscreen mode Exit fullscreen mode

6. Set Up the API Handler

src/pages/api/trpc/[trpc].ts

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

export default createNextApiHandler({
  router: appRouter,
  createContext: () => ({}),
});
Enter fullscreen mode Exit fullscreen mode

7. Setup tRPC on the Client Side

src/utils/trpc.ts

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

export const trpc = createTRPCReact<AppRouter>();
Enter fullscreen mode Exit fullscreen mode

8. Wrap Your App with Providers

src/pages/_app.tsx

import { trpc } from "@/utils/trpc";
import { httpBatchLink } from "@trpc/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { AppProps } from "next/app";
import { useState } from "react";

export default function MyApp({ Component, pageProps }: AppProps) {
  const [queryClient] = useState(() => new QueryClient());
  const [trpcClient] = useState(() =>
    trpc.createClient({
      links: [
        httpBatchLink({
          url: "/api/trpc",
        }),
      ],
    })
  );

  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>
        <Component {...pageProps} />
      </QueryClientProvider>
    </trpc.Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

9. Use tRPC in a Page

src/pages/index.tsx

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

export default function Home() {
  const hello = trpc.example.hello.useQuery({ name: "Dhanian" });

  if (!hello.data) return <p>Loading...</p>;

  return (
    <div className="flex items-center justify-center h-screen text-2xl font-bold">
      {hello.data.greeting}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

10. Run the App

npm run dev
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:3000 and you should see:

Hello, Dhanian!


Conclusion

That’s it! You’ve just created a type-safe, full-stack app using tRPC and Next.js with minimal setup and zero duplication of types. This setup has boosted my productivity and made debugging way easier.

If you're building modern apps with TypeScript, tRPC is a game-changer worth trying.


Next Up?

  • Add Prisma for a typesafe database layer
  • Protect routes with authentication
  • Build and deploy to Vercel

Enjoyed this guide?

You can support my work or grab my premium developer ebooks at:

codewithdhanian.gumroad.com

Top comments (0)