Full Stack Development

tRPC



by Tomas Trescak t.trescak@westernsydney.edu.au

tRPC

/

Introduction

  • What is tRPC
  • Why Learn It?
  • REST
  • tRPC Twist
  • Pros & Cons

tRPC

/

Setup

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

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

export const appRouter = t.router({
  post: t.router({
    getPosts: t.procedure
      .input(z.object({ tag: z.string().optional() }))
      .query(({ input }) => {
        const posts = [
          { id: 1, title: "Hello tRPC", tag: "tech" },
          { id: 2, title: "Why TypeScript?", tag: "code" },
        ];
        return posts.filter((p) => !input.tag || p.tag === input.tag);
      }),
  }),
  admin: t.router({
    addPost: t.procedure
      .input(z.object({ title: z.string(), tag: z.string() }))
      .mutation(({ input, ctx }) => {
        if (!ctx.user?.id) throw new Error("Unauthorized");
        return { id: Date.now(), ...input };
      }),
  }),
});

export type AppRouter = typeof appRouter;
  

tRPC

/

Client Calls

    // utils/trpc.ts
import { createTRPCNext } from '@trpc/next';
import type { AppRouter } from '../server/router';
export const trpc = createTRPCNext<AppRouter>({ config: () => ({ 
  url: '/api/trpc' 
}) });

// pages/posts.tsx
import { trpc } from '../utils/trpc';
export default function Posts() {
  const { data: posts } = trpc.post.getPosts.useQuery({ tag: "tech" });
  return (
    <ul>
      {posts?.map((post) => (
        <li key={post.id}>{post.title} ({post.tag})</li>
      ))}
    </ul>
  );
}

// pages/admin.tsx
import { trpc } from '../utils/trpc';
export default function AdminPanel() {
  const mutation = trpc.admin.addPost.useMutation();
  const handleAdd = () => mutation.mutate({ title: "New Post", tag: "news" });
  return <button onClick={handleAdd}>Add Post</button>;
}
  

tRPC

/

Server Calls

    // server/trpc.ts
import { createContext } from './context';
import { appRouter } from './router';
export const trpc = appRouter.createCaller(
  await createContext({ req, res })
);

// app/api/latest.ts
import { trpc } from "server/trpc"

export default async function GET(req, res) {
  const posts = await trpc.post.getPosts({ tag: "news" });
  res.json(posts);
}
  

tRPC

/

Security

    // server/trpc.ts

export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
  if (!ctx.session || !ctx.session.user) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }
  return next({
    ctx: {
      // infers the `session` as non-nullable
      user: ctx.session.user
    }
  });
});

// server/router.ts

 const adminRouter = t.router({
  addPost: protectedProcedure
    .input(z.object({ title: z.string(), tag: z.string() }))
    .mutation(({ input, ctx }) => {
      const newPost = { id: Date.now(), ...input };
      return newPost; // In reality, save to a DB
    }),
});
  

tRPC

/

Wrap Up