Kuasai GraphQL dari fundamental hingga produksi. Pahami mengapa GraphQL ada, bagaimana perbandingannya dengan REST dan gRPC, dan bangun aplikasi real-world dengan NestJS.

GraphQL telah mengubah cara kita berpikir tentang API. Sejak Facebook membuka sumbernya pada 2015, GraphQL menjadi pilihan utama untuk perusahaan yang membangun aplikasi kompleks berbasis data. Namun GraphQL bukan hanya standar API lain—ini adalah perubahan paradigma dalam cara klien dan server berkomunikasi.
Jika Anda telah membangun REST API selama bertahun-tahun, GraphQL mungkin terasa berlebihan pada awalnya. Tetapi setelah memahami masalah yang dipecahkannya, Anda akan melihat mengapa Netflix, GitHub, Shopify, dan ribuan perusahaan lain telah mengadopsinya.
Panduan ini membawa Anda dari fundamental GraphQL hingga implementasi siap produksi menggunakan NestJS. Kami akan mengeksplorasi sejarah, membandingkannya dengan REST dan gRPC, dan membangun use case real-world yang penting.
Pada 2012, tim engineering mobile Facebook menghadapi tantangan kritis. Aplikasi iOS mereka lambat, mengonsumsi bandwidth berlebihan, dan memerlukan banyak panggilan API untuk merender satu layar. REST API yang mereka bangun dioptimalkan untuk browser web, bukan perangkat mobile dengan bandwidth dan konektivitas terbatas.
Setiap endpoint mengembalikan struktur data tetap. Jika aplikasi mobile hanya membutuhkan tiga field dari objek user yang berisi dua puluh field, tetap menerima semua dua puluh field. Masalah over-fetching ini membuang bandwidth dan daya baterai.
Lebih buruk lagi, jika aplikasi membutuhkan data dari banyak resource (user, posts, comments), diperlukan banyak round-trip ke endpoint berbeda. Masalah under-fetching ini berarti waktu loading lebih lambat dan logika client-side lebih kompleks.
Pada 2012, Lee Byron, Dan Schafer, dan Nick Schrock dari Facebook mulai bereksperimen dengan pendekatan baru. Mereka menyebutnya GraphQL—bahasa query yang memungkinkan klien meminta data yang mereka butuhkan, tidak lebih, tidak kurang.
Tim menghabiskan tiga tahun menyempurnakan konsep secara internal sebelum membuka sumbernya pada 2015. Respons langsung. Developer mengenali bahwa GraphQL memecahkan masalah nyata yang mereka hadapi setiap hari.
Hari ini, GraphQL menggerakkan API yang melayani miliaran request setiap hari.
Endpoint REST mengembalikan struktur data tetap. Endpoint /users/123 mungkin mengembalikan:
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"address": "123 Main St",
"bio": "Software engineer",
"createdAt": "2020-01-15",
"updatedAt": "2024-03-02"
}Tetapi aplikasi mobile Anda hanya membutuhkan id, name, dan email. Anda mengunduh data yang tidak perlu, membuang bandwidth dan baterai.
Dengan GraphQL, Anda meminta persis apa yang Anda butuhkan:
query {
user(id: 123) {
id
name
email
}
}REST sering memerlukan banyak request. Untuk menampilkan profil user dengan post dan komentar terbaru mereka, Anda mungkin memerlukan:
GET /users/123
GET /users/123/posts
GET /users/123/commentsTiga round-trip. Tiga kesempatan untuk latency jaringan berkompilasi.
GraphQL mengambil semuanya dalam satu request:
query {
user(id: 123) {
name
posts {
title
content
}
comments {
text
createdAt
}
}
}REST API sering memerlukan versioning (/v1/, /v2/) karena mengubah struktur response merusak klien. Mengelola banyak versi adalah mimpi buruk maintenance.
GraphQL berkembang dengan lancar. Anda menambah field baru tanpa merusak query yang ada. Klien meminta hanya field yang mereka butuhkan, jadi klien lama terus bekerja sementara klien baru menggunakan field baru.
Aplikasi mobile perlu meminimalkan transfer data. Pengambilan data presisi GraphQL sempurna untuk mobile, IoT, dan lingkungan bandwidth rendah.
Kekuatan:
Kelemahan:
Terbaik untuk: Aplikasi CRUD sederhana, API publik dengan pola akses dapat diprediksi, ketika caching kritis.
Kekuatan:
Kelemahan:
Terbaik untuk: Hubungan data kompleks, banyak tipe klien, API yang berkembang cepat, API internal, aplikasi mobile-first.
Kekuatan:
Kelemahan:
Terbaik untuk: Komunikasi microservices, sistem high-performance, aplikasi real-time, komunikasi service-to-service internal.
| Skenario | Pilihan Terbaik | Alasan |
|---|---|---|
| API CRUD sederhana | REST | Kesederhanaan, caching, tooling standar |
| Banyak tipe klien (web, mobile, IoT) | GraphQL | Pengambilan data presisi, single request |
| Komunikasi microservices | gRPC | Performa, streaming, strong typing |
| Streaming data real-time | gRPC atau WebSocket | Low latency, bidirectional |
| API publik dengan akses dapat diprediksi | REST | Caching, kesederhanaan, discoverability |
| Hubungan data kompleks | GraphQL | Mengurangi round-trip, pengambilan presisi |
| Sistem high-frequency, real-time | gRPC | Performa, low latency |
Schema adalah kontrak GraphQL antara klien dan server. Ini mendefinisikan data apa yang tersedia dan cara query-nya.
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
createdAt: DateTime!
}
type Query {
user(id: ID!): User
posts(limit: Int = 10): [Post!]!
}Konsep kunci:
! berarti non-nullable (required)[Post!]! berarti list non-null dari Posts non-nullQuery adalah entry point untuk operasi readQueries mengambil data. Mereka adalah operasi read-only.
query GetUserWithPosts {
user(id: "123") {
name
email
posts {
title
createdAt
}
}
}Mutations memodifikasi data. Mereka adalah operasi write.
mutation CreatePost {
createPost(input: {
title: "GraphQL Guide"
content: "Complete guide to GraphQL"
authorId: "123"
}) {
id
title
createdAt
}
}Subscriptions memungkinkan update data real-time via WebSocket.
subscription OnPostCreated {
postCreated {
id
title
author {
name
}
}
}Resolvers adalah fungsi yang mengembalikan data untuk setiap field. Mereka adalah jembatan antara schema dan data sources.
const resolvers = {
Query: {
user: (parent, args, context) => {
return database.users.findById(args.id);
}
},
User: {
posts: (parent, args, context) => {
return database.posts.findByAuthorId(parent.id);
}
}
};Pitfall umum. Jika Anda mengambil 100 user dan resolver setiap user query database untuk posts mereka, Anda telah membuat 101 database queries.
// BAD: N+1 queries
User: {
posts: (parent) => {
return db.query('SELECT * FROM posts WHERE author_id = ?', [parent.id]);
}
}
// GOOD: Gunakan DataLoader untuk batching
import DataLoader from 'dataloader';
const postLoader = new DataLoader(async (userIds) => {
const posts = await db.query(
'SELECT * FROM posts WHERE author_id IN (?)',
[userIds]
);
return userIds.map(id => posts.filter(p => p.author_id === id));
});
User: {
posts: (parent) => postLoader.load(parent.id)
}Directives memodifikasi perilaku eksekusi query.
query GetUser {
user(id: "123") {
name
email @include(if: $includeEmail)
phone @skip(if: $skipPhone)
}
}npm install @nestjs/graphql @nestjs/apollo graphql apollo-server-express
npm install -D @types/nodeimport { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { UserService } from './user.service';
import { User } from './user.entity';
import { CreateUserInput } from './dto/create-user.input';
@Resolver(() => User)
export class UserResolver {
constructor(private userService: UserService) {}
@Query(() => User)
async user(@Args('id') id: string) {
return this.userService.findById(id);
}
@Query(() => [User])
async users() {
return this.userService.findAll();
}
@Mutation(() => User)
async createUser(@Args('input') input: CreateUserInput) {
return this.userService.create(input);
}
}import { ObjectType, Field, ID } from '@nestjs/graphql';
import { Post } from '../post/post.entity';
@ObjectType()
export class User {
@Field(() => ID)
id: string;
@Field()
name: string;
@Field()
email: string;
@Field(() => [Post])
posts: Post[];
@Field()
createdAt: Date;
}Mari kita bangun sistem e-commerce praktis dengan products, orders, dan inventory management.
Masalah: Setiap field resolver query database secara independen.
Solusi: Gunakan DataLoader untuk batching queries.
// ❌ BAD
@ResolveField()
async author(@Parent() post: Post) {
return db.query('SELECT * FROM users WHERE id = ?', [post.authorId]);
}
// ✅ GOOD
@ResolveField()
async author(@Parent() post: Post) {
return this.userLoader.load(post.authorId);
}Masalah: Klien dapat meminta data nested dalam, menyebabkan masalah performa.
query {
user {
posts {
comments {
author {
posts {
comments {
# ... infinite nesting
}
}
}
}
}
}
}Solusi: Implementasikan query depth limiting dan complexity analysis.
import { depthLimit } from 'graphql-depth-limit';
GraphQLModule.forRoot({
validationRules: [depthLimit(5)],
});Masalah: Error tidak diformat atau dicatat dengan benar.
Solusi: Implementasikan custom error handling.
import { Catch, ArgumentsHost } from '@nestjs/common';
import { GqlExceptionFilter } from '@nestjs/graphql';
@Catch()
export class GraphQLErrorFilter implements GqlExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
return {
message: exception.message,
code: exception.code || 'INTERNAL_SERVER_ERROR',
timestamp: new Date().toISOString(),
};
}
}Masalah: Response GraphQL tidak di-cache secara efektif.
Solusi: Implementasikan HTTP caching headers dan response caching.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Response } from 'express';
@Injectable()
export class CacheMiddleware implements NestMiddleware {
use(req: any, res: Response, next: Function) {
res.set('Cache-Control', 'public, max-age=300');
next();
}
}Kurangi bandwidth dengan mengirim query IDs daripada full query strings.
GraphQLModule.forRoot({
persistedQueries: {
cache: new InMemoryLRUCache(),
},
});Cegah abuse dan DoS attacks.
import { RateLimiterMemory } from 'rate-limiter-flexible';
const rateLimiter = new RateLimiterMemory({
points: 100,
duration: 60,
});
GraphQLModule.forRoot({
context: async ({ req }) => {
try {
await rateLimiter.consume(req.ip);
} catch {
throw new Error('Too many requests');
}
},
});Track slow queries dan optimalkan mereka.
GraphQLModule.forRoot({
plugins: [
{
requestDidStart: () => ({
didResolveOperation: ({ operationName, document }) => {
console.log(`Operation: ${operationName}`);
},
willSendResponse: ({ operationName, errors }) => {
if (errors) {
console.error(`Errors in ${operationName}:`, errors);
}
},
}),
},
],
});Subscriptions mempertahankan open connections. Monitor memory usage.
@Subscription(() => Order)
orderCreated() {
return pubSub.asyncIterator(['ORDER_CREATED']);
}Lindungi data sensitif.
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
@Injectable()
export class GqlAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const gqlContext = GqlExecutionContext.create(context);
const { req } = gqlContext.getContext();
return !!req.user;
}
}GraphQL bukan silver bullet. Pertimbangkan alternatif ketika:
GraphQL merepresentasikan perubahan fundamental dalam desain API. Ini memecahkan masalah nyata yang dihadapi developer REST setiap hari: over-fetching, under-fetching, kompleksitas versioning, dan transfer data mobile tidak efisien.
Ketika Anda memahami konsep inti GraphQL—schemas, resolvers, queries, mutations, dan subscriptions—Anda dapat membangun API yang scale dengan kompleksitas aplikasi Anda. NestJS membuat implementasi GraphQL siap produksi straightforward dengan decorators dan dependency injection-nya.
Contoh e-commerce mendemonstrasikan bagaimana GraphQL menangani skenario real-world: hubungan data kompleks, inventory management, dan transaction handling. DataLoader mencegah N+1 queries, proper error handling memastikan reliability, dan monitoring menjaga performa.
Mulai dengan endpoint GraphQL kecil. Ukur dampak pada performa aplikasi mobile Anda. Setelah mengalami benefits, Anda akan memahami mengapa GraphQL menjadi standar untuk desain API modern.
Langkah selanjutnya: