>_fusion-stack
API Layer

tRPC

Add end-to-end type-safe APIs to any frontend — Next.js, TanStack Start, or Vite React — no schemas, no code generation, just TypeScript.

Overview

tRPC lets you build fully type-safe APIs without a schema language or code generation step. Your router is defined in TypeScript and the client infers all types automatically — if you rename a procedure or change its input, TypeScript will catch the mismatch instantly.

Fusion Stack scaffolds a complete tRPC v11 setup tailored to your chosen frontend and backend.

Scaffolded Structure

The file paths vary by frontend:

Next.js (default src/ layout — drop src/ prefix with --no-src-dir):

Note: Paths below use the default src/ layout. If you chose the root layout (--no-src-dir), drop the src/ prefix — e.g. server/ instead of src/server/.

src/
  server/
    trpc.ts             # initTRPC — exports router and publicProcedure
    routers/
      index.ts          # Root appRouter with a sample hello procedure
    trpc-hono.ts        # Hono adapter helper (only used when backend = Hono)
  app/
    api/
      trpc/
        [trpc]/
          route.ts      # Next.js App Router handler
  lib/
    trpc/
      client.ts         # Vanilla tRPC client (server components / server actions)
      react.ts          # createTRPCReact hook factory
      provider.tsx      # TRPCProvider — wraps QueryClient + tRPC client

Setup

1. Wrap your root layout

Add TRPCProvider to src/app/layout.tsx:

import { TRPCProvider } from '@/lib/trpc/provider'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <TRPCProvider>{children}</TRPCProvider>
      </body>
    </html>
  )
}

2. Call procedures from React components

Use the generated React hooks to call your procedures:

'use client'

import { trpcReact } from '@/lib/trpc/react'

export function Hello() {
  const { data } = trpcReact.hello.useQuery({ name: 'fusion-stack' })
  return <p>{data?.greeting}</p>
}

3. Call from Server Components

Use the vanilla client for server-side calls:

import { trpc } from '@/lib/trpc/client'

export default async function Page() {
  const data = await trpc.hello.query({ name: 'world' })
  return <p>{data.greeting}</p>
}

Adding Procedures

Define new procedures in src/server/routers/index.ts:

import { z } from 'zod'
import { router, publicProcedure } from '../trpc'

export const appRouter = router({
  hello: publicProcedure
    .input(z.object({ name: z.string().optional() }))
    .query(({ input }) => {
      return { greeting: `Hello, ${input.name ?? 'world'}!` }
    }),

  createPost: publicProcedure
    .input(z.object({ title: z.string(), body: z.string() }))
    .mutation(async ({ input }) => {
      const post = await db.post.create({ data: input })
      return post
    }),
})

export type AppRouter = typeof appRouter

Hono Backend

When your backend is Hono, mount tRPC on the Hono server instead of (or in addition to) the Next.js route handler:

// src/server/index.ts
import { Hono } from 'hono'
import { serve } from '@hono/node-server'
import { trpcHonoHandler } from './trpc-hono'

const app = new Hono()

app.get('/health', (c) => c.json({ status: 'ok' }))
app.use('/trpc/*', trpcHonoHandler)

serve({ fetch: app.fetch, port: 3001 })

Then update the tRPC client URL to point at the Hono server:

// src/lib/trpc/client.ts
httpBatchLink({ url: 'http://localhost:3001/trpc', transformer: superjson })

Adding Authentication Context

Pass auth context to procedures via createContext:

// src/app/api/trpc/[trpc]/route.ts
import { auth } from '@clerk/nextjs/server'

const handler = (req: Request) =>
  fetchRequestHandler({
    endpoint: '/api/trpc',
    req,
    router: appRouter,
    createContext: async () => {
      const { userId } = await auth()
      return { userId }
    },
  })

Access it in procedures:

const protectedProcedure = t.procedure.use(({ ctx, next }) => {
  if (!ctx.userId) throw new TRPCError({ code: 'UNAUTHORIZED' })
  return next({ ctx: { userId: ctx.userId } })
})

TanStack Start

When your frontend is TanStack Start, tRPC mounts via createAPIFileRoute — no separate server needed:

// app/routes/api/trpc.ts
import { createAPIFileRoute } from "@tanstack/start/api"
import { fetchRequestHandler } from "@trpc/server/adapters/fetch"
import { appRouter } from "../../server/routers/index"

export const APIRoute = createAPIFileRoute("/api/trpc/$")({
  GET: ({ request }) => fetchRequestHandler({ endpoint: "/api/trpc", req: request, router: appRouter, createContext: () => ({}) }),
  POST: ({ request }) => fetchRequestHandler({ endpoint: "/api/trpc", req: request, router: appRouter, createContext: () => ({}) }),
})

Wrap app/routes/__root.tsx with TRPCProvider from @/app/lib/trpc/provider.

Vite React

When your frontend is Vite React, tRPC is served via the Hono backend on port :3001. Mount the handler in src/server/index.ts:

import { trpcHonoHandler } from "./trpc-hono"

app.use("/trpc/*", trpcHonoHandler)

Wrap src/main.tsx with TRPCProvider from @/lib/trpc/provider.

Compatibility

  • ✅ Works with: Next.js API Routes, TanStack Start (built-in), Hono, Clerk, WorkOS, Better Auth, shadcn/ui
  • ❌ Not compatible with: Convex backend (Convex has its own type-safe RPC system)

On this page