Belajar tRPC - Membuat API tRPC Pertama
Episode 3 of 19

Belajar tRPC - Membuat API tRPC Pertama

Episode ini membangun API tRPC pertama secara utuh: struktur project yang rapi, router dengan query, mutation, dan subscription, serta penerapan zod untuk validasi input dan output modern dengan schema yang aman terhadap tipe.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Cukup teori — sekarang kalian akan membangun API tRPC pertama. Episode 3 memandu langkah demi langkah: menyusun struktur project yang rapi dan bisa berkembang, mendefinisikan router dengan tiga jenis procedure, serta memakai zod untuk memvalidasi input dan output dengan cara modern.

Semua contoh di episode ini berjalan tanpa server HTTP — kita akan memakai caller untuk mengeksekusi procedure langsung. Di episode 4, API yang sama akan dilayani lewat server nyata dan dikonsumsi client.

Struktur Project tRPC

Organisasi Direktori yang Bisa Berkembang

Project tRPC biasanya dipecah berdasarkan domain. Struktur yang direkomendasikan untuk project yang akan terus tumbuh:

Struktur project tRPC
src/
├── server/
│   ├── trpc.ts
│   └── routers/
│       ├── index.ts
│       ├── user.ts
│       └── post.ts

File trpc.ts menjadi fondasi: di sinilah initTRPC dibuat dan di-export. Router-router domain memakai fondasi ini, lalu digabung di routers/index.ts.

Fondasi trpc.ts

Fondasi trpc.ts
import { initTRPC } from "@trpc/server";
 
export const t = initTRPC.create();
 
export const router = t.router;
export const publicProcedure = t.procedure;

Dengan ekspor ini, setiap file router cukup mengimpor router dan publicProcedure dari trpc.ts — satu sumber kebenaran untuk seluruh API.

Menyusun Router dan Procedure Dasar

Query: Membaca Data

Query adalah procedure untuk membaca data, setara dengan GET pada REST. Contoh router user:

Router user dengan query
import { z } from "zod";
import { router, publicProcedure } from "../trpc";
 
const daftarUser = [
  { id: 1, nama: "Arman", role: "admin" },
  { id: 2, nama: "Budi", role: "user" },
];
 
export const userRouter = router({
  list: publicProcedure.query(() => daftarUser),
  byId: publicProcedure
    .input(z.object({ id: z.number() }))
    .query(({ input }) =>
      daftarUser.find((u) => u.id === input.id),
    ),
});

publicProcedure.query(() => ...) tanpa .input() berarti procedure tidak menerima argumen. Untuk byId, .input(z.object({ id: z.number() })) memastikan client mengirim id bertipe number.

Mutation: Menulis Data

Mutation dipakai untuk operasi yang mengubah state, setara dengan POST, PUT, atau DELETE:

Mutation create
export const userRouter = router({
  create: publicProcedure
    .input(z.object({ nama: z.string().min(1) }))
    .mutation(({ input }) => {
      const user = { id: daftarUser.length + 1, ...input, role: "user" };
      daftarUser.push(user);
      return user;
    }),
});

Perhatikan bahwa output mutation adalah nilai yang dikembalikan resolver — tipe-nya langsung diturunkan ke client.

Subscription: Data Real-Time

Subscription memakai observable untuk mengalirkan data real-time, cocok untuk notifikasi atau feed:

Subscription sederhana
import { observable } from "@trpc/server/observable";
 
clock: publicProcedure.subscription(() =>
  observable<{ waktu: string }>((emit) => {
    const timer = setInterval(() => {
      emit.next({ waktu: new Date().toISOString() });
    }, 1000);
    return () => clearInterval(timer);
  }),
),

Menggabungkan Router dan Memvalidasi Output

Root Router

Semua router domain digabung dalam satu root router:

Root router
import { userRouter } from "./user";
 
export const appRouter = router({
  user: userRouter,
});
 
export type AppRouter = typeof appRouter;

Validasi Output dengan zod

Selain input, zod juga bisa memvalidasi output. Ini penting agar data yang bocor ke client sesuai kontrak, misalnya menyembunyikan field sensitif:

Validasi output
import { z } from "zod";
 
const UserPublic = z.object({
  id: z.number(),
  nama: z.string(),
});
 
export const userRouter = router({
  profile: publicProcedure
    .input(z.object({ id: z.number() }))
    .output(UserPublic)
    .query(({ input }) => {
      const user = daftarUser.find((u) => u.id === input.id);
      if (!user) throw new Error("User tidak ditemukan");
      return { id: user.id, nama: user.nama, role: user.role };
    }),
});

Resolver di atas mengembalikan objek dengan field role, tetapi karena .output(UserPublic) menetapkan kontrak hanya id dan nama, tRPC akan memvalidasi dan menolak response yang tidak sesuai schema. Error handling yang layak akan kita bahas di episode 5.

Menjalankan API Pertama dengan Caller

Tanpa server HTTP, kita bisa langsung menguji seluruh router lewat caller:

Menguji router lewat caller
import { createCallerFactory } from "@trpc/server";
import { appRouter } from "./routers";
 
const createCaller = createCallerFactory(appRouter);
const caller = createCaller({});
 
const user = await caller.user.byId({ id: 1 });
const created = await caller.user.create({ nama: "Citra" });
console.log(user, created);

Panggilan caller.user.byId({ id: 1 }) mengeksekusi procedure lengkap dengan validasi input dan output, tanpa melibatkan network. Jika kalian mengirim { id: "satu" }, zod akan menolaknya sebelum resolver berjalan.

Penutup

Episode 3 mengajarkan kalian membangun API tRPC pertama: struktur project yang terorganisir, router dengan query, mutation, dan subscription, validasi input dan output dengan zod, serta pengujian cepat lewat caller.

Inti yang harus dibawa pulang:

  • Pisahkan fondasi trpc.ts dari router domain agar project rapi.
  • Query untuk membaca, mutation untuk menulis, subscription untuk real-time.
  • .input() memvalidasi argumen; .output() memvalidasi response.
  • Gabungkan semua router ke satu root router dan export tipe AppRouter.
  • Output procedure diturunkan otomatis dari nilai yang dikembalikan resolver.
  • Caller mengeksekusi procedure tanpa server, lengkap dengan validasi.

Di episode 4 selanjutnya kalian akan menjalankan server tRPC dan mengkonsumsi API dari client — menyiapkan server dengan Express atau Next.js App Router, membuat client tRPC di frontend, dan melihat autocompletion type-safe pada request pertama.

Belajar tRPC - Membuat API tRPC Pertama | Belajar tRPC