REST vs GraphQL vs tRPC: SaaS 2026
Tiga format API saya jalankan paralel 22 bulan di SaaS B2B Jakarta. REST untuk public API + integrasi partner. GraphQL untuk dashboard internal kompleks. tRPC untuk product TypeScript-first end-to-end. Verdict per audience + use case.
TL;DR
- REST: web standard, OpenAPI matur, ideal untuk public API + integrasi partner.
- GraphQL: query fleksibel, mengurangi over/under-fetching, ideal untuk dashboard kompleks + mobile.
- tRPC: TypeScript end-to-end type safety, DX terbaik untuk product TS-first.
- Verdict: Conditional — pilih per audience + use case. SaaS modern Indonesia: campur ketiga dengan boundary jelas.
Konteks
Saya jalankan ketiga format ini paralel 22 bulan di SaaS B2B fintech-adjacent Jakarta (Agustus 2024 - Mei 2026). 18 microservice + 6 frontend channel. Pattern:
- REST API publik untuk partner integration (8 endpoint group, ~120 endpoint total)
- GraphQL untuk dashboard internal product (1 schema unified, ~280 type)
- tRPC untuk product web (Next.js frontend + Node backend, ~340 procedure)
Sebelumnya di BUMN Jakarta 2022-2024 saya pakai REST + SOAP (legacy). Pengalaman GraphQL 3 tahun (di SaaS klien 2022-2025), tRPC 2 tahun.
Pricing + cost model
API format sendiri gratis (semua open spec). Cost real = infra + ops + tooling.
REST tooling
- OpenAPI spec generation: gratis (library)
- Documentation portal Mintlify: USD 350-1.500/bulan (Rp 5,6-24 juta)
- Atau Redoc self-host: gratis
- Atau Stoplight: USD 39-1.299/bulan tergantung tier
- API gateway (Kong, Tyk, Cloudflare API Shield): USD 25-500/bulan
- Saya pakai Cloudflare API Shield: USD 25/bulan = Rp 400 ribu/bulan
GraphQL tooling
- Apollo Server: gratis open source
- GraphQL Federation (Apollo Gateway atau Hive): self-host gratis, managed USD 99-2.500/bulan
- Hasura Cloud: USD 0-99/bulan
- Saya pakai Apollo Server + GraphQL Codegen self-host: gratis + Rp 300 ribu/bulan compute
tRPC tooling
- tRPC: gratis (library MIT)
- No external tooling needed (type-sync via TypeScript langsung)
- Cost: gratis
Total format API cost
| Komponen | Cost/bulan |
|---|---|
| Cloudflare API Shield (REST publik) | Rp 400 ribu |
| Apollo Server compute (GraphQL) | Rp 300 ribu |
| Mintlify documentation portal | Rp 5,6 juta |
| OpenAPI spec generation (built-in) | Rp 0 |
| Total | Rp 6,3 juta/bulan |
Bandingkan all-managed (Apollo Studio Pro + Stoplight + Kong Konnect): bisa Rp 15-25 juta/bulan untuk equivalent feature. Self-host + Mintlify dokumentasi = sweet spot cost/feature.
Performance comparison
Setup: 1 pod GKE Jakarta (e2-medium 2 vCPU 4GB), Postgres regional, workload campuran.
Query simple (1 entity, GET by ID)
| Format | p50 | p99 | p99.9 |
|---|---|---|---|
| REST GET /user/:id | 18ms | 95ms | 220ms |
| GraphQL query { user(id) { … } } | 24ms | 125ms | 280ms |
| tRPC user.byId.query() | 19ms | 105ms | 240ms |
GraphQL ~25-30% overhead untuk parsing + validation di query simple. tRPC overhead minimal.
Query kompleks (3-entity join)
| Format | p50 | p99 | p99.9 |
|---|---|---|---|
| REST (3 endpoint sequential) | 95ms | 380ms | 850ms |
| REST (1 custom endpoint dengan join) | 35ms | 180ms | 420ms |
| GraphQL (1 query dengan DataLoader) | 42ms | 195ms | 480ms |
| tRPC (1 procedure dengan join manual) | 38ms | 185ms | 440ms |
REST sequential round-trip kalah jauh. REST custom endpoint, GraphQL, tRPC comparable. Trade-off: REST custom endpoint = banyak endpoint custom (endpoint explosion); GraphQL = 1 endpoint fleksibel; tRPC = procedure per use case.
Throughput
| Format | RPS max per pod | p99 di max RPS |
|---|---|---|
| REST (Fastify) | 1.250 | 220ms |
| GraphQL (Apollo Server) | 850 | 380ms |
| tRPC (Fastify adapter) | 1.180 | 235ms |
GraphQL throughput lebih rendah 30-35% karena parsing overhead. Untuk hot path 5k+ RPS, REST atau tRPC lebih cocok.
Capability comparison
| Capability | REST | GraphQL | tRPC |
|---|---|---|---|
| Type safety end-to-end | dengan OpenAPI gen | dengan codegen | native (TS) |
| Schema as code | OpenAPI / RAML | SDL | TypeScript |
| Client SDK auto-gen | ya (openapi-typescript, dll) | ya (graphql-codegen) | tidak perlu (type langsung) |
| Field selection | tidak (full payload) | ya | tidak |
| Batching | tidak | ya (DataLoader) | tidak |
| Real-time subscription | tidak (WebSocket terpisah) | ya | ya |
| File upload | ya | dengan multipart | ya |
| Caching HTTP standard | ya | sulit (POST default) | sulit |
| Versioning | URL/header | field deprecation | TypeScript versioning |
| Documentation auto-gen | OpenAPI + Mintlify | introspection + GraphiQL | TypeScript type comment |
| Audit + rate limit per endpoint | mudah | per-resolver custom | per-procedure custom |
| Public API friendly | ya | medium | tidak |
| Mobile client (Kotlin, Swift) | ya | ya | butuh OpenAPI export |
| Webhook + event pattern | natural | ada | ada |
Trade-off arsitektural
Pilih REST kalau:
- API target audience external (partner integration, public API)
- Tooling enterprise standard wajib (Postman collection, OpenAPI portal, API gateway)
- Client variety tinggi (mobile native, web, server-to-server, third-party)
- Cache-ability via HTTP cache penting
- Tim familiar standard pattern (REST + HTTP semantics)
Pilih GraphQL kalau:
- Dashboard kompleks dengan query bervariasi per user role
- Mobile client butuh minimize payload size (field selection)
- Federation antar service (Apollo Federation, Hive)
- Subscription real-time native
- Team frontend punya autonomy ngambil data sesuai kebutuhan tanpa nunggu backend tambah endpoint
Pilih tRPC kalau:
- Stack TypeScript end-to-end (Next.js + Node, atau SvelteKit + Node)
- Internal product API (bukan public API)
- DX iteration speed prioritas (zero codegen step)
- Tim full-stack TypeScript
- Tidak butuh client non-TS
Pattern hybrid yang saya jalankan
Internet
│
├── Public REST API (/api/v1/*)
│ └── Cloudflare API Shield → Gateway → REST service
│ (untuk partner integration, OAuth, rate limit)
│
├── Product web (Next.js)
│ └── tRPC procedure (/trpc/*)
│ (untuk internal product, TS end-to-end)
│
├── Dashboard analitik (React + Apollo)
│ └── GraphQL endpoint (/graphql)
│ (untuk BI query kompleks)
│
└── Mobile (Android Kotlin, iOS Swift)
└── REST endpoint (/mobile/v1/*)
(OpenAPI codegen ke Kotlin/Swift)
Per-channel pakai format yang cocok. Engineering cost upfront lebih tinggi (3 format = 3 tooling chain), tapi long-term DX + performance per-channel optimal.
Trade-off pattern hybrid
Pro:
- Per-audience optimization (publik = REST standar, internal TS = tRPC native, dashboard = GraphQL fleksibel)
- Tidak compromise
Con:
- 3 tooling chain (OpenAPI, GraphQL codegen, tRPC) butuh maintenance
- Engineer harus familiar dengan 3 pattern
- Boundary antar format harus jelas — siapa pakai mana?
Bandingkan pure REST: lebih sederhana, 1 tool chain. Tapi DX internal product turun + dashboard query kompleks butuh banyak custom endpoint.
Bandingkan pure GraphQL: 1 endpoint universal. Tapi public API GraphQL = friction tinggi untuk partner integration (mereka biasa REST).
SLO target per format
| Format | Latency p99 target | Throughput target | Error rate target |
|---|---|---|---|
| REST publik (partner) | < 250ms | 500 RPS sustained | < 0,1% 5xx |
| GraphQL dashboard | < 600ms (complex query OK) | 200 RPS | < 0,5% |
| tRPC product internal | < 300ms | 1k RPS | < 0,1% |
Untuk fintech, public REST API SLO ketat — partner punya kontrak SLA. Internal GraphQL lebih longgar karena query bisa kompleks. tRPC product hot path ketat (frontend user-facing).
Migration risk
REST → GraphQL
Saya jalankan di SaaS klien 2023: 60 REST endpoint → 1 GraphQL schema (110 type).
- Effort: 8-12 minggu untuk dual-run + cutover
- Pain point: caching strategy harus redesign (HTTP cache → resolver-level cache + DataLoader)
- Outcome: client request volume turun 35% (field selection), p99 dashboard turun 40%
- Trade-off: throughput service drop 30% untuk endpoint sederhana
REST → tRPC
Untuk product TS-first, smoother:
- Effort: 4-6 minggu untuk 40 endpoint
- Pain point: tRPC tidak ada HTTP method semantik (semua POST) — middleware yang depend GET/POST butuh refactor
- Outcome: DX naik signifikan, codegen step hilang
- Trade-off: hilang HTTP caching native — lakukan caching di tRPC middleware atau layer di atas
GraphQL → tRPC
Tidak biasa, tapi mungkin untuk internal-only product TS. Pengalaman teman saya: 8-12 minggu, gain DX, lose flexibility query custom.
Common pitfall
- GraphQL N+1. Resolver default = 1 DB call per field. Tanpa DataLoader, query 1 list × 5 field nested = 5 + 25 = 30 query. Pakai DataLoader dari hari pertama.
- REST over-fetching. Endpoint return full object termasuk field besar (logs, history) yang client tidak butuh. Implement sparse fieldset (
?fields=id,name) atau bikin variant endpoint. - tRPC accidental public exposure. tRPC procedure mudah expose. Implement auth middleware mandatory di tRPC router root, audit per-procedure permission.
- GraphQL query complexity attack. Client kirim query nested deep = bunuh server. Implement query complexity analysis (graphql-cost-analysis), max depth, max alias.
- Versioning REST tanpa kontrak test. Breaking change accidental ke client integrasi partner = trust kerusakan. Wajib contract test (Pact, Dredd) di CI sebelum release.
OpenAPI workflow untuk REST
Pattern saya:
- Source of truth: TypeScript type di backend (Hono RPC, NestJS, FastAPI)
- Auto-generate OpenAPI spec di build step
- Spec di-commit ke
api/openapi.yamldi repo (versioned) - Mintlify watch repo, auto-deploy doc portal
- Client SDK auto-generate dari spec (openapi-typescript, openapi-generator)
Setup awal 1 minggu. Setelah itu maintenance < 1 jam/minggu.
GraphQL Federation pattern
Untuk 18 microservice fintech, saya tidak pakai Federation full (overhead tinggi). Pattern saya:
- 1 GraphQL gateway service (Apollo Gateway atau Hive)
- 3-4 subgraph (user domain, payment domain, notification domain, analytics)
- Sisanya service expose REST internal, gateway tarik via resolver
Federation full worth kalau service domain 10+ dengan team berbeda per domain. Saya skala 4 domain, hybrid pattern cukup.
Indonesia specific
Partner integration
Partner bank Indonesia (BCA, BNI, Mandiri) tooling-nya REST + SOAP legacy. Public API SaaS fintech Indonesia wajib REST untuk mengakomodasi.
Documentation Bahasa
Mintlify support multi-language. Saya provide doc portal dalam Bahasa Indonesia + English. Partner Indonesia prefer Bahasa, partner regional prefer English. Cost translation: 1x effort initial, sync via Mintlify export.
Public API rate limit untuk partner
Cloudflare API Shield rate limit per-customer (via API key). Pattern saya: 10 RPS sustained + 100 burst untuk tier basic, 100 RPS sustained + 1k burst untuk tier premium. Audit log via Cloudflare R2 + Splunk.
Yang surprising
Saya kira GraphQL akan jadi primary format di SaaS modern. Realita 22 bulan: tRPC menggantikan banyak use case yang dulu saya assume GraphQL only (internal product API). GraphQL tetap valuable, tapi scope-nya menyempit ke dashboard analitik kompleks + mobile field selection.
Surprise lain: REST tetap dominan untuk public API + partner integration. OpenAPI 3.1 ekosistem maturity nyata — partner Indonesia mengirim integration team yang familiar Postman + OpenAPI, tidak familiar GraphiQL.
Conclusion: tidak ada “winner” format. Pilih per-audience. Pure single-format adalah optimization yang salah arah untuk SaaS dengan multi-audience.
Verdict
Conditional dengan rule konkret per audience:
- Public API + partner integration: REST + OpenAPI 3.1. Non-negotiable untuk Indonesia.
- Internal product TS end-to-end: tRPC. DX + type safety unmatched.
- Dashboard analitik kompleks + mobile field selection: GraphQL. Worth overhead untuk query flexibility.
- Mobile native (Kotlin, Swift): REST dengan OpenAPI codegen ke Kotlin/Swift.
- Skip pattern hybrid kalau tim engineering < 5 dev. Pakai REST saja, tambah tRPC kalau stack TS-first. GraphQL terlalu ops overhead untuk small team.
Threshold konkret untuk add GraphQL ke stack: dashboard memiliki > 8 query distinct per user role atau mobile client butuh field selection untuk > 50% endpoint. Di bawah ini, REST + GraphQL = over-engineering.
Ditulis oleh Asti Larasati