TrueSpec

tRPC End-to-End Type Safety

tRPC router, procedures, React Query integration, and error handling

What You’ll Build

After following this guide, you will have a working implementation of trpc end-to-end type safety in your project. Build a fully type-safe API layer with tRPC — no code generation, no schemas, no REST/GraphQL boilerplate. Define your API procedures on the server and call them from the client with full TypeScript autocompletion. Changes to your API are instantly reflected in your frontend types.

Use Cases & Problems Solved

  • Build reliable server endpoints that clients can consume consistently
  • Handle common backend patterns like routing, middleware, and error handling
  • Provide a clean interface between your frontend and data layer

Prerequisites

  • Next.js 14+ with App Router
  • TypeScript

Step-by-Step Implementation

Install tRPC

The following snippet shows how to install trpc. Copy this into your project and adjust the values for your environment.

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

Define your tRPC router

The following snippet shows how to define your trpc router. Copy this into your project and adjust the values for your environment.

// server/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.context<{ userId?: string }>().create();

export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
  if (!ctx.userId) throw new TRPCError({ code: 'UNAUTHORIZED' });
  return next({ ctx: { userId: ctx.userId } });
});

// server/routers/app.ts
export const appRouter = router({
  hello: publicProcedure
    .input(z.object({ name: z.string() }))
    .query(({ input }) => ({ greeting: \`Hello \${input.name}\` })),

  createPost: protectedProcedure
    .input(z.object({ title: z.string().min(1), content: z.string() }))
    .mutation(async ({ input, ctx }) => {
      return db.post.create({ data: { ...input, authorId: ctx.userId } });
    }),
});

export type AppRouter = typeof appRouter;

Call from React client with full types

The following snippet shows how to call from react client with full types. Copy this into your project and adjust the values for your environment.

import { trpc } from '../utils/trpc';

export default function HomePage() {
  // Fully typed! ctrl+space shows all available procedures
  const hello = trpc.hello.useQuery({ name: 'World' });
  const createPost = trpc.createPost.useMutation();

  return (
    <div>
      <p>{hello.data?.greeting}</p>
      <button onClick={() => createPost.mutate({ title: 'New', content: 'Hello' })}>
        Create Post
      </button>
    </div>
  );
}

⚠️ Don’t Do This

❌ Importing server code directly into client components

// DO NOT import server modules in client code!
import { db } from '../server/db'; // Bundles your DB client into the browser!

✅ Only import the AppRouter TYPE — never server implementations

// Only import the TYPE — stripped at compile time
import type { AppRouter } from '../server/routers/app';
// The actual server code stays server-side

Testing

Add these tests to verify your API endpoints work correctly:

// __tests__/api.test.ts
import { describe, it, expect } from 'vitest';

describe('tRPC End-to-End Type Safety', () => {
  it('should return 200 for valid requests', async () => {
    const res = await fetch('/api/endpoint', { method: 'GET' });
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data).toBeDefined();
  });

  it('should return 400 for invalid input', async () => {
    const res = await fetch('/api/endpoint', {
      method: 'POST',
      body: JSON.stringify({}),
    });
    expect(res.status).toBe(400);
  });

  it('should handle errors gracefully', async () => {
    const res = await fetch('/api/endpoint/nonexistent');
    expect(res.status).toBe(404);
  });
});

Verification

npm run dev
# Visit your page, browser console should show no errors
# Verify autocompletion: in your editor, type 'trpc.' and see all procedures
# Test mutation and verify data persists

Related Specs

Intermediate

Background Jobs with BullMQ

Queue setup, workers, retries, job scheduling, and monitoring dashboard

API & Backend
Advanced

Secure Webhook Handler

Signature verification, idempotency, retry handling, and queue processing

API & Backend
Intermediate

Production-Ready REST API (Express)

Error handling, Zod validation, rate limiting, CORS, and structured logging

API & Backend