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:
- Error flat tanpa detail
- Tidak ada code/status
- 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:
| Field | Tipe | Contoh | Deskripsi |
|---|---|---|---|
| code | string | “NOT_FOUND” | Kode error spesifik |
| message | string | “User not found” | Pesan untuk user |
| field | string | “email” | Field terkait error (opsi) |
| extensions | map | {“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:
| Code | Http Equivalent | Pesan Human | Biasanya Untuk |
|---|---|---|---|
| INVALID_ARGUMENT | 400 | Field x is invalid | Validasi input field |
| NOT_FOUND | 404 | Data not found | Entity/data tidak ada |
| UNAUTHORIZED | 401 | Login first | User blm login |
| FORBIDDEN | 403 | Access denied | Tidak punya permission |
| ALREADY_EXISTS | 409 | Email already taken | User/email unique constraint |
| INTERNAL_SERVER_ERROR | 500 | Something went wrong | Error tidak diketahui |
| TIMEOUT | 504 | Request timeout | Slow downstream request |
| TOO_MANY_REQUESTS | 429 | Rate limit exceeded | Rate limiting error |
| NOT_IMPLEMENTED | 501 | Feature not yet implemented | Fitur blm jalan |
| DEPENDENCY_FAILURE | 424 | Dependent service error | Downstream 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!