Episode ini membedah arsitektur tRPC: bagaimana TypeScript menginferensikan tipe dari server ke client, peran router, procedure, input, output, dan caller, serta lapisan adapter untuk transport HTTP, WebSocket, dan serverless beserta client proxy dan client React.

Episode 2 masuk ke bagian paling penting sebelum menulis kode: arsitektur tRPC. Kalian sudah tahu mengapa tRPC ada; sekarang saatnya memahami cara kerjanya di balik layar — bagaimana tipe mengalir dari server ke client, komponen apa saja yang menyusun sebuah API, dan bagaimana tRPC terhubung ke HTTP, WebSocket, atau environment serverless.
Setelah episode ini, istilah seperti router, procedure, input, output, caller, dan links tidak akan lagi asing. Ini adalah kosakata yang akan dipakai di seluruh series.
Kunci tRPC adalah TypeScript inference. Ketika kalian mendefinisikan router di server, tipe lengkapnya — nama procedure, bentuk input, dan bentuk output — disimpan dalam sebuah tipe AppRouter. Client memakai tipe ini sebagai type argument saat membuat proxy client, sehingga editor tahu persis apa yang tersedia.
Perhatikan alurnya dalam satu diagram sederhana:
server router → type AppRouter → client proxy → autocompletionTidak ada langkah ekstra seperti codegen. Tipe hanya dibaca dari definisi server, dan TypeScript menjaganya tetap sinkron setiap kali prosedur berubah. Inilah mengapa tRPC disebut zero-codegen dan zero-schema-duplication.
Sebuah API tRPC tersusun dari tiga lapis:
Lapisan transport ini bersifat pluggable, jadi router yang sama bisa dilayani oleh server standalone, Express, Fastify, Next.js, atau serverless tanpa mengubah definisi procedure.
Mari lihat komponen inti dalam satu contoh nyata:
import { initTRPC } from "@trpc/server";
import { z } from "zod";
const t = initTRPC.create();
export const appRouter = t.router({
user: t.router({
byId: t.procedure
.input(z.object({ id: z.number() }))
.query(({ input }) => ({ id: input.id, nama: "Arman" })),
create: t.procedure
.input(z.object({ nama: z.string().min(1) }))
.mutation(({ input }) => ({ id: 2, nama: input.nama })),
}),
});
export type AppRouter = typeof appRouter;Bagian-bagian penting:
t.router({...}) mendefinisikan router, dan router boleh bersarang seperti user.t.procedure adalah blok dasar; diakhiri dengan .query(), .mutation(), atau .subscription()..input(schema) memvalidasi dan mengetikkan input yang diterima.Dalam kode di atas, appRouter.user.byId.query({ id: 1 }) akan mengembalikan objek bertipe { id: number; nama: string }.
Router tidak tahu menahu soal protocol. Sebuah adapter menghubungkannya ke dunia luar:
createHTTPServer untuk server standalone, createExpressMiddleware untuk Express, fetchRequestHandler untuk Next.js dan serverless.wsServer (di v10 bernama applyWSSHandler) untuk subscription real-time.Konsekuensinya besar: kode procedure tetap sama, hanya adapter yang berubah sesuai deployment. Episode 10 dan 16 akan membahas keduanya secara detail.
Di sisi client, tRPC menyediakan dua cara mengakses router yang sama:
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
import { createTRPCReact } from "@trpc/react-query";
const trpc = createTRPCProxyClient<AppRouter>({
links: [httpBatchLink({ url: "http://localhost:3000/trpc" })],
});
const api = createTRPCReact<AppRouter>();createTRPCProxyClient dipakai di luar React — terminal, script, atau vanilla JS.createTRPCReact menghasilkan hooks seperti api.user.byId.useQuery() di dalam React.Keduanya menerima AppRouter sebagai type argument, sehingga autocompletion dan keamanan tipe berlaku penuh di sisi client.
Terkadang kalian perlu memanggil procedure dari dalam server itu sendiri — misalnya dari cron job, webhook handler, atau saat menyiapkan data di server. Di sinilah caller berperan:
import { createCallerFactory } from "@trpc/server";
const createCaller = createCallerFactory(appRouter);
const caller = createCaller({});
const user = await caller.user.byId({ id: 5 });
console.log(user.nama);createCallerFactory(appRouter) menghasilkan fungsi pemanggil yang mengeksekusi procedure tanpa HTTP round-trip, tetapi tetap menjalankan middleware dan validasi. Pola ini penting untuk internal service dan akan dipakai lagi di episode 17 saat testing.
Tip
Pahami perbedaan: resolver adalah definisi, caller adalah eksekusi. Caller mengeksekusi procedure seolah-olah datang dari client, lengkap dengan middleware dan validasi input.
Episode 2 memberi kalian peta arsitektur tRPC: tipe mengalir dari router server ke client lewat inference, procedure tersusun dari input, resolver, dan output, transport disediakan oleh adapter yang pluggable, dan caller memungkinkan pemanggilan dari dalam server.
Inti yang harus dibawa pulang:
.input() memvalidasi dan mengetikkan input; output diturunkan dari resolver.createTRPCProxyClient untuk non-React; createTRPCReact untuk hooks.caller mengeksekusi procedure di dalam server tanpa round-trip.AppRouter di-export sebagai tipe agar client bisa menginferensikannya.Di episode 3 selanjutnya kita akan langsung membuat API tRPC pertama — menyusun struktur project, mendefinisikan router dengan query, mutation, dan subscription, serta menerapkan validasi zod pada input dan output.