tutorial

117 Membuat Directive Kustom di gqlgen

117 Membuat Directive Kustom di gqlgen

Jika Anda sudah lama berkutat dengan GraphQL di ekosistem Go, kemungkinan besar Anda tak asing dengan gqlgen. Library satu ini memang jadi standar de facto untuk membangun server GraphQL di Go. Fitur-fiturnya yang eksplisit dan strongly typed, membuatnya unggul dalam kode maintainable dan debuggable. Namun, ada satu fitur yang seringkali kurang dimanfaatkan: Custom Directives.

Pada artikel ke-117 kali ini, saya akan mengajak Anda mengeksplorasi pembuatan directive kustom di gqlgen. Kita tidak hanya akan membahas kode, tapi juga alasan teknis di baliknya, alur request, simulasinya, dan beberapa best practice yang saya temukan sepanjang pengalaman mengimplementasikan fitur ini di beberapa production system.


1. Mengapa Directive Kustom Penting?

Directive di GraphQL adalah semacam annotation, yang bisa kita letakkan di schema atau query untuk memberikan instruksi khusus saat resolver bekerja. Beberapa contoh built-in directive adalah @skip atau @include. Namun seringkali, kita perlu business logic spesifik—seperti otorisasi, logging, validasi input, atau masking data. Di sinilah custom directive berperan.


2. Flow Eksekusi Directive di gqlgen

Sebelum masuk ke kode, pahami dulu bagaimana gqlgen menjalankan directive. Diagram alurnya kira-kira seperti ini:

flowchart TD
  A[Client Request] --> B(GraphQL Server Parse Query)
  B --> C{Ada Directive?}
  C -- Ya --> D[Eksekusi Logic Directive]
  D --> E[Eksekusi Resolver]
  C -- Tidak --> E
  E --> F[Return Response ke Client]

Catatan:

  • Setiap field/table di schema yang memakai directive akan diproses oleh fungsi middleware directive sebelum resolver utama dieksekusi.
  • Logic pada directive bisa memutus atau membiarkan request berjalan.

3. Studi Kasus: Membuat Directive @auth(role: "ADMIN")

Problem

Misal, Anda ingin agar field tertentu hanya boleh diakses user dengan peran ADMIN. Kita ingin schema seperti ini:

type Query {
  users: [User!]! @auth(role: "ADMIN")
}

type User {
  id: ID!
  name: String!
}

4. Mendefinisikan Directive di Schema

Pertama, kita modifikasi schema.graphql:

directive @auth(
  role: String!
) on FIELD_DEFINITION

5. Generate Kode Directive

Setelah mengubah schema, jalankan:

go run github.com/99designs/gqlgen generate

Ini akan menambah stub di generated.go dan butuh kita definisikan implementasinya di resolver.go atau modul lain.


6. Implementasi Logic Directive

Siapkan Konteks User

Biasanya authentication meletakkan user info di context.Context. Misal, di middleware HTTP:

type User struct {
    ID   string
    Role string
}

func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Simulasi: user sudah login sebagai "USER" atau "ADMIN"
        user := &User{ID: "1", Role: r.Header.Get("X-Role")}
        ctx := context.WithValue(r.Context(), "user", user)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

Implementasi @auth Directive

Buka resolver.go, cari (atau buat):

func (r *directiveResolver) Auth(ctx context.Context, obj interface{}, next graphql.Resolver, role string) (res interface{}, err error) {
    userVal := ctx.Value("user")
    user, ok := userVal.(*User)
    if !ok || user == nil {
        return nil, fmt.Errorf("unauthenticated")
    }

    if user.Role != role {
        return nil, fmt.Errorf("unauthorized: need role %s", role)
    }

    // Lanjut ke resolver berikutnya
    return next(ctx)
}

Kode di atas secara singkat:

  • Ambil user dari context.
  • Jika belum login atau role tidak sesuai, return error.
  • Jika lolos, panggil next(ctx) supaya resolver berjalan.

7. Simulasi dan Testing

Buat test HTTP sederhana. Endpoint GraphQL di-wrap pakai AuthMiddleware.

Contoh HTTP Request

POST /graphql
Headers:
  X-Role: USER
Body:
  {
    "query": "{ users { id name } }"
  }

Hasil: Error unauthorized.

POST /graphql
Headers:
  X-Role: ADMIN
Body:
  {
    "query": "{ users { id name } }"
  }

Hasil: Berhasil, list user tampil.


8. Tabel Alur Eksekusi

HTTP HeaderDirective TriggerHasilKeterangan
X-Role: USER@auth(“ADMIN”)ErrorDitolak, role tidak cocok
X-Role:@auth(“ADMIN”)ErrorDitolak, belum login
X-Role: ADMIN@auth(“ADMIN”)SuksesLolos, resolver di-eksekusi
-Tidak adaSuksesResolver tetap dieksekusi

9. Best Practice dalam Custom Directive di gqlgen

  • Isolasi Logic: Simpan logic directive di package sendiri (internal/directive/), supaya mudah unit test.
  • Reusable: Gunakan context dan type assertion, hindari global variable.
  • Error Handling: Return custom error type supaya mudah distinguish di frontend.
  • Composable: Bisa chaining lebih dari 1 directive, urutan di schema berpengaruh.

10. Simulasi Multi-Directive

Misal, ingin field sembari @auth dan @log.

type Query {
  sensitiveInfo: String! @auth(role: "ADMIN") @log
}

gqlgen akan mengeksekusi dari kiri ke kanan: @auth dulu, lalu @log, lalu resolver utama.


11. Penutup & Refleksi

Dengan custom directive di gqlgen, Anda bisa memindahkan logic umum seperti otorisasi, audit, dan lain-lain ke layer schema. Ini memperjelas separation of concerns, dan membuat schema benar-benar self-documenting.

Penerapannya tidak sulit, namun kunci sukses di production adalah konsistensi, testing, serta desain konteks yang robust. Implementasi di atas hanya contoh sederhana—Anda tentu bisa mengembangkannya untuk kebutuhan di organisasi Anda, misalnya dynamic permission, rate-limiting, atau masking data secara otomatis.

Selamat bereksperimen dengan custom directive di gqlgen, semoga arsitektur GraphQL Anda makin robust dan maintainable!


Referensi:


Kritik, saran, dan diskusi sangat terbuka di kolom komentar. Sampai bertemu di artikel selanjutnya!

comments powered by Disqus