tRPC Full-stack
End-to-end type-safe APIs without code generation: routers, procedures, Next.js integration.
trpctypescriptfullstackapi
# tRPC
## Install (Next.js)
```bash
npm install @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod
```
## Server: init & router
```ts
// server/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server'
import { z } from 'zod'
const t = initTRPC.context<Context>().create()
export const router = t.router
export const publicProcedure = t.procedure
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
if (!ctx.session) throw new TRPCError({ code: 'UNAUTHORIZED' })
return next({ ctx: { ...ctx, user: ctx.session.user } })
})
// server/routers/post.ts
export const postRouter = router({
list: publicProcedure
.input(z.object({ cursor: z.number().optional(), limit: z.number().default(10) }))
.query(async ({ input, ctx }) => {
return db.post.findMany({ take: input.limit, cursor: input.cursor })
}),
create: protectedProcedure
.input(z.object({ title: z.string().min(1), content: z.string() }))
.mutation(async ({ input, ctx }) => {
return db.post.create({ data: { ...input, authorId: ctx.user.id } })
}),
byId: publicProcedure
.input(z.string())
.query(async ({ input }) => {
const post = await db.post.findUnique({ where: { id: input } })
if (!post) throw new TRPCError({ code: 'NOT_FOUND' })
return post
}),
})
// server/root.ts
export const appRouter = router({ post: postRouter })
export type AppRouter = typeof appRouter
```
## Next.js API handler
```ts
// app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
export const GET = POST = (req) => fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext,
})
```
## Client usage
```tsx
// utils/trpc.ts
import { createTRPCReact } from '@trpc/react-query'
import type { AppRouter } from '../server/root'
export const trpc = createTRPCReact<AppRouter>()
// Component
const { data, isLoading } = trpc.post.list.useQuery({ limit: 20 })
const create = trpc.post.create.useMutation({
onSuccess: () => utils.post.list.invalidate(),
})
create.mutate({ title: 'Hello', content: '...' })
```
## Server-side call
```ts
import { createCaller } from '../server/root'
const caller = createCaller(ctx)
const posts = await caller.post.list({ limit: 10 })
```API: /api/skills/trpc-fullstack