Skip to content
Santekno.com | Level Up Your Engineering Skills
ID
📖 0%
21 Jul 2025 · 6 mnt baca ·Artikel 21 / 125
Go

21 Best Practice dalam Mendesain Skema GraphQL

IH
Ihsan Arif
Penulis di Santekno · Backend Engineer

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:

graphql
1type Usr { nm: String }
Contoh baik:
graphql
1type User { name: String }


2. Strukturkan Query Root Field secara Topikal

Kelompokkan entity utama di root query agar mudah ditemukan client.

graphql
1type Query {
2  user(id: ID!): User
3  post(id: ID!): Post
4  search(query: String!): [SearchResult!]
5}


3. Gunakan Scalar dan Custom Scalar Separuh Pakai

Definisikan scalar customized seperti DateTime atau Email untuk validasi yang lebih baik.

graphql
1scalar DateTime
2
3type User {
4  birthDate: DateTime
5}


4. Manfaatkan Input Type pada Mutasi

Alih-alih menaruh banyak argumen pada fungsi mutasi, gunakan input object.

graphql
1input CreatePostInput {
2  title: String!
3  content: String!
4}
5
6type Mutation {
7  createPost(input: CreatePostInput!): Post!
8}


5. Pakai Non-Null (!) Jika Data Harus Ada

Bantu client developer dengan menandai field yang “wajib” (!).

graphql
1type User {
2  id: ID!
3  email: String!
4  phone: String
5}


6. Dukung Pagination dan Batasi Query yang Over-fetching

Gunakan pola relay (edges & pageInfo) atau cukup limit, offset sederhana.

graphql
1type Query {
2  posts(first: Int, after: String): PostConnection!
3}


7. Adopsi Standard Connection Pattern untuk Pagination

Bentuknya seperti ini:

graphql
1type PostConnection {
2  pageInfo: PageInfo!
3  edges: [PostEdge!]
4}
5
6type PostEdge {
7  node: Post!
8  cursor: String!
9}
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).

graphql
1directive @auth(role: Role!) on FIELD_DEFINITION
2
3type Query {
4  me: User! @auth(role: USER)
5  adminPanel: Panel! @auth(role: ADMIN)
6}


9. Ekspos Error Secara Terstruktur

Gunakan pola union/error payloads.

graphql
1union CreateUserResult = User | ValidationError
2
3type Mutation {
4  createUser(input: CreateUserInput!): CreateUserResult!
5}


10. Jangan Over-Nest! Hindari Skema yang Terlalu Dalam

Terlalu banyak level membuat query sulit di-maintain dan riskan N+1 masalah.

graphql
 1# Hindari kedalaman relasi tak terkontrol
 2query {
 3  user {
 4    posts {
 5      comments {
 6        author {
 7          posts { ... }
 8        }
 9      }
10    }
11  }
12}
Terapkan depth limit di backend.


11. Tambah Simulasi Rate Limiting via Directive

graphql
1directive @rateLimit(max: Int, window: String) on FIELD_DEFINITION
2
3type Query {
4  heavyResource: Data! @rateLimit(max: 5, window: "1m")
5}

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.

graphql
1"""
2User is a person that uses the app
3"""
4type User {
5  ...
6}


14. Pisahkan Mutation untuk Create, Update, Delete

Jangan buat satu mutation “savePost”. Dipisah demi kejelasan.

graphql
1type Mutation {
2  createPost(...): Post!
3  updatePost(...): Post!
4  deletePost(id: ID!): Boolean!
5}


15. Avoid Breaking Changes—Versioning Bijak

Hindari modifikasi/removal field/type secara tiba-tiba. Gunakan deprecated.

graphql
1type User {
2  oldField: String @deprecated(reason: "Use newField instead")
3  newField: String
4}


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.

graphql
1enum OrderStatus {
2  PENDING
3  PROCESSED
4  DELIVERED
5}
Union untuk multi tipe hasil query.


18. Gunakan Fragments untuk Konsistensi Query Client

Di sisi client, fragment membantu hindari duplikasi struktur query.

graphql
1fragment userInfo on User {
2  id
3  name
4  avatar
5}


19. Tambahkan Field Metadata pada Edge-case

Seperti totalCount untuk pagination, hasMore untuk lazy load, dst.

graphql
1type PageInfo {
2  hasNextPage: Boolean!
3  totalCount: Int
4}


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:

graphql
 1input CreateUserInput {
 2  name: String!
 3  email: String!
 4  password: String!
 5}
 6
 7type ValidationError {
 8  field: String!
 9  message: String!
10}
11
12union CreateUserResult = User | ValidationError
13
14type Mutation {
15  createUser(input: CreateUserInput!): CreateUserResult!
16}

Contoh Resolver Pseudocode

js
1async function createUser(parent, args, context) {
2  const { name, email, password } = args.input
3  const errors = validateUser(args.input)
4  if (errors.length > 0) {
5    return { field: errors[0].field, message: errors[0].message }
6  }
7  const user = await db.user.create({ name, email, passwordHash: hash(password) })
8  return user
9}

Visualisasi: Diagram Alur CreateUser

MERMAID
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

PraktikManfaat
Konsistensi namingMudah dipahami seluruh tim
Custom scalarValidasi & tipe data lebih baik
InputType untuk mutationMudah scaling & maintain param
Pagination standarKonsistensi pattern konsumsi data
Authorization directiveJelas mana yang butuh otentikasi
Field Non-nullClient aware data harus ada
CI/CD linterCegah bug & breaking change
Enum & unionTipe aman, query powerful
Dokumentasi skemaOtomatisasi 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 🚀


Danger

References:

Artikel Terkait

💬 Komentar