Kembali ke Blog
Tutorial#trpc#typescript#nextjs#api#fullstack#2026

tRPC: Buat API yang Sepenuhnya Type-Safe Tanpa Tulis Satu Baris Schema

tRPC adalah cara paling elegan untuk komunikasi client-server di Next.js. Tidak perlu REST endpoints, tidak perlu GraphQL schema — cukup TypeScript, dan kedua sisi otomatis sinkron.

Muhamad Putra Aulia Hidayat

Muhamad Putra Aulia Hidayat

3 Maret 20263 menit baca

tRPC: End-to-End Type Safety Tanpa Overhead

Bayangkan bisa call fungsi server dari client seperti memanggil fungsi lokal — lengkap dengan autocomplete, type checking, dan error handling yang penuh. Itu yang tRPC tawarkan.

Masalah yang Diselesaikan tRPC

// Tanpa tRPC - tidak ada type safety end-to-end
// Server
app.get("/api/users/:id", async (req, res) => {
  const user = await getUser(req.params.id)
  res.json(user) // Tipe? Tidak jelas di client
})

// Client
const response = await fetch("/api/users/123")
const user = await response.json() // Tipe: any. Harus cast manual
// Dengan tRPC - full type safety
// Server
const userRouter = router({
  getById: publicProcedure
    .input(z.string().uuid())
    .query(async ({ input }) => {
      return getUser(input) // Return type otomatis di-infer
    })
})

// Client - autocomplete penuh, type-safe!
const user = await trpc.user.getById.query("550e8400-...")
// user: User (type otomatis dari server)

Setup di Next.js

npm install @trpc/server @trpc/client @trpc/react-query @tanstack/react-query zod
// server/trpc.ts
import { initTRPC } from "@trpc/server"
import { z } from "zod"

const t = initTRPC.create()

export const router = t.router
export const publicProcedure = t.procedure

// Untuk protected routes
export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
  if (!ctx.session?.user) {
    throw new TRPCError({ code: "UNAUTHORIZED" })
  }
  return next({ ctx: { user: ctx.session.user } })
})
// server/routers/products.ts
import { router, publicProcedure, protectedProcedure } from "../trpc"
import { z } from "zod"

export const productsRouter = router({
  list: publicProcedure
    .input(z.object({
      page: z.number().min(1).default(1),
      limit: z.number().min(1).max(100).default(20),
      category: z.string().optional()
    }))
    .query(async ({ input }) => {
      const { page, limit, category } = input
      return db.products.findMany({
        where: category ? { category } : undefined,
        skip: (page - 1) * limit,
        take: limit
      })
    }),

  getById: publicProcedure
    .input(z.string().uuid())
    .query(async ({ input }) => {
      const product = await db.products.findUnique({ where: { id: input } })
      if (!product) throw new TRPCError({ code: "NOT_FOUND" })
      return product
    }),

  create: protectedProcedure
    .input(z.object({
      name: z.string().min(3),
      price: z.number().positive(),
      stock: z.number().int().min(0)
    }))
    .mutation(async ({ input, ctx }) => {
      return db.products.create({
        data: { ...input, createdById: ctx.user.id }
      })
    }),
})
// server/routers/_app.ts
import { router } from "../trpc"
import { productsRouter } from "./products"
import { usersRouter } from "./users"
import { ordersRouter } from "./orders"

export const appRouter = router({
  products: productsRouter,
  users: usersRouter,
  orders: ordersRouter,
})

export type AppRouter = typeof appRouter

Pakai di React Component

"use client"
import { trpc } from "@/lib/trpc"

export function ProductList() {
  const { data, isLoading } = trpc.products.list.useQuery({
    page: 1,
    limit: 20
  })

  const createProduct = trpc.products.create.useMutation({
    onSuccess: () => {
      // Invalidate dan refetch
      trpc.products.list.invalidate()
    }
  })

  if (isLoading) return <div>Loading...</div>

  return (
    <div>
      {data?.map(product => (
        <div key={product.id}>
          {product.name} - Rp {product.price.toLocaleString("id")}
        </div>
      ))}
    </div>
  )
}

tRPC vs REST vs GraphQL

AspekRESTGraphQLtRPC
Type safetyManualCode-genOtomatis
Setup complexityRendahTinggiSedang
Fleksibilitas queryTerbatasTinggiSedang
Best forPublic APIComplex data graphFull-stack TypeScript

Kapan Pakai tRPC?

Ideal untuk: Full-stack TypeScript app di mana client dan server dalam satu codebase (Next.js monorepo).

Tidak ideal untuk: Public API yang dikonsumsi banyak client berbeda (mobile, third-party) — lebih baik REST atau GraphQL.

tRPC adalah game-changer untuk developer yang sudah all-in di TypeScript dan Next.js.

trpctypescriptnextjsapifullstack2026

Newsletter Digital Uptime

Tips teknologi & bisnis mingguan

Bergabung dengan 2,500+ subscriber yang mendapatkan insight teknologi, tutorial development, dan tips bisnis digital langsung ke inbox mereka setiap minggu.

Tidak ada spam. Unsubscribe kapan saja.

Artikel Terkait

Kami menggunakan cookies untuk meningkatkan pengalaman Anda di website ini. Dengan melanjutkan, Anda menyetujui penggunaan cookies sesuai Kebijakan Privasi kami.