DEV Community

Alex Spinov
Alex Spinov

Posted on

tRPC Has a Free Type-Safe API Layer That Eliminates REST and GraphQL Boilerplate

REST needs schemas, validators, and code generation for type safety. GraphQL needs schemas, resolvers, and code generation for type safety. tRPC gives you end-to-end type safety with zero code generation, zero schemas, and zero boilerplate.

What tRPC Gives You for Free

  • End-to-end type safety — change a server function, client gets type errors instantly
  • Zero code generation — types flow automatically from server to client
  • No schema files — your TypeScript IS the schema
  • Subscriptions — real-time updates via WebSocket
  • Batching — multiple requests in one HTTP call
  • Works with Next.js, SvelteKit, Remix, Express, Fastify, SolidStart

Quick Start

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

Server Setup

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

const t = initTRPC.create();

export const router = t.router;
export const publicProcedure = t.procedure;
Enter fullscreen mode Exit fullscreen mode
// 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 }) => {
      const user = await db.users.find(input.id);
      return user; // TypeScript infers the return type
    }),

  create: publicProcedure
    .input(z.object({
      name: z.string().min(2),
      email: z.string().email(),
    }))
    .mutation(async ({ input }) => {
      return await db.users.create({ data: input });
    }),

  list: publicProcedure
    .input(z.object({
      limit: z.number().min(1).max(100).default(10),
      cursor: z.string().optional(),
    }))
    .query(async ({ input }) => {
      const users = await db.users.findMany({
        take: input.limit + 1,
        cursor: input.cursor ? { id: input.cursor } : undefined,
      });
      return {
        items: users.slice(0, input.limit),
        nextCursor: users[input.limit]?.id,
      };
    }),
});
Enter fullscreen mode Exit fullscreen mode

Client Usage (Fully Typed)

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

export function UserProfile({ userId }: { userId: string }) {
  // TypeScript knows: data is { id, name, email, ... } | undefined
  const { data: user, isLoading } = trpc.user.getById.useQuery({ id: userId });

  // TypeScript knows: mutate takes { name: string, email: string }
  const createUser = trpc.user.create.useMutation();

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

  return (
    <div>
      <h1>{user?.name}</h1>
      <p>{user?.email}</p>
      <button onClick={() => createUser.mutate({ name: 'New', email: 'new@test.com' })}>
        Create User
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Change the server return type → client immediately shows type errors. No code generation needed.

Middleware (Authentication, Logging)

const isAuthed = t.middleware(async ({ ctx, next }) => {
  if (!ctx.session?.user) {
    throw new TRPCError({ code: 'UNAUTHORIZED' });
  }
  return next({ ctx: { user: ctx.session.user } });
});

const protectedProcedure = t.procedure.use(isAuthed);

// Now ctx.user is typed and guaranteed to exist
export const settingsRouter = router({
  update: protectedProcedure
    .input(z.object({ theme: z.enum(['light', 'dark']) }))
    .mutation(async ({ ctx, input }) => {
      return await db.settings.update({
        where: { userId: ctx.user.id }, // ctx.user is typed!
        data: { theme: input.theme }
      });
    })
});
Enter fullscreen mode Exit fullscreen mode

tRPC vs REST vs GraphQL

Feature tRPC REST GraphQL
Type safety Automatic Manual (codegen) Manual (codegen)
Schema files None OpenAPI spec .graphql files
Code generation None Required for types Required for types
Overfetching N/A (RPC) Common Solved
Learning curve Know TS → ready Low High
Best for TS full-stack apps Public APIs Complex data graphs

The Verdict

tRPC is the fastest path to a type-safe full-stack TypeScript application. No schemas, no codegen, no boilerplate — just TypeScript functions on the server that the client calls with full type inference.


Need help building production web scrapers or data pipelines? I build custom solutions. Reach out: spinov001@gmail.com

Check out my awesome-web-scraping collection — 400+ tools for extracting web data.

Top comments (0)