REST vs GraphQL: Memilih Desain API yang Tepat

Foto oleh Unsplash

Foto oleh Unsplash
REST API dan GraphQL adalah dua pendekatan dominan dalam membangun API, dan pilihan di antara keduanya membentuk bagaimana tim frontend dan backend Anda berkolaborasi, bagaimana client Anda mengambil data, dan bagaimana API Anda berkembang dari waktu ke waktu. Keduanya adalah pendekatan yang matang dan telah teruji dengan trade-off yang nyata. Postingan ini memberi Anda perbandingan konkret — model data yang sama, kedua pendekatan — sehingga Anda dapat membuat pilihan yang tepat untuk proyek Anda berikutnya.
REST mengorganisir API Anda di sekitar resource — kata benda seperti /users, /posts, /orders — dan menggunakan HTTP verb (GET, POST, PUT, DELETE) untuk mengekspresikan maksud. GraphQL mengekspos satu endpoint dan memungkinkan client mendeskripsikan persis data yang mereka butuhkan menggunakan query language. Trade-off fundamentalnya adalah: REST mudah dipahami dan di-cache di layer HTTP, sementara GraphQL menghilangkan over-fetching dan under-fetching dengan memberi client kontrol presisi atas bentuk data.
Tantangan terbesar REST dalam aplikasi UI yang kaya adalah bahwa satu layar sering membutuhkan beberapa resource. Halaman blog post membutuhkan post, penulis, komentar, dan tag — itu empat round-trip. Setiap round-trip menambah latensi, terutama di mobile. Response untuk setiap resource juga menyertakan setiap field yang diketahui server, meskipun client hanya membutuhkan dua. GraphQL dirancang khusus untuk menyelesaikan masalah ini.
// REST: multiple round-trips to assemble a blog post page
GET /api/posts/42 // fetch post
GET /api/users/7 // fetch author
GET /api/posts/42/comments // fetch comments
GET /api/tags?postId=42 // fetch tags
// GraphQL: single query, exactly the fields you need
query GetPostPage($postId: ID!) {
post(id: $postId) {
title
body
publishedAt
author {
name
avatarUrl
}
comments(first: 5) {
text
author { name }
}
tags { name }
}
}Over-fetching REST dapat sebagian diselesaikan dengan sparse fieldset (?fields=id,title,author) dan compound document mengikuti spesifikasi JSON:API. Ini tidak menghilangkan semua round-trip tetapi secara signifikan mengurangi ukuran payload untuk REST API mobile.
REST API yang dirancang dengan baik mengikuti struktur URL yang konsisten, menggunakan HTTP status code yang tepat, mengembalikan error body yang bermakna, dan melakukan versioning dengan bijak. Di Next.js App Router, setiap route handler memetakan dengan bersih ke endpoint resource. TypeScript memastikan bentuk response Anda konsisten, dan Zod memberi Anda validasi runtime untuk request body.
Gunakan kata benda jamak untuk koleksi (/posts, bukan /post). Gunakan nested resource secukupnya — /posts/42/comments tidak masalah, tetapi /users/7/posts/42/comments/5/likes adalah tanda masalah. Kembalikan 201 Created dengan header Location untuk request POST. Kembalikan 204 No Content untuk DELETE yang berhasil. Gunakan 422 Unprocessable Entity untuk validation error dengan error body yang terstruktur.
// REST API with TypeScript (Next.js App Router)
// app/api/posts/[id]/route.ts
export async function GET(req: Request, { params }: { params: { id: string } }) {
const post = await db.post.findUnique({
where: { id: params.id },
include: { author: true, tags: true },
});
if (!post) return Response.json({ error: "Not found" }, { status: 404 });
return Response.json(post);
}
export async function PATCH(req: Request, { params }: { params: { id: string } }) {
const body = await req.json();
const updated = await db.post.update({ where: { id: params.id }, data: body });
return Response.json(updated);
}
export async function DELETE(_: Request, { params }: { params: { id: string } }) {
await db.post.delete({ where: { id: params.id } });
return new Response(null, { status: 204 });
}Versioning melalui URL path (/api/v1/posts) sederhana dan eksplisit, memudahkan menjalankan dua versi secara bersamaan. Versioning melalui Accept header lebih RESTful tetapi lebih sulit ditest di browser. Versioning melalui query string (?version=2) paling tidak mengganggu. Untuk API internal antar layanan Anda sendiri, pertimbangkan menggunakan evolusi additive-only (jangan pernah menghapus field) daripada versioning sama sekali.
GraphQL menyelesaikan masalah over-fetching dan under-fetching dengan elegan, tetapi menambah kompleksitasnya sendiri. Anda perlu memikirkan otorisasi di level field, bukan hanya level resource. Anda membutuhkan DataLoader untuk mem-batch query database dan mencegah masalah N+1 dalam resolver. Caching lebih kompleks karena setiap query adalah POST ke endpoint yang sama, membuat caching level HTTP menjadi tidak efektif.
Saat me-resolve daftar post dengan penulisnya, resolver GraphQL yang naif membuat satu query database per post untuk mengambil penulis — masalah N+1 klasik. DataLoader menyelesaikan ini dengan mengumpulkan semua ID penulis dalam satu event-loop tick dan mengeluarkan satu batched query. Ini penting untuk GraphQL API yang melayani volume data lebih dari sepele.
Fleksibilitas GraphQL dapat menyebabkan query yang tidak terbatas jika Anda tidak mengimplementasikan pembatasan kedalaman query dan analisis kompleksitas. Client yang jahat atau ceroboh dapat membuat query yang sangat bersarang yang memicu ribuan database call. Library seperti graphql-depth-limit dan graphql-query-complexity membantu, tetapi Anda harus aktif mengonfigurasinya. REST API dengan bentuk response tetap memiliki profil performa yang secara alami terbatas.
Dalam REST, otorisasi biasanya di level resource — middleware memeriksa apakah pengguna dapat mengakses /posts/42. Dalam GraphQL, satu query dapat menyentuh beberapa tipe resource dalam satu request, sehingga otorisasi harus terjadi di level resolver untuk setiap field. Library seperti graphql-shield menyediakan sistem berbasis aturan deklaratif untuk otorisasi level field yang dapat dikomposisi dan ditest.
Pilih REST untuk API publik di mana Anda menginginkan HTTP caching, dukungan tooling yang luas, dan pola akses yang dapat diprediksi. REST unggul untuk operasi resource-centric (CRUD), webhook, dan upload file. Pilih GraphQL ketika Anda memiliki beberapa client (web, mobile, pihak ketiga) dengan kebutuhan data yang berbeda, ketika data Anda sangat relasional, atau ketika tim frontend Anda membutuhkan fleksibilitas untuk iterasi pada kebutuhan data tanpa perubahan backend.
REST adalah pilihan yang tepat untuk: API publik (ekosistem yang lebih baik untuk generasi client dan dokumentasi dengan OpenAPI/Swagger), upload file dan data biner, layanan CRUD sederhana di mana resource memetakan langsung ke tabel database, ketika HTTP caching di level CDN sangat penting untuk performa, dan ketika konsumen Anda adalah developer pihak ketiga yang tidak familiar dengan GraphQL.
GraphQL paling bermanfaat ketika: Anda memiliki pola BFF (backend for frontend) dengan beberapa UI client, model data Anda adalah sebuah graph (social network, knowledge graph), tim frontend Anda perlu iterasi pada kebutuhan data tanpa menunggu perubahan backend, Anda menginginkan satu typed schema sebagai kontrak antara frontend dan backend, atau Anda membangun platform developer di mana fleksibilitas dan eksplorasi dihargai.
Keduanya tidak saling eksklusif. Banyak sistem skala besar menggunakan REST untuk CRUD resource sederhana dan operasi file, sambil mengekspos endpoint GraphQL untuk pengambilan data relasional yang kompleks oleh UI client utama mereka.
Terlepas dari apakah Anda memilih REST atau GraphQL, beberapa prinsip berlaku secara universal: gunakan konvensi penamaan yang konsisten, rancang untuk kebutuhan client (bukan schema database), lakukan versioning dengan bijak, kembalikan error yang bermakna dengan pesan yang dapat ditindaklanjuti, dokumentasikan secara menyeluruh, dan perlakukan API Anda sebagai produk yang memiliki konsumen yang bergantung pada stabilitas.
Konsep desain API kunci yang dibahas dalam postingan ini meliputi REST, GraphQL, over-fetching, under-fetching, N+1 problem, and DataLoader.