21 Best Practice dalam Mendesain Skema GraphQL
GraphQL telah merevolusi cara kita membangun dan mengkonsumsi API, menawarkan fleksibilitas yang sulit diberikan REST. Namun, fleksibilitas ini bisa menjadi pedang bermata dua jika kita tidak hati-hati dalam mendesain skema (schema) GraphQL. Skema yang buruk dapat menyebabkan masalah performa, keamanan, dan user experience yang tidak optimal.
Sebagai seorang engineer yang sudah beberapa tahun bekerja dengan GraphQL — dari scale-up fintech, e-commerce, hingga organisasi nirlaba — saya belajar bahwa ada “jalan ninja” unik dalam mendesain skema GraphQL agar lebih sustainable, scalable, dan mudah dipelihara. Berikut adalah 21 best practice beserta contoh, simulasi, hingga diagram alur yang biasa saya terapkan.
1. Gunakan Nama yang Konsisten dan Deskriptif
Gunakan naming convention yang mudah dipahami tim lintas fungsi. Nama field, type, dan operasi harus konsisten, mudah diingat, serta deskriptif.
Contoh buruk:
type Usr { nm: String }
Contoh baik:
type User { name: String }
2. Strukturkan Query Root Field secara Topikal
Kelompokkan entity utama di root query agar mudah ditemukan client.
type Query {
user(id: ID!): User
post(id: ID!): Post
search(query: String!): [SearchResult!]
}
3. Gunakan Scalar dan Custom Scalar Separuh Pakai
Definisikan scalar customized seperti DateTime
atau Email
untuk validasi yang lebih baik.
scalar DateTime
type User {
birthDate: DateTime
}
4. Manfaatkan Input Type pada Mutasi
Alih-alih menaruh banyak argumen pada fungsi mutasi, gunakan input object.
input CreatePostInput {
title: String!
content: String!
}
type Mutation {
createPost(input: CreatePostInput!): Post!
}
5. Pakai Non-Null (!) Jika Data Harus Ada
Bantu client developer dengan menandai field yang “wajib” (!
).
type User {
id: ID!
email: String!
phone: String
}
6. Dukung Pagination dan Batasi Query yang Over-fetching
Gunakan pola relay (edges
& pageInfo
) atau cukup limit, offset
sederhana.
type Query {
posts(first: Int, after: String): PostConnection!
}
7. Adopsi Standard Connection Pattern untuk Pagination
Bentuknya seperti ini:
type PostConnection {
pageInfo: PageInfo!
edges: [PostEdge!]
}
type PostEdge {
node: Post!
cursor: String!
}
Ini membantu konsistensi dan alat built-in GraphQL bekerja maksimal.
8. Imposisikan Authorization di Level Skema
Definisikan custom directive agar developer tahu mana field/operation yang secured (bekerja sama dengan backend auth logic).
directive @auth(role: Role!) on FIELD_DEFINITION
type Query {
me: User! @auth(role: USER)
adminPanel: Panel! @auth(role: ADMIN)
}
9. Ekspos Error Secara Terstruktur
Gunakan pola union/error payloads.
union CreateUserResult = User | ValidationError
type Mutation {
createUser(input: CreateUserInput!): CreateUserResult!
}
10. Jangan Over-Nest! Hindari Skema yang Terlalu Dalam
Terlalu banyak level membuat query sulit di-maintain dan riskan N+1 masalah.
# Hindari kedalaman relasi tak terkontrol
query {
user {
posts {
comments {
author {
posts { ... }
}
}
}
}
}
Terapkan depth limit di backend.
11. Tambah Simulasi Rate Limiting via Directive
directive @rateLimit(max: Int, window: String) on FIELD_DEFINITION
type Query {
heavyResource: Data! @rateLimit(max: 5, window: "1m")
}
12. Batasi Query Complexity di Layer Server
Implementasi instrumentasi untuk hitung “bobot” tiap field. Misal mutation dengan efek berat diberi skor tinggi.
13. Komentari Schema dengan Deskripsi
Deskripsi bermanfaat untuk dokumentasi otomatis seperti GraphQL Playground atau GraphiQL.
"""
User is a person that uses the app
"""
type User {
...
}
14. Pisahkan Mutation untuk Create, Update, Delete
Jangan buat satu mutation “savePost”. Dipisah demi kejelasan.
type Mutation {
createPost(...): Post!
updatePost(...): Post!
deletePost(id: ID!): Boolean!
}
15. Avoid Breaking Changes—Versioning Bijak
Hindari modifikasi/removal field/type secara tiba-tiba. Gunakan deprecated.
type User {
oldField: String @deprecated(reason: "Use newField instead")
newField: String
}
16. Optimalkan untuk Batching & Caching
Aplikasi DataLoader di backend untuk batching, cache resolusi child node.
17. Pakai Enum dan Union dengan Bijak
Enum untuk nilai terbatas.
enum OrderStatus {
PENDING
PROCESSED
DELIVERED
}
Union untuk multi tipe hasil query.
18. Gunakan Fragments untuk Konsistensi Query Client
Di sisi client, fragment membantu hindari duplikasi struktur query.
fragment userInfo on User {
id
name
avatar
}
19. Tambahkan Field Metadata pada Edge-case
Seperti totalCount
untuk pagination, hasMore
untuk lazy load, dst.
type PageInfo {
hasNextPage: Boolean!
totalCount: Int
}
20. Audit Security: Limit Introspection di Production
Introspection idealnya hanya aktif saat development.
21. Rawat Skema dengan Tooling CI/CD
Lint dan validate skema secara otomatis (misal pakai graphql-schema-linter), deploy preview, behavioral test di pipeline CI.
Simulasi Kasus: Alur CreateUser
Mari lihat skema, alur logic, dan error handling:
input CreateUserInput {
name: String!
email: String!
password: String!
}
type ValidationError {
field: String!
message: String!
}
union CreateUserResult = User | ValidationError
type Mutation {
createUser(input: CreateUserInput!): CreateUserResult!
}
Contoh Resolver Pseudocode
async function createUser(parent, args, context) {
const { name, email, password } = args.input
const errors = validateUser(args.input)
if (errors.length > 0) {
return { field: errors[0].field, message: errors[0].message }
}
const user = await db.user.create({ name, email, passwordHash: hash(password) })
return user
}
Visualisasi: Diagram Alur CreateUser
graph TD A[Client kirim createUser] --> B{Validasi Input} B -- Tidak valid --> C[Return ValidationError] B -- Valid --> D[Simpan ke Database] D --> E[Return User]
Tabel Checklist Best Practice
Praktik | Manfaat |
---|---|
Konsistensi naming | Mudah dipahami seluruh tim |
Custom scalar | Validasi & tipe data lebih baik |
InputType untuk mutation | Mudah scaling & maintain param |
Pagination standar | Konsistensi pattern konsumsi data |
Authorization directive | Jelas mana yang butuh otentikasi |
Field Non-null | Client aware data harus ada |
CI/CD linter | Cegah bug & breaking change |
Enum & union | Tipe aman, query powerful |
Dokumentasi skema | Otomatisasi API doc |
Penutup
Merancang skema GraphQL yang solid itu ibarat fondasi rumah digital kita. Dengan menerapkan 21 best practice di atas, Anda dan tim dapat membangun API yang scalable, mudah dipelihara, aman, dan disukai oleh developer client maupun frontend. Tidak ada satu solusi yang mutlak: terus evaluasi, adaptasi, dan belajar dari kebutuhan bisnis serta feedback tim konsumsi API.
Jika punya pengalaman unik atau tips lain, bagikan di kolom komentar!
Happy GraphQL-ing 🚀
References:
- GraphQL Official Best Practices
- Apollo GraphQL Patterns
- Pengalaman pribadi membangun skema API di startup Indonesia
43 Memanfaatkan `oneof` di Protobuf
44 Reuse Message dengan `import`
Artikel Terhangat
22 Membuat Sub-Schema Berdasarkan Modul
07 Jul 2025
44 Reuse Message dengan `import`
07 Jul 2025
43 Memanfaatkan `oneof` di Protobuf
07 Jul 2025
42 Menggunakan Enum di Protobuf
07 Jul 2025
19 Implementasi Resolver untuk Query `users`
07 Jul 2025
41 Memahami Nested Messages
07 Jul 2025

22 Membuat Sub-Schema Berdasarkan Modul

44 Reuse Message dengan `import`

43 Memanfaatkan `oneof` di Protobuf

42 Menggunakan Enum di Protobuf

19 Implementasi Resolver untuk Query `users`
