Building Type-Safe Full-Stack Apps with Next.js 15 and tRPC in 2026

Type safety from database to UI eliminates an entire class of bugs. Next.js 15 combined with tRPC gives you end-to-end type safety with zero code generation. Here is how to build a modern full-stack application.

Why tRPC?

tRPC lets you call server functions directly from the client with full TypeScript autocompletion. No REST endpoints, no GraphQL schemas, no code generation.

Project Setup

npx create-next-app@latest my-app --typescript
cd my-app
npm install @trpc/server @trpc/client @trpc/next @trpc/react-query
npm install @tanstack/react-query zod superjson

Define Your Router

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

const t = initTRPC.create({ transformer: superjson });

export const router = t.router;
export const publicProcedure = t.procedure;
// server/routers/user.ts
import { router, publicProcedure } from "../trpc";
import { z } from "zod";

export const userRouter = router({
  getAll: publicProcedure.query(async () => {
    return await db.user.findMany();
  }),
  
  getById: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input }) => {
      return await db.user.findUnique({ where: { id: input.id } });
    }),
  
  create: publicProcedure
    .input(z.object({
      name: z.string().min(2),
      email: z.string().email(),
    }))
    .mutation(async ({ input }) => {
      return await db.user.create({ data: input });
    }),
});

Client Usage

// app/users/page.tsx
"use client";
import { trpc } from "@/utils/trpc";

export default function UsersPage() {
  const { data: users, isLoading } = trpc.user.getAll.useQuery();
  const createUser = trpc.user.create.useMutation({
    onSuccess: () => utils.user.getAll.invalidate(),
  });

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      {users?.map(user => (
        <div key={user.id}>{user.name} - {user.email}</div>
      ))}
      <button onClick={() => createUser.mutate({
        name: "New User",
        email: "new@example.com"
      })}>
        Add User
      </button>
    </div>
  );
}

Server-Side Calls

// app/users/[id]/page.tsx (Server Component)
import { createCaller } from "@/server/routers";

export default async function UserPage({ params }: { params: { id: string } }) {
  const caller = createCaller({});
  const user = await caller.user.getById({ id: params.id });
  return <div>{user?.name}</div>;
}

Authentication Middleware

const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
  if (!ctx.session?.user) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }
  return next({ ctx: { user: ctx.session.user } });
});

Benefits

  • Change a server function signature and TypeScript catches all client errors instantly
  • Full autocompletion for inputs and outputs
  • No API documentation needed — the types ARE the documentation
  • Smaller bundle than GraphQL clients
  • Works perfectly with React Query for caching

Conclusion

tRPC with Next.js 15 is the most productive way to build full-stack TypeScript applications. The developer experience of end-to-end type safety is transformative.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Privacy Policy · Contact · Sitemap

© 7Tech – Programming and Tech Tutorials