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 2. Install Required Packages npm install @trpc/server @trpc/client @trpc/react-query @trpc/next zod npm install @tanstack/react-query 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; 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"}!`, }; }), }); 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; 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: () => ({}), }); 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(); 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 ( ); } 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 Loading...; return ( {hello.data.greeting} ); } 10. Run the App npm run dev 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

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
2. Install Required Packages
npm install @trpc/server @trpc/client @trpc/react-query @trpc/next zod
npm install @tanstack/react-query
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;
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"}!`,
};
}),
});
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;
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: () => ({}),
});
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>();
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>
);
}
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>
);
}
10. Run the App
npm run dev
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