Skip to content
Santekno.com | Tech Tutorials and Trends
ID
📖 0%
21 Oct 2025 · 6 mnt baca ·Artikel 113 / 125
Go

113 Custom Validation di Resolver gqlgen

IH
Ihsan Arif
Penulis di Santekno · Backend Engineer

113 Custom Validation di Resolver gqlgen

Saat membangun API dengan GraphQL di Golang, salah satu package andalan adalah gqlgen. Framework ini punya kemampuan powerful dalam menyusun schema, membangun resolver, hingga auto-generation kode berdasarkan schema yang kita desain. Tapi ada satu masalah klasik: validasi data.

Sebagai engineer yang sudah beberapa kali memakai gqlgen, saya sadar validasi data—terutama custom validation—sering jadi area “abu-abu”. Tidak sedikit orang yang hanya mengandalkan validasi di layer atas (misal saat menerima request), atau bahkan baru memvalidasi sebelum operasi ke database. Padahal, letak validasi di resolver sendiri bisa jadi solusi performa dan arsitektur yang lebih solid.

Di artikel ini, saya akan breakdown 113 custom validation di Resolver gqlgen: apa, bagaimana, dan best practice-nya. Saya akan lengkapi dengan contoh kode, simulasi, tabel, serta diagram supaya kamu benar-benar paham dan bisa langsung aplikasikan di project-mu.


Mengapa Perlu Custom Validation di Resolver?

Sebelum masuk ke “bagaimana”, mari cari tahu “kenapa” dulu. Biasanya pertanyaan saya saat mendesain API adalah:

  • Apakah validasi perlu di schema (menggunakan directive)?
  • Apakah validasi better di model/database level saja?
  • Bagaimana dengan validasi yang sifatnya kompleks (cross-field, multi-model)?

Jika jawabannya pada salah satu kondisi di bawah, custom validation di resolver adalah jawabannya:

No.Kondisi Kebutuhan ValidasiSolusi Ideal
1Validasi spesifik yang tidak bisa diakomodasi directive/schema sajaCustom code di resolver
2Validasi yang melibatkan query ke data lain (cross-table/model validation)Resolver + repo/service
3Validasi yang berhubungan antara banyak field pada satu objekResolver (logic cross-field)
4Validasi yang output-nya error khusus (custom error message, status code)Resolver dengan error handling
5Validasi on-demand, hanya di certain mutation/queyLokal di resolver terkait

Jadi, validasi di resolver = fleksibel + powerful + bisa custom error handling.


Fondasi: Resolver di gqlgen

Sebelum bicara custom validation, sedikit refresh soal resolver di gqlgen.

graphql
 1# schema.graphqls
 2type Mutation {
 3  registerUser(input: RegisterUserInput!): UserResponse!
 4}
 5
 6input RegisterUserInput {
 7  username: String!
 8  email: String!
 9  password: String!
10}

Lalu di Go:

go
1// resolver.go
2func (r *mutationResolver) RegisterUser(ctx context.Context, input model.RegisterUserInput) (*model.UserResponse, error) {
3  // Implementasi di sini, termasuk custom validation
4}

Pola Basic: Custom Validation di Resolver

Pattern custom validation di resolver cukup sederhana:

  1. Ambil input dari argumen fungsi resolver
  2. Jalankan semua rules validasi (bisa dengan helper, struct tag, kondisi manual)
  3. Return error jika tidak valid

Contoh:

go
 1func (r *mutationResolver) RegisterUser(ctx context.Context, input model.RegisterUserInput) (*model.UserResponse, error) {
 2  if len(input.Username) < 5 {
 3    return nil, errors.New("username minimal 5 karakter")
 4  }
 5  if !strings.Contains(input.Email, "@") {
 6    return nil, errors.New("format email tidak valid")
 7  }
 8  if len(input.Password) < 8 {
 9    return nil, errors.New("password harus minimal 8 karakter")
10  }
11  
12  // Validasi lainnya...
13
14  // Proses user registration dan return UserResponse
15}

Simulasi 113 Custom Validation

Saya akan simulasikan 10 kategori validasi (dengan total 113 rules) yang sering ditemui:

No.KategoriContoh RuleDeskripsi Singkat
1LengthMin/max karakterUsername min 5, password min 8
2Email formatValid email regexEmail harus valid
3Uniqueness DBEmail/username sudah adaQuery DB sebelum insert
4Strong password checkKombinasi angka, huruf besar/kecilPassword kuat
5Cross-field ruleKonfirmasi password matchpassword == confirmPassword
6Date/time validationValid tanggal lahirBukan future date
7Enum validationGender = [“male”, “female”, “other”]Validasi pilihan
8Nested object validationValidasi field dalam object complexAlamat lengkap
9Custom business rulesMinimal umur, status user aktifUmur > 17
10Custom error typeKode error, field error mappingPesan error field spesifik

Simulasi implementasi untuk 3 rules: format, unik di DB, konfirmasi password

go
 1func (r *mutationResolver) RegisterUser(ctx context.Context, input model.RegisterUserInput) (*model.UserResponse, error) {
 2  // 1. Email valid
 3  if !validateEmail(input.Email) {
 4    return nil, fmt.Errorf("Email '%s' tidak valid", input.Email)
 5  }
 6
 7  // 2. Unik di DB
 8  exists, _ := r.UserRepo.IsEmailExist(ctx, input.Email)
 9  if exists {
10    return nil, errors.New("Email sudah digunakan")
11  }
12
13  // 3. Konfirmasi password
14  if input.Password != input.ConfirmPassword {
15    return nil, errors.New("Konfirmasi password tidak sesuai")
16  }
17
18  // ... lanjutkan logic normal
19}
20
21func validateEmail(email string) bool {
22    re := regexp.MustCompile(`^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$`)
23    return re.MatchString(email)
24}

Best Practice: Validasi Lebih Modular

Kebanyakan engineer akan kewalahan kalau ada lebih dari 5 validasi per endpoint. Supaya tetap clean, gunakan pattern modular validation.

Misal: Buat package validator/ dengan helper.

go
 1package validator
 2
 3import (
 4    "regexp"
 5    "errors"
 6)
 7
 8func ValidateRegisterUserInput(input *model.RegisterUserInput) error {
 9    if len(input.Username) < 5 {
10      return errors.New("username min 5 karakter")
11    }
12    if !validateEmail(input.Email) {
13      return errors.New("email format tidak valid")
14    }
15    // ... dan seterusnya
16    return nil
17}

Di resolver:

go
1if err := validator.ValidateRegisterUserInput(&input); err != nil {
2  return nil, err
3}

Mapping Error ke Field (User Experience)

Error di GraphQL secara default akan dilemparkan jadi satu array. Namun, UX akan jauh lebih baik jika field error dikembalikan per field.

Schema:

graphql
 1type UserResponse {
 2  success: Boolean!
 3  user: User
 4  errors: [FieldError!]
 5}
 6
 7type FieldError {
 8  field: String!
 9  message: String!
10}

Resolver:

go
 1func (r *mutationResolver) RegisterUser(ctx context.Context, input model.RegisterUserInput) (*model.UserResponse, error) {
 2  var errors []*model.FieldError
 3
 4  if len(input.Username) < 5 {
 5    errors = append(errors, &model.FieldError{Field: "username", Message: "username min 5 karakter"})
 6  }
 7  if !validateEmail(input.Email) {
 8    errors = append(errors, &model.FieldError{Field: "email", Message: "format email tidak valid"})
 9  }
10
11  if len(errors) > 0 {
12    return &model.UserResponse{Success: false, Errors: errors}, nil
13  }
14
15  // lanjutkan proses...
16}

Diagram Alur Validasi di Resolver (Mermaid)

Kadang gambar lebih mudah membekas. Berikut flow custom validation di resolver gqlgen.

MERMAID
flowchart TD
    A[Receive GraphQL Mutation] --> B[Parse Input]
    B --> C[Custom Validation di Resolver]
    C -->|Valid| D[Proses Data (DB, Service, dsb)]
    C -->|Invalid| E[Return Error/FieldError]
    D --> F[Return Response (User data, Success)]
    E --> F

Kesimpulan

Custom validation di resolver gqlgen sebetulnya bukan sekadar fitur, melainkan salah satu kunci menjaga data integrity di aplikasi kita. Dengan 113 rules (atau bahkan lebih), kamu bisa atur step-by-step logic validasi dari yang paling sederhana hingga kompleks—termasuk pengembalian error yang ramah UX.

Saran saya:

  • Jangan overload resolver-mu; gunakan modular helper.
  • Gunakan mapping field error agar consumers mudah paham gagal di mana.
  • Validasi di resolver adalah mid-layer yang melindungi data — jangan cuma berharap pada validasi schema atau DB constraint.

Selamat mencoba, semoga API gqlgen-mu makin kokoh dan maintainable!

Artikel Terkait

💬 Komentar