DEV Community

Alex Spinov
Alex Spinov

Posted on

tRPC v11 Has a Free API You've Never Heard Of

tRPC lets you build end-to-end type-safe APIs without code generation. Version 11 brings server-sent events, streaming, and a new link architecture. Most developers only scratch the surface of what tRPC can do.

What's New in tRPC v11?

  • SSE support — real-time subscriptions via Server-Sent Events
  • Streaming — progressive data loading
  • New link system — composable middleware for the client
  • FormData — native form handling

The Hidden API: Advanced Patterns

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

const t = initTRPC.context<Context>().create();

// Middleware with typed context augmentation
const authMiddleware = t.middleware(async ({ ctx, next }) => {
  if (!ctx.user) throw new TRPCError({ code: 'UNAUTHORIZED' });
  return next({ ctx: { ...ctx, user: ctx.user } });
});

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

// Streaming responses
const appRouter = t.router({
  // Standard query
  user: protectedProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input, ctx }) => {
      return ctx.db.user.findUnique({ where: { id: input.id } });
    }),

  // SSE subscription (new in v11)
  onNewMessage: protectedProcedure
    .subscription(async function* ({ ctx }) {
      for await (const msg of ctx.messageStream) {
        yield msg;
      }
    }),

  // Streaming query
  searchResults: t.procedure
    .input(z.object({ query: z.string() }))
    .query(async function* ({ input }) {
      const results = await searchEngine(input.query);
      for (const result of results) {
        yield result; // Stream results as they come
      }
    }),
});
Enter fullscreen mode Exit fullscreen mode

Client-Side Link API

import { createTRPCClient, httpBatchLink, splitLink, httpSubscriptionLink } from '@trpc/client';

const client = createTRPCClient<AppRouter>({
  links: [
    splitLink({
      condition: (op) => op.type === 'subscription',
      true: httpSubscriptionLink({ url: '/api/trpc' }),
      false: httpBatchLink({ url: '/api/trpc' }),
    }),
  ],
});

// Type-safe calls — autocomplete everything
const user = await client.user.query({ id: '123' });

// Real-time subscription
const unsub = client.onNewMessage.subscribe(undefined, {
  onData: (msg) => console.log('New:', msg),
});
Enter fullscreen mode Exit fullscreen mode

React Query Integration

import { createTRPCReact } from '@trpc/react-query';

const trpc = createTRPCReact<AppRouter>();

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

  // Optimistic updates
  const utils = trpc.useUtils();
  const mutation = trpc.updateUser.useMutation({
    onSuccess: () => utils.user.invalidate({ id }),
  });

  return data ? <div>{data.name}</div> : <Spinner />;
}
Enter fullscreen mode Exit fullscreen mode

Quick Start

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

Why Teams Love tRPC

A full-stack developer shared: "We deleted our entire OpenAPI spec, all codegen scripts, and 200+ lines of type definitions. tRPC gives us end-to-end type safety with zero code generation. Refactoring an API endpoint shows errors in the client instantly."


Building full-stack apps? Email spinov001@gmail.com or check my developer tools.

Using tRPC? How do you handle API type safety?

Top comments (0)