Episode ini membuka tiga titik intercept di tRPC: middleware di server untuk autentikasi, logging, tracing, dan rate limit; links di client seperti loggerLink, httpBatchLink, wsLink, dan custom link; serta lifecycle hooks onError, onSuccess, dan onSettled pada integrasi React Query.

Sebuah API jarang cukup hanya dengan resolver. Kalian butuh cara untuk menyisipkan logika lintas-procedure — mengecek login, mencatat request, mengukur waktu, membatasi frekuensi. Di sisi client, kalian juga butuh kontrol atas bagaimana request dikirim dan apa yang terjadi setelahnya.
Episode 6 membuka tiga titik intercept yang menjadi kekuatan tRPC: middleware di server, links di client, dan lifecycle hooks dari React Query.
Middleware adalah fungsi yang membungkus procedure. Ia menerima ctx (context) dan next, dan bisa mengubah context sebelum meneruskannya:
const logger = t.middleware(async ({ path, type, ctx, next }) => {
const mulai = Date.now();
const hasil = await next();
console.log(`${type} ${path} selesai dalam ${Date.now() - mulai}ms`);
return hasil;
});t.middleware(async ({ path, type, next }) => ...) menerima metadata seperti nama path dan jenis procedure, lalu await next() mengeksekusi lanjutan. Setelahnya kita bisa membaca waktu eksekusi.
Berikut middleware yang menyisipkan user ke context — pola dasar untuk otentikasi yang akan diperluas di episode 11:
export const isLoggedIn = t.middleware(({ ctx, next }) => {
const user = ctx.req?.headers.get("authorization");
if (!user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
ctx: { ...ctx, userId: user },
});
});
export const protectedProcedure = publicProcedure.use(isLoggedIn);publicProcedure.use(isLoggedIn) menghasilkan procedure baru yang menjalankan middleware setiap kali dipanggil. Karena middleware mengembalikan next({ ctx: { ...ctx, userId } }), seluruh procedure yang memakainya bisa mengakses ctx.userId.
Middleware berjalan berlapis sesuai urutan .use(). Untuk rate limit, misalnya, jalankan pengecekan limit sebelum validasi berat lainnya. Gabungan beberapa middleware memungkinkan satu procedure memakai otentikasi, logging, dan rate limit sekaligus tanpa mengubah resolver.
Links adalah pipeline request di sisi client. Setiap link menerima operasi dan bisa meneruskannya ke link berikutnya:
import { createTRPCProxyClient, httpBatchLink, loggerLink, splitLink, wsLink } from "@trpc/client";
const client = createTRPCProxyClient<AppRouter>({
links: [
loggerLink({ enabled: () => process.env.NODE_ENV === "development" }),
splitLink({
condition: (op) => op.type === "subscription",
true: wsLink({ url: "ws://localhost:3000" }),
false: httpBatchLink({ url: "http://localhost:3000/trpc" }),
}),
],
});Tiga link utama yang wajib dipahami:
loggerLink mencatat setiap request dan response — sempurna untuk development.httpBatchLink menggabungkan beberapa query dalam satu request HTTP.wsLink memakai WebSocket, wajib untuk subscription.splitLink({ condition, true, false }) memilih link berdasarkan kondisi — di sini subscription lewat WebSocket, sisanya lewat HTTP batch.
Ketika butuh logika khusus, buat link sendiri. Setiap link adalah fungsi yang menerima next dan mengembalikan fungsi lain yang menerima operasi:
import type { TRPCLink } from "@trpc/client";
const headerLink: TRPCLink<AppRouter> = () => {
return ({ op, next }) => {
op.context.headers = { ...op.context.headers, "X-App": "belajar-trpc" };
return next(op);
};
};Custom link headerLink menyisipkan header tambahan ke setiap operasi sebelum diteruskan. Karena links bisa dirantai dan berpola seperti middleware, kalian bisa membangun pipeline request yang sangat fleksibel.
Saat memakai @trpc/react-query, setiap hook menerima opsi React Query. Tiga callback lifecycle yang paling berguna:
const createUser = trpc.user.create.useMutation({
onSuccess: () => {
trpc.user.list.invalidate();
},
onError: (error) => {
toast(error.message);
},
onSettled: () => {
setSubmitting(false);
},
});onSuccess dipanggil saat mutasi berhasil — contohnya meng-invalidate cache list.onError dipanggil saat gagal — di sini menampilkan pesan error ke user.onSettled selalu dipanggil, sukses maupun gagal — ideal untuk mematikan loading state.Query juga punya onError. Saat request gagal, kalian bisa menampilkan fallback atau logging:
const { data } = trpc.user.list.useQuery(undefined, {
onError: (error) => console.error("Gagal memuat user", error.message),
});Catatan: useQuery(undefined, { onError }) — argumen pertama input (jika procedure tidak menerima input, kirim undefined), argumen kedua opsi hook.
Tip
Kombinasikan ketiganya: gunakan onSettled untuk hal yang pasti terjadi, onSuccess untuk aksi yang bergantung keberhasilan seperti invalidate, dan onError untuk penanganan kegagalan yang terlihat user.
Episode 6 memberi kalian kendali penuh atas alur request: middleware menyisipkan logika di server, links membentuk pipeline request di client, dan lifecycle hooks merespons hasil di React.
Inti yang harus dibawa pulang:
next() meneruskan eksekusi.publicProcedure.use(middleware) menghasilkan procedure dengan perilaku baru.loggerLink, httpBatchLink, dan wsLink menangani kebutuhan umum request.splitLink memilih link berdasarkan kondisi seperti jenis operasi.onSuccess, onError, dan onSettled menutup siklus hidup di client.Di episode 7 selanjutnya kita akan membahas konfigurasi tRPC dan environment variables — mengelola base URL, API keys, dan mode pengembangan, menyusun konfigurasi shared antara client dan server, serta menerapkan conditional loggerLink untuk development dan production.