DEV Community

Alex Spinov
Alex Spinov

Posted on

tRPC v11 Has a Free Type-Safe API Layer — End-to-End Types Without Code Generation

GraphQL needs code generation. REST needs manual types. tRPC gives you end-to-end type safety by simply importing your server types into your client. Zero code gen.

What is tRPC?

tRPC lets you build fully type-safe APIs without schemas, code generation, or runtime validation at the boundary. Your TypeScript types flow from server to client automatically.

What's New in tRPC v11

1. Simplified Router Definition

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

const t = initTRPC.create();

export const appRouter = t.router({
  getUser: t.procedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input }) => {
      return await db.users.findUnique({ where: { id: input.id } });
    }),

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

  listPosts: t.procedure
    .input(z.object({ limit: z.number().default(10), cursor: z.string().optional() }))
    .query(async ({ input }) => {
      const posts = await db.posts.findMany({
        take: input.limit + 1,
        cursor: input.cursor ? { id: input.cursor } : undefined,
      });
      return {
        items: posts.slice(0, input.limit),
        nextCursor: posts[input.limit]?.id,
      };
    }),
});

export type AppRouter = typeof appRouter;
Enter fullscreen mode Exit fullscreen mode

2. Client With Full Autocomplete

// client.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server/router';

const trpc = createTRPCClient<AppRouter>({
  links: [httpBatchLink({ url: 'http://localhost:3000/api/trpc' })],
});

// Full type inference — no code generation!
const user = await trpc.getUser.query({ id: '123' });
// user is typed as { id: string, name: string, email: string }

const newUser = await trpc.createUser.mutate({ name: 'Alice', email: 'a@b.com' });
Enter fullscreen mode Exit fullscreen mode

3. React Query Integration

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

function UserProfile({ userId }: { userId: string }) {
  const { data: user, isLoading } = trpc.getUser.useQuery({ id: userId });

  if (isLoading) return <Skeleton />;

  return <h1>{user?.name}</h1>;
}

function CreateUserForm() {
  const utils = trpc.useUtils();
  const mutation = trpc.createUser.useMutation({
    onSuccess: () => utils.listUsers.invalidate(),
  });

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      mutation.mutate({ name: 'Bob', email: 'bob@example.com' });
    }}>
      <button>Create User</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

4. Subscriptions (Real-Time)

// Server
const appRouter = t.router({
  onNewMessage: t.procedure.subscription(async function* ({ ctx }) {
    for await (const message of ctx.messageStream) {
      yield message;
    }
  }),
});

// Client
const subscription = trpc.onNewMessage.subscribe(undefined, {
  onData: (message) => console.log('New message:', message),
});
Enter fullscreen mode Exit fullscreen mode

5. Middleware

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

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

const appRouter = t.router({
  secretData: protectedProcedure.query(async ({ ctx }) => {
    return await getSecretData(ctx.user.id);
  }),
});
Enter fullscreen mode Exit fullscreen mode

tRPC vs REST vs GraphQL

tRPC REST GraphQL
Type safety Automatic Manual Code gen required
Schema None (inferred) OpenAPI (optional) SDL required
Code gen None Optional Required
Bundle size Minimal Minimal Apollo (~40KB)
Learning curve Low (just TS) Low Moderate
Use case TS fullstack Any Multi-client

Getting Started

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

The Bottom Line

tRPC is the fastest way to build a type-safe API in TypeScript. No schemas, no code generation — just import your server types and get full autocomplete on the client.


Need data solutions? I build scraping tools. Check my Apify actors or email spinov001@gmail.com.

Top comments (0)