Belajar tRPC - Keamanan API, Rate Limiting & Best Practice
Series/Belajar tRPC/Episode 12
Episode 12 of 19

Belajar tRPC - Keamanan API, Rate Limiting & Best Practice

Episode ini menguatkan pertahanan API tRPC: proteksi terhadap injection, overfetch, dan payload abuse, rate limiting di level router maupun adapter, serta best practice TLS, CSP, dan sanitasi data input.

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

Pendahuluan

Keamanan bukan fitur terakhir — ia lapisan yang menyatu dengan desain. Episode 12 membahas ancaman yang nyata menimpa API tRPC dan cara menangkisnya: injection, overfetch, dan payload abuse, lalu rate limiting, dan ditutup best practice seperti TLS, CSP, serta sanitasi input.

Karena tRPC mengekspos procedure langsung, permukaan serangannya berbeda dari REST. Memahami titik lemah ini membuat kalian bisa menutupnya sejak awal.

Proteksi dari Injection, Overfetch, dan Payload Abuse

Injection pada Input

Ancaman pertama adalah data yang diinterpretasikan sebagai perintah — misalnya string yang masuk ke query database. Zod membatasi bentuk data, tetapi sanitasi tetap tanggung jawab resolver:

Sanitasi sebelum masuk database
import { z } from "zod";
 
cari: t.procedure
  .input(z.object({ q: z.string().min(1).max(100) }))
  .query(({ input }) => {
    // gunakan parameterized query, jangan interpolasi mentah
    const escaped = input.q.replace(/[\s'"\\]/g, " ");
    return users.filter((u) => u.nama.toLowerCase().includes(escaped.toLowerCase()));
  }),

Aturan dasarnya: validasi bentuk dengan zod (tipe, panjang, format), lalu query ke database memakai parameterized statement atau ORM. Jangan pernah menyusun query dengan string yang disisipkan mentah — ini mencegah SQL/NoSQL injection.

Overfetch dan Underfetch

Overfetch: client meminta lebih banyak data dari yang dibutuhkan. Karena output procedure ditentukan resolver, hindari mengembalikan objek database penuh jika client hanya butuh beberapa field — gunakan validasi output dari episode 3 dan proyeksikan field yang diperlukan.

Underfetch adalah kebalikannya: banyak round-trip kecil. Gabungkan data terkait dalam satu procedure agar client tidak memanggil berulang kali:

Menghindari overfetch dan underfetch
userWithPosts: t.procedure
  .input(z.object({ id: z.number() }))
  .query(({ input }) => {
    const user = users.find((u) => u.id === input.id);
    const posts = posts.filter((p) => p.authorId === input.id);
    return { id: user.id, nama: user.nama, postCount: posts.length };
  }),

Procedure userWithPosts mengembalikan hanya field yang dibutuhkan client — satu request, tidak berlebihan.

Payload Abuse

Input yang sangat besar bisa menghabiskan memori dan CPU. Batasi dengan zod (panjang, ukuran) dan batasi ukuran body di level server:

Membatasi ukuran input
create: t.procedure
  .input(
    z.object({
      judul: z.string().min(3).max(200),
      konten: z.string().min(1).max(10_000),
      tags: z.array(z.string()).max(10),
    }),
  )
  .mutation(({ input }) => simpanPost(input)),

Di Express, batasi body parser:

Batasi ukuran body di Express
app.use(express.json({ limit: "1mb" }));

express.json({ limit: "1mb" }) menolak request dengan body lebih dari 1 megabyte sebelum masuk pipeline tRPC.

Rate Limiting

Middleware Rate Limit di Level Router

Rate limiting membatasi frekuensi akses agar API tidak dibanjiri. Buat middleware sederhana dengan penyimpanan dalam memori:

Rate limit middleware
const rateLimit = t.middleware(({ ctx, next, path }) => {
  const key = `${ctx.user?.id ?? ctx.req?.ip}:${path}`;
  const now = Date.now();
 
  const hits = counters.get(key) ?? [];
  const recent = hits.filter((t) => now - t < 60_000);
 
  if (recent.length >= 30) {
    throw new TRPCError({ code: "TOO_MANY_REQUESTS" });
  }
  recent.push(now);
  counters.set(key, recent);
 
  return next();
});
 
export const limitedProcedure = t.procedure.use(rateLimit);

TRPCError({ code: "TOO_MANY_REQUESTS" }) memetakan ke HTTP 429. Counter dalam memori cukup untuk satu instance, tetapi untuk produksi multi-instance gunakan penyimpanan bersama seperti Redis.

Rate Limit di Level Adapter

Rate limiting juga bisa dipasang di lapisan HTTP sebelum tRPC — misalnya middleware Express atau layanan edge seperti Cloudflare. Keunggulannya: limit berlaku untuk seluruh path tanpa menyentuh kode tRPC, dan berjalan sangat dekat dengan user.

Pilih sesuai kebutuhan: middleware tRPC memberi kontrol per-user dan per-procedure; adapter/edge memberi proteksi global dengan performa terbaik. Kombinasi keduanya adalah pendekatan production yang umum.

Best Practice Keamanan Lainnya

TLS/HTTPS dan Header Keamanan

Semua traffic production harus lewat HTTPS. Di balik proxy, pastikan TLS di-terminate di edge dan header keamanan ditambahkan:

Header keamanan dasar
app.use((req, res, next) => {
  res.setHeader("Strict-Transport-Security", "max-age=63072000");
  res.setHeader("X-Content-Type-Options", "nosniff");
  res.setHeader("Referrer-Policy", "no-referrer");
  next();
});

Strict-Transport-Security memaksa browser selalu memakai HTTPS. Header lain mencegah jenis serangan berbasis MIME sniffing dan kebocoran referrer.

CSP untuk Frontend

Content-Security-Policy membatasi sumber konten yang boleh dimuat halaman — menangkis XSS. Pastikan connect-src mengizinkan endpoint tRPC dan WebSocket:

Contoh CSP via header
Content-Security-Policy: default-src 'self'; connect-src 'self' wss://api.contoh.com;

Perhatikan wss://api.contoh.com di connect-src — tanpa ini, koneksi WebSocket dari frontend ke server akan diblokir browser.

Validasi dan Sanitasi Berlapis

Keamanan terbaik adalah berlapis: validasi schema zod menolak input yang salah bentuk, sanitasi membersihkan data sebelum masuk penyimpanan, dan otorisasi membatasi siapa yang bisa memicu prosedur. Jangan pernah menggantungkan keamanan hanya pada satu lapisan.

Warning

Rate limit berbasis memori tidak berbagi state antar instance server. Di deployment dengan banyak instance, gunakan Redis atau layanan edge agar limit tetap konsisten di seluruh instance.

Penutup

Episode 12 memperkuat pertahanan API tRPC: menangkis injection lewat validasi dan sanitasi, menghindari overfetch dan underfetch dengan proyeksi serta penggabungan data, membatasi payload abuse, dan menegakkan rate limiting di dua level plus best practice TLS dan CSP.

Inti yang harus dibawa pulang:

  • Validasi zod bukan pengganti sanitasi di resolver.
  • Pakai parameterized query untuk mencegah injection.
  • Overfetch dihindari dengan proyeksi field; underfetch dengan prosedur gabungan.
  • Batasi ukuran input di schema dan body limit di server.
  • Rate limit per-user dengan Redis untuk multi-instance.
  • Aktifkan HTTPS, HSTS, dan CSP dengan connect-src yang mencakup WebSocket.

Di episode 13 selanjutnya kita akan membahas performansi & optimasi tRPC — mengoptimalkan batch request dan mengurangi round-trips, memakai dehydrate dan hydration di SSR untuk meminimalisasi fetch ulang, serta optimasi server seperti caching, data loader, dan lazy routers.

Belajar tRPC - Keamanan API, Rate Limiting & Best Practice | Belajar tRPC