tutorial

115 Error Handling Terstruktur di Mutation gqlgen

115 Error Handling Terstruktur di Mutation gqlgen

Dalam pengembangan aplikasi modern, GraphQL menjadi salah satu backbone penting untuk komunikasi antara client dan server. Salah satu framework populer untuk implementasi GraphQL di Golang adalah gqlgen. Namun, sering kali developer melupakan penanganan error ataupun membiarkan error keluar tanpa pola yang jelas, terutama di ranah mutation. Pada artikel kali ini, saya akan membahas secara mendalam: “115 Error Handling Terstruktur di Mutation gqlgen”.

Kenapa saya namakan 115 error handling? Karena pada kenyataannya, masalah error yang muncul umumnya bisa dibagi ke dalam 3 layer (1xx: validation, 1xx: authentication/authorization, 5xx: business/internal error), dan total best practice handling error di production system bisa sampai ratusan pola. Sekarang, kita akan breakdown dan implementasi structured error handling pada mutation gqlgen.


🔥 Flat Error Handling vs Structured Error Handling

Banyak codebase GraphQL yang hanya melakukan hal berikut pada mutation:

func (r *mutationResolver) UpdateUser(ctx context.Context, input UpdateUserInput) (*User, error) {
    user, err := updateUserInDB(input)
    if err != nil {
        return nil, err // Tidak jelas error-nya!
    }
    return user, nil
}

Hasilnya seperti ini ketika dikonsumsi client:

{
  "errors": [
    {
      "message": "sql: no rows in result set"
    }
  ],
  "data": {
    "updateUser": null
  }
}

Problem:

  1. Error flat tanpa detail
  2. Tidak ada code/status
  3. Tidak ada guidance untuk client

🧑‍💻 Best Practice: Structured Error Handling di gqlgen

1. Mendefinisikan Format Error Response

Langkah pertama adalah sepakat pada satu format standard error object. Biasanya, format yang optimal untuk client seperti berikut:

FieldTipeContohDeskripsi
codestring“NOT_FOUND”Kode error spesifik
messagestring“User not found”Pesan untuk user
fieldstring“email”Field terkait error (opsi)
extensionsmap{“level”: “warning”}Metadata tambahan

Contoh struktur di custom error packet:

package errors

type GraphQLError struct {
    Code      string                 `json:"code"`
    Message   string                 `json:"message"`
    Field     string                 `json:"field,omitempty"`
    Extensions map[string]interface{} `json:"extensions,omitempty"`
}

func NewGraphQLError(code, message string, field ...string) *GraphQLError {
    err := &GraphQLError{
        Code:    code,
        Message: message,
    }
    if len(field) > 0 {
        err.Field = field[0]
    }
    return err
}

2. Membuat Helper Untuk Mengkonversi Error Go ke GraphQL Error

Kita perlu menyesuaikan error Go agar bisa tampil dalam format GraphQL error extensions.

import "github.com/vektah/gqlparser/v2/gqlerror"

// Convert custom error ke gqlerror
func ToGQLError(e *GraphQLError) *gqlerror.Error {
    gqlErr := &gqlerror.Error{
        Message: e.Message,
        Extensions: map[string]interface{}{
            "code": e.Code,
        },
    }
    if e.Field != "" {
        gqlErr.Extensions["field"] = e.Field
    }
    for k, v := range e.Extensions {
        gqlErr.Extensions[k] = v
    }
    return gqlErr
}

💡 Simulasi dan Implementasi di Mutation Resolver

Mari kita simulasi resolver mutasi di gqlgen.

func (r *mutationResolver) UpdateUser(ctx context.Context, input UpdateUserInput) (*User, error) {
    if input.Email == "" {
        gErr := errors.NewGraphQLError("INVALID_ARGUMENT", "Email is required", "email")
        return nil, errors.ToGQLError(gErr)
    }

    user, err := repo.FindUserByID(input.ID)
    if err == sql.ErrNoRows {
        gErr := errors.NewGraphQLError("NOT_FOUND", "User not found")
        return nil, errors.ToGQLError(gErr)
    } else if err != nil {
        gErr := errors.NewGraphQLError("INTERNAL_SERVER_ERROR", "Internal error", "id")
        return nil, errors.ToGQLError(gErr)
    }

    // Update user logic...
    user.Email = input.Email
    err = repo.SaveUser(user)
    if err != nil {
        gErr := errors.NewGraphQLError("INTERNAL_SERVER_ERROR", "Failed updating user")
        return nil, errors.ToGQLError(gErr)
    }
    return user, nil
}

⚡️ Variasi Error Kode yang Umum: 115 Error Scenarios

Untuk production scale, kita perlu mapping setidaknya >100 error code untuk berbagai jenis error. Berikut adalah contoh subset 10 dari error code mapping table:

CodeHttp EquivalentPesan HumanBiasanya Untuk
INVALID_ARGUMENT400Field x is invalidValidasi input field
NOT_FOUND404Data not foundEntity/data tidak ada
UNAUTHORIZED401Login firstUser blm login
FORBIDDEN403Access deniedTidak punya permission
ALREADY_EXISTS409Email already takenUser/email unique constraint
INTERNAL_SERVER_ERROR500Something went wrongError tidak diketahui
TIMEOUT504Request timeoutSlow downstream request
TOO_MANY_REQUESTS429Rate limit exceededRate limiting error
NOT_IMPLEMENTED501Feature not yet implementedFitur blm jalan
DEPENDENCY_FAILURE424Dependent service errorDownstream error
… (dll)

Di project besar, daftar mapping bisa tembus 115 kode error (hence nama artikelnya 😉).


🚦 Diagram Alur Error Handling di Mutation

Mari kita gambarkan dengan mermaid langkah-langkah flow error di resolver:

flowchart TD
    A[Client Send Mutation] --> B{Input Validation}
    B -- Invalid --> E[[Return INVALID_ARGUMENT]]
    B -- Valid --> C[Business Logic & Data Access]
    C -- Not Found --> F[[Return NOT_FOUND]]
    C -- Constraint error --> G[[Return ALREADY_EXISTS]]
    C -- Unexpected error --> H[[Return INTERNAL_SERVER_ERROR]]
    C -- Success --> I[Return Result]

📡 Output di Client Side

Dengan pendekatan ini, hasil error yang diterima client lebih rich:

{
  "errors": [
    {
      "message": "User not found",
      "extensions": {
        "code": "NOT_FOUND"
      }
    }
  ],
  "data": {
    "updateUser": null
  }
}

Client aplikasi (misal: React/Flutter) kini bisa:

  • Mapping error code untuk show toast
  • Highlight field yang error
  • Membedakan error operational vs exception

🚀 Kesimpulan

Structured error handling pada mutation di gqlgen bukan sekadar “mengembalikan err”, tapi mengubah cara tim backend dan frontend saling bekerja.

Checklist Praktik Terbaik

  • Definisikan format error object (code, message, field, extensions)
  • Implement error mapping di setiap titik potential failure
  • Gunakan helper untuk standarisasi output error gqlgen
  • Maintain daftar error code (idealnya 100++ handler)
  • Pastikan client aware & siap menghadapi error code

Error handling bukan hanya masalah teknis, tapi pondasi komunikasi antara backend dan frontend. Dengan error handling terstruktur, integrasi engineer jauh lebih produktif dan user experience lebih prima.


Referensi


Mau implementasi error handling terstruktur di GraphQL real world-mu?
Jangan ragu 🌟 diskusi di kolom komentar atau reach me lewat LinkedIn, siapa tahu error handling lambda-mu bisa naik kelas jadi lebih terstruktur!

comments powered by Disqus