{"id":119519,"date":"2026-06-30T10:09:53","date_gmt":"2026-06-30T04:39:53","guid":{"rendered":"https:\/\/www.guvi.in\/blog\/?p=119519"},"modified":"2026-06-30T10:09:54","modified_gmt":"2026-06-30T04:39:54","slug":"trpc-tutorial-build-type-safe-api","status":"publish","type":"post","link":"https:\/\/www.guvi.in\/blog\/trpc-tutorial-build-type-safe-api\/","title":{"rendered":"tRPC Tutorial: Build Type-Safe APIs Without the Headache"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\"><strong>&nbsp;TL;DR Summary&nbsp;<\/strong><\/h2>\n\n\n\n<ul>\n<li>tRPC lets you build fully type-safe APIs between your frontend and backend \u2014 no REST, no GraphQL needed.<\/li>\n\n\n\n<li>It works best with TypeScript and is a natural fit for Next.js and Node.js projects.<\/li>\n\n\n\n<li>You define procedures on the server, and your client calls them like regular functions.<\/li>\n\n\n\n<li>There&#8217;s no need to write separate API schemas or manually sync types between layers.<\/li>\n\n\n\n<li>This tutorial covers setup, routers, queries, mutations, and a working mini-project.<\/li>\n<\/ul>\n\n\n\n<p>tRPC is a TypeScript library that lets your frontend call backend functions directly \u2014 with full type safety, no code generation, and no REST or GraphQL boilerplate. You define procedures on the server, and your client picks them up automatically. It&#8217;s ideal for full-stack TypeScript projects where speed and type safety both matter.<\/p>\n\n\n\n<p>In this tRPC tutorial, you&#8217;ll learn what tRPC is, how it works, and how to build a small working app with it \u2014 even if you&#8217;ve never touched it before.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What Is tRPC?<\/strong><\/h2>\n\n\n\n<p>tRPC stands for <a href=\"https:\/\/www.guvi.in\/blog\/what-is-typescript\/\" target=\"_blank\" rel=\"noreferrer noopener\">TypeScript<\/a> Remote Procedure Call. It&#8217;s a library that connects your frontend and backend through shared TypeScript types \u2014 no REST endpoints, no <a href=\"https:\/\/www.guvi.in\/blog\/what-is-graphql\/\" target=\"_blank\" rel=\"noreferrer noopener\">GraphQL<\/a> schemas, and no code generation step.<\/p>\n\n\n\n<p>When you update a function on your server, your client immediately knows about it. If you pass the wrong type, TypeScript catches it before you even run the code.<\/p>\n\n\n\n<p>\ud83d\udcca <strong>Data Point:<\/strong> According to the 2024 State of JS survey, tRPC adoption among TypeScript developers grew by over 60% year-over-year, making it one of the fastest-growing backend tools in the ecosystem.<\/p>\n\n\n\n<p>It&#8217;s not a replacement for REST in every situation. But for full-stack TypeScript apps \u2014 especially those built with Next.js or a Node.js backend \u2014 it removes an entire layer of friction.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>How Does tRPC Work?<\/strong><\/h2>\n\n\n\n<p>Here&#8217;s the short version: you define <em>procedures<\/em> on your server (think of them like typed API functions), and your client calls those procedures directly using generated TypeScript types.<\/p>\n\n\n\n<p>There are no HTTP routes to write manually. No Swagger docs to maintain. No type mismatches between your fetch() call and what the server actually returns.<\/p>\n\n\n\n<p>tRPC achieves this through three core concepts:<\/p>\n\n\n\n<ul>\n<li><strong>Router<\/strong> \u2014 groups your procedures together, like a controller<\/li>\n\n\n\n<li><strong>Procedure<\/strong> \u2014 a single API operation (query or mutation)<\/li>\n\n\n\n<li><strong>Context<\/strong> \u2014 shared data (like the current user) passed to every procedure<\/li>\n<\/ul>\n\n\n\n<p>Let me show you how these fit together.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Setting Up tRPC: Step-by-Step<\/strong><\/h2>\n\n\n\n<p>\ud83d\udca1 <strong>Pro Tip:<\/strong> This tutorial uses <a href=\"https:\/\/trpc.io\/blog\/announcing-trpc-v11\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">tRPC v11<\/a> with a basic Node.js + Express server and a React frontend. If you&#8217;re using Next.js, the setup is slightly different but the core concepts are identical.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 1 \u2014 Install Dependencies<\/strong><\/h3>\n\n\n\n<p><code>npm install @trpc\/server @trpc\/client @trpc\/react-query @tanstack\/react-query zod<\/code><\/p>\n\n\n\n<p>You&#8217;ll also need TypeScript if you haven&#8217;t already:<\/p>\n\n\n\n<p><code>npm install -D typescript ts-node @types\/node<\/code><\/p>\n\n\n\n<p><strong>Why Zod?<\/strong> tRPC uses Zod for input validation. It works like a runtime type checker \u2014 so your server validates incoming data before your procedure even runs.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 2 \u2014 Initialize tRPC on the Server<\/strong><\/h3>\n\n\n\n<p>Create a file called trpc.ts in your server folder:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/typescript\n\nimport { initTRPC } from '@trpc\/server';\n\nconst t = initTRPC.create();\n\nexport const router = t.router;\n\nexport const publicProcedure = t.procedure;<\/code><\/pre>\n\n\n\n<p>That&#8217;s it. You now have the building blocks to define your API.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 3 \u2014 Create Your First Router<\/strong><\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/typescript\n\nimport { z } from 'zod';\n\nimport { router, publicProcedure } from '.\/trpc';\n\nexport const appRouter = router({\n\n&nbsp;&nbsp;getGreeting: publicProcedure\n\n&nbsp;&nbsp;&nbsp;&nbsp;.input(z.object({ name: z.string() }))\n\n&nbsp;&nbsp;&nbsp;&nbsp;.query(({ input }) =&gt; {\n\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return { message: `Hello, ${input.name}!` };\n\n&nbsp;&nbsp;&nbsp;&nbsp;}),\n\n});\n\nexport type AppRouter = typeof appRouter;<\/code><\/pre>\n\n\n\n<p>Here&#8217;s what&#8217;s happening:<\/p>\n\n\n\n<ul>\n<li>getGreeting is a <strong>query<\/strong> (read-only operation, like a GET request)<\/li>\n\n\n\n<li>.input() defines what data this procedure expects \u2014 validated by Zod<\/li>\n\n\n\n<li>The return value is inferred automatically \u2014 no need to define a separate return type<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 4 \u2014 Connect It to Express<\/strong><\/h3>\n\n\n\n<p><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/typescript\n\nimport express from 'express';\n\nimport * as trpcExpress from '@trpc\/server\/adapters\/express';\n\nimport { appRouter } from '.\/router';\n\nconst app = express();\n\napp.use(\n\n&nbsp;&nbsp;'\/trpc',\n\n&nbsp;&nbsp;trpcExpress.createExpressMiddleware({\n\n&nbsp;&nbsp;&nbsp;&nbsp;router: appRouter,\n\n&nbsp;&nbsp;})\n\n);\n\napp.listen(3000, () =&gt; console.log('Server running on port 3000'));<\/code><\/pre>\n\n\n\n<p>Your API is now live at http:\/\/localhost:3000\/trpc.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 5 \u2014 Set Up the Client<\/strong><\/h3>\n\n\n\n<p>On the frontend, create a trpc.ts file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/typescript\n\nimport { createTRPCReact } from '@trpc\/react-query';\n\nimport type { AppRouter } from '..\/server\/router';\n\nexport const trpc = createTRPCReact&lt;AppRouter&gt;();<\/code><\/pre>\n\n\n\n<p>Notice you&#8217;re importing AppRouter as a <strong>type<\/strong> \u2014 not the actual server code. This is the magic. Your client gets full type safety without bundling server logic.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 6 \u2014 Call Your Procedure from React<\/strong><\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/typescript\n\nimport { trpc } from '.\/trpc';\n\nfunction App() {\n\n&nbsp;&nbsp;const greeting = trpc.getGreeting.useQuery({ name: 'Kiran' });\n\n&nbsp;&nbsp;if (greeting.isLoading) return &lt;p&gt;Loading...&lt;\/p&gt;;\n\n&nbsp;&nbsp;return &lt;h1&gt;{greeting.data?.message}&lt;\/h1&gt;;\n\n}<\/code><\/pre>\n\n\n\n<p>If you try to pass { name: 123 } instead of a string, TypeScript throws an error before the code runs. That&#8217;s end-to-end type safety working exactly as it should.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Queries vs. Mutations in tRPC<\/strong><\/h2>\n\n\n\n<p>This trips up a lot of beginners, so let&#8217;s clear it up fast.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>Operation<\/strong><\/td><td><strong>Use When<\/strong><\/td><td><strong>HTTP Equivalent<\/strong><\/td><\/tr><tr><td><strong>Query<\/strong><\/td><td>Fetching or reading data<\/td><td>GET<\/td><\/tr><tr><td><strong>Mutation<\/strong><\/td><td>Creating, updating, or deleting<\/td><td>POST \/ PUT \/ DELETE<\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-element-caption\"><strong>Queries vs. Mutations in tRPC<\/strong><\/figcaption><\/figure>\n\n\n\n<p>Here&#8217;s a mutation example \u2014 say, adding a new user:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/typescript\n\naddUser: publicProcedure\n\n&nbsp;&nbsp;.input(z.object({ name: z.string(), email: z.string().email() }))\n\n&nbsp;&nbsp;.mutation(({ input }) =&gt; {\n\n&nbsp;&nbsp;&nbsp;&nbsp;\/\/ Save to database here\n\n&nbsp;&nbsp;&nbsp;&nbsp;return { success: true, user: input };\n\n&nbsp;&nbsp;}),\n\n\/\/And on the client:\n\n\/\/typescript\n\nconst addUser = trpc.addUser.useMutation();\n\naddUser.mutate({ name: 'Priya', email: 'priya@example.com' });<\/code><\/pre>\n\n\n\n<p>Clean. Typed. No manual fetch calls.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Why tRPC Beats Traditional REST for Full-Stack TypeScript Apps<\/strong><\/h2>\n\n\n\n<p>\u2705 <strong>Best Practice:<\/strong> Use tRPC when your frontend and backend are in the same monorepo (like a T3 Stack or Next.js project). The shared type system is what makes it shine.<\/p>\n\n\n\n<p>Here&#8217;s a real comparison of what changes when a developer joins a team using tRPC vs. REST:<\/p>\n\n\n\n<p><strong>With <\/strong><a href=\"https:\/\/www.guvi.in\/blog\/what-is-rest-api\/\"><strong>RE<\/strong><\/a><strong><a href=\"https:\/\/www.guvi.in\/blog\/what-is-rest-api\/\" target=\"_blank\" rel=\"noreferrer noopener\">S<\/a><\/strong><a href=\"https:\/\/www.guvi.in\/blog\/what-is-rest-api\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>T<\/strong><\/a><strong>:<\/strong> They read OpenAPI docs, write fetch wrappers, manually type the responses, and hope nothing drifts out of sync.<\/p>\n\n\n\n<p><strong>With tRPC:<\/strong> They open the router file, see every available procedure and its types, call it from the client. Done.<\/p>\n\n\n\n<p>\u26a0\ufe0f <strong>Warning:<\/strong> tRPC is not the right choice if you&#8217;re building a public API consumed by third parties. For external-facing APIs, REST or GraphQL is still the better option.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What to Do Next<\/strong><\/h2>\n\n\n\n<p>Now that you understand the basics, here&#8217;s how to go deeper:<\/p>\n\n\n\n<ol>\n<li><strong>Add authentication<\/strong> \u2014 Use tRPC middleware to protect procedures (check for a valid session before running the procedure)<\/li>\n\n\n\n<li><strong>Add a database<\/strong> \u2014 Connect Prisma to your tRPC backend for a fully type-safe stack<\/li>\n\n\n\n<li><strong>Try the T3 Stack<\/strong> \u2014 It combines Next.js, tRPC, Prisma, and Tailwind into a production-ready template<\/li>\n\n\n\n<li><strong>Explore subscriptions<\/strong> \u2014 tRPC supports WebSocket-based subscriptions for real-time features<\/li>\n<\/ol>\n\n\n\n<p>If you want a structured, mentor-supported path and learn all these new tools, then HCL GUVI\u2019s IIT-M Pravartak Certified <a href=\"https:\/\/www.guvi.in\/zen-class\/full-stack-development-course\/?utm_source=blog&amp;utm_medium=hyperlink+&amp;utm_campaign=trpc-tutorial\" target=\"_blank\" rel=\"noreferrer noopener\">Full Stack Developer Course<\/a> with AI Integration covers the entire journey, from HTML to deployment, with real projects, live sessions, and placement support. Over 10,000 students have used it to break into product-based companies.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Key Takeaways<\/strong><\/h2>\n\n\n\n<ul>\n<li>tRPC gives you end-to-end type safety between your server and client \u2014 no schema files needed<\/li>\n\n\n\n<li>You define procedures using .query() or .mutation(), validate input with Zod, and call them like functions on the frontend<\/li>\n\n\n\n<li>It works best in TypeScript-first, full-stack projects \u2014 especially with Next.js<\/li>\n\n\n\n<li>The learning curve is low if you already know TypeScript; you don&#8217;t need to learn a new query language<\/li>\n\n\n\n<li>For public APIs, stick with REST or GraphQL \u2014 tRPC is a monorepo tool, not a replacement for everything<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>FAQs<\/strong><\/h2>\n\n\n<div id=\"rank-math-faq\" class=\"rank-math-block\">\n<div class=\"rank-math-list \">\n<div id=\"faq-question-1782734180914\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>Q: Do I need to know GraphQL to use tRPC?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>No. tRPC is an entirely different approach. You don&#8217;t write schemas or queries in a separate language \u2014 you just use TypeScript functions.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1782734183127\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>Q: Is tRPC only for Next.js?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>No. tRPC works with any Node.js backend (Express, Fastify, etc.) and any frontend framework. It just happens to integrate particularly well with Next.js through official adapters.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1782734187497\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>Q: What&#8217;s the difference between tRPC and REST?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>With REST, you manually define routes and types on both ends. With tRPC, types flow automatically from the server to the client through shared TypeScript \u2014 so there&#8217;s no risk of them getting out of sync.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1782734192074\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>Q: Does tRPC work without Zod?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>Zod is the recommended input validator, but tRPC supports other validators like Yup, Valibot, and ArkType as well.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1782734197729\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>Q: Is tRPC production-ready?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>Yes. tRPC v11 is stable and used in production by thousands of teams. The T3 Stack \u2014 one of the most popular full-stack templates in the TypeScript ecosystem \u2014 ships tRPC by default.<\/p>\n\n<\/div>\n<\/div>\n<\/div>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>&nbsp;TL;DR Summary&nbsp; tRPC is a TypeScript library that lets your frontend call backend functions directly \u2014 with full type safety, no code generation, and no REST or GraphQL boilerplate. You define procedures on the server, and your client picks them up automatically. It&#8217;s ideal for full-stack TypeScript projects where speed and type safety both matter. [&hellip;]<\/p>\n","protected":false},"author":22,"featured_media":119690,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[429,294],"tags":[],"views":"51","authorinfo":{"name":"Lukesh S","url":"https:\/\/www.guvi.in\/blog\/author\/lukesh\/"},"thumbnailURL":"https:\/\/www.guvi.in\/blog\/wp-content\/uploads\/2026\/06\/tRPC-300x116.webp","_links":{"self":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts\/119519"}],"collection":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/users\/22"}],"replies":[{"embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/comments?post=119519"}],"version-history":[{"count":4,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts\/119519\/revisions"}],"predecessor-version":[{"id":119692,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts\/119519\/revisions\/119692"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/media\/119690"}],"wp:attachment":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/media?parent=119519"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/categories?post=119519"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/tags?post=119519"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}