Skip to content
Learni
View all tutorials
API & Backend

How to Create a Type-Safe API with tRPC in 2026

18 minBEGINNER
Lire en français

Introduction

tRPC allows you to create TypeScript APIs without writing OpenAPI definitions or manual contracts. The typing is automatically shared between the client and the server. This approach eliminates runtime errors related to data and speeds up development. In 2026, tRPC remains the preferred choice for Next.js projects seeking simplicity and maximum type safety.

Prerequisites

  • Node.js 20 or higher
  • Next.js 15 with App Router
  • Basic knowledge of TypeScript
  • npm or pnpm installed

Initialize the Next.js Project

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

This command creates a fresh Next.js project and installs the essential tRPC packages along with Zod for validation.

Create the Main tRPC Router

src/server/trpc.ts
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';

const t = initTRPC.create({
  transformer: superjson,
});
export const router = t.router;
export const publicProcedure = t.procedure;

We initialize tRPC with SuperJSON to support complex types like Date. The router and procedure exports will be reused throughout the project.

Define the API Routes

src/server/routers/user.ts
import { router, publicProcedure } from '../trpc';
import { z } from 'zod';

export const userRouter = router({
  getById: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input }) => {
      return { id: input.id, name: 'Alice', email: 'alice@example.com' };
    }),
  create: publicProcedure
    .input(z.object({ name: z.string().min(2) }))
    .mutation(async ({ input }) => {
      return { id: 'new-id', ...input };
    }),
});

Each procedure is typed thanks to Zod. getById is a query and create is a mutation. Typing is automatic on the client side.

Assemble the Root Router

src/server/routers/_app.ts
import { router } from '../trpc';
import { userRouter } from './user';

export const appRouter = router({
  user: userRouter,
});
export type AppRouter = typeof appRouter;

The main router combines all sub-routers. The AppRouter type is exported for the client.

Create the Next.js API Handler

src/app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from '@/server/routers/_app';

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

This file exposes all tRPC procedures via Next.js App Router. No additional configuration is required.

Configure the React Provider

src/app/providers.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpBatchLink } from '@trpc/client';
import { createTRPCReact } from '@trpc/react-query';
import { AppRouter } from '@/server/routers/_app';
import { useState } from 'react';

export const trpc = createTRPCReact<AppRouter>();
export function TRPCProvider({ children }: { children: React.ReactNode }) {
  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}>{children}</QueryClientProvider>
    </trpc.Provider>
  );
}

The provider connects React Query and tRPC. Place it in the root layout so all components have access to it.

Best Practices

  • Always use Zod to validate inputs
  • Separate routers by business domain
  • Enable SuperJSON from the start to support Dates and Maps
  • Name your procedures clearly (get, create, update, delete)
  • Write integration tests for critical procedures

Common Mistakes to Avoid

  • Forgetting to export the AppRouter type on the server
  • Not placing the TRPCProvider in the root layout
  • Using procedures without input when data is expected
  • Ignoring Zod validation errors that don't surface properly on the client

Go Further

Discover our complete tRPC and Next.js courses at Learni Group.