Mind~G
TRPC

tRPC

What is tRPC?

tRPC is a tool that helps you connect your frontend (React, Next.js, etc.) with your backend (Node.js, Express, etc.) easily without writing REST APIs or GraphQL.

tRPC = directly call backend functions from frontend Think of it like this:

  • You write a function on the backend
  • and directly call that same function on the frontend
  • Like you are calling a normal JavaScript function.

Main tRPC Keywords

KeywordSimple MeaningCompare with REST
RouterA group of functions (like your API endpoints)Like a collection of routes /user, /post
ProcedureA single function that does somethingLike one REST endpoint /api/user
QueryUsed to get (read) dataLike GET request
MutationUsed to change (write) dataLike POST, PUT, DELETE request
InputWhat data you send to the backendLike req.body in REST
OutputWhat data backend returnsLike res.json() in REST
ZodA library to check and validate dataLike checking data before saving in REST

When to Use tRPC

Use tRPC when:

  • You are building a TypeScript project (it works best with TS)
  • You want automatic type safety between frontend and backend
  • You want to use monorepo
  • Your frontend and backend are in the same codebase (like Next.js)
  • You are tired of writing extra REST endpoints for simple things

Do not use tRPC when:

  • You need to connect with external apps or mobile apps
  • You want to make public APIs (use REST or GraphQL for that)
  • Your frontend and backend are in different servers or languages

Why use tRPC?

Let us compare it with REST API.

REST API way

You make endpoints like /api/users, /api/posts, etc.
You must send and receive JSON manually.

Example:

Frontend:

const res = await fetch('/api/user', {
  method: 'POST',
  body: JSON.stringify({ name: "John" })
});
const data = await res.json();

Backend:

app.post('/api/user', (req, res) => {
  const { name } = req.body;
  res.json({ message: "Hello " + name });
});

You have to write types twice (once for frontend and once for backend).

tRPC way

You just write one function on the server and call it from the client. Types are shared automatically.

Backend:

const appRouter = createTRPCRouter({
  sayHello: publicProcedure
    .input(z.string())
    .query(({ input }) => {
      return `Hello ${input}`;
    }),
});

Frontend:

const { data } = trpc.sayHello.useQuery("John");
console.log(data); // "Hello John"

No need to fetch manually No need to parse JSON No need to write types twice

Visual Comparison Diagram

REST API
+-------------+           +-------------+
| Frontend    |  HTTP Req | Backend     |
| fetch(url)  | --------> | app.get(...)|
| parse JSON  | <-------- | res.json()  |
+-------------+           +-------------+
         ^ manual coding both sides
 
tRPC
+-------------+           +-------------+
| Frontend    |  direct   | Backend     |
| trpc.call() | --------> | procedure() |
| auto typed  | <-------- | return data |
+-------------+           +-------------+
         ^ types shared automatically

Context

What is context in tRPC?

  • tRPC context = REST req
  • In tRPC, context is an object that is created for each request.
  • This object holds data that all your procedures (queries, mutations) can access.
  • It is a place to put shared things you need everywhere: user info, database client, authorization data, etc.

Diagram

HTTP Request --> tRPC Handler --> createContext(req) --> ctx object
                                           |
                                    +------+------+
                                    |             |
                           passed into each       |
                           procedure / middleware  |
                            as opts.ctx              |
                                    |
                         procedure logic (resolver)

tRPC Context vs REST Pattern


Overview

ConceptREST PatterntRPC Context
Shared DataStored in req (e.g. req.user, req.db)Stored in ctx (context object)
CreationMiddleware runs before each routecreateContext() runs before each procedure
AccessAccess via req.user or req.dbAccess via ctx.user or ctx.db
TypingManual TypeScript types or noneFully typed automatically
Usage ScopePer requestPer request
Where UsedRoute handlers (app.get, app.post)Procedures (query, mutation)
Setup Exampleapp.use(authMiddleware)initTRPC.context<Context>().create()
Best ForPublic APIs or mixed tech stacksTypeScript full stack apps (Next.js etc.)

Code Comparison Example

REST Example

// Express server
app.use((req, res, next) => {
  const user = getUserFromToken(req.headers.authorization);
  req.user = user;
  next();
});
 
app.get("/api/secret", (req, res) => {
  if (!req.user) return res.status(401).send("Not logged in");
  res.send(`Hello ${req.user.name}`);
});

tRPC Example

// server/context.ts
export async function createContext({ req }) {
  const user = getUserFromToken(req.headers.authorization);
  return { user };
}
 
// router
const t = initTRPC.context<typeof createContext>().create();
 
export const appRouter = t.router({
  secretData: t.procedure.query(({ ctx }) => {
    if (!ctx.user) throw new Error("Not logged in");
    return `Hello ${ctx.user.name}`;
  }),
});

Note: Middleware is block the request but Context will not block user request. because context is used to only get the user data,db connection,authorization etc.

On this page