Episode 11 merancang strategi error handling yang efektif: struktur error GraphQL dengan extensions dan path, custom error classes, pola throwing versus returning, error masking untuk keamanan production, serta result pattern dengan union types untuk error handling yang type-safe.

Error tidak bisa dihindari dalam aplikasi nyata — database bisa down, user bisa mengirim data salah, dan koneksi bisa putus. Yang membedakan API berkualitas adalah bagaimana error ditangani dan disajikan. Episode 11 membahas error handling secara menyeluruh.
Kita akan mempelajari struktur error GraphQL, membuat custom error classes, menerapkan pola throwing versus returning, melakukan error masking demi keamanan, dan membangun result pattern dengan union types yang menjadikan error handling type-safe dari schema sampai client.
Saat resolver melempar error, GraphQL mengembalikan array errors di response:
{
"errors": [
{
"message": "Postingan tidak ditemukan",
"locations": [{ "line": 2, "column": 3 }],
"path": ["post"]
}
],
"data": null
}Field locations dan path membantu kalian menemukan field mana yang gagal. Catatan penting: ketika error terjadi pada field non-null, error akan merambat ke parent — itulah mengapa desain nullability di episode 3 berdampak langsung pada perilaku error. GraphQL juga mengembalikan data: null bila root query gagal.
Untuk membawa data terstruktur, gunakan field extensions. Apollo Server 4 secara otomatis menambahkan code pada extensions untuk error yang dilempar:
{
"errors": [
{
"message": "Anda tidak berhak",
"extensions": {
"code": "FORBIDDEN",
"reason": "role: MEMBER"
}
}
]
}Apollo Server menyediakan class GraphQLError yang bisa diperluas untuk membuat error domain kalian sendiri:
import { GraphQLError } from "graphql";
class NotFoundError extends GraphQLError {
constructor(resource) {
super(`${resource} tidak ditemukan`, {
extensions: { code: "NOT_FOUND", resource },
});
}
}Sekarang resolver cukup melempar new NotFoundError("Post"), dan client akan menerima error yang konsisten dengan kode NOT_FOUND. Pola ini menjaga konsistensi seluruh API dan mudah dipahami tim lain.
Ada dua pendekatan yang perlu kalian pilih dengan sadar:
data bernilai null.Query: {
post: async (_, args, ctx) => {
const post = await ctx.db.posts.find(args.id);
if (!post) throw new NotFoundError("Post");
return post;
},
postOrNull: async (_, args, ctx) =>
ctx.db.posts.find(args.id),
},Praktik yang dianut banyak tim: throwing untuk error yang menghentikan operasi, returning (lewat payload/union) untuk error yang perlu ditampilkan per field.
GraphQL bersifat "seluruhnya atau sebagian": field yang independen tetap dieksekusi meski field lain gagal. Manfaatkan ini untuk partial success. Sementara itu, di production, lakukan error masking — jangan pernah bocorkan stack trace dan pesan internal ke client. Log error lengkap ke server, dan kirimkan pesan generik kepada client.
Pendekatan modern: modelkan hasil operasi sebagai union dari tipe sukses dan tipe error:
union RegisterResult = RegisterSuccess | FieldErrors
type RegisterSuccess {
user: User!
}
type FieldErrors {
errors: [FieldError!]!
}
type FieldError {
field: String!
message: String!
}
type Mutation {
register(input: RegisterInput!): RegisterResult!
}Kelebihannya: client bisa mengecek ... on RegisterSuccess dan ... on FieldErrors lewat inline fragments, dan TypeScript client hasil codegen (episode 23) akan memodelkan semua kemungkinan. Ini menggabungkan error handling dengan type safety — kombinasi yang sulit dicapai pola throwing.
Di client, result pattern memaksa developer menangani kedua kasus:
mutation Daftar($input: RegisterInput!) {
register(input: $input) {
... on RegisterSuccess {
user { id username }
}
... on FieldErrors {
errors { field message }
}
}
}Tidak ada lagi kasus "lupa menangani error", karena schema memaksa kedua cabang di-select. Ini pola yang kami rekomendasikan untuk mutation yang berinteraksi dengan form dan logika bisnis kompleks.
Inti yang harus dibawa pulang:
message, locations, path, dan extensions dengan kode.GraphQLError untuk error domain yang konsisten.Di episode 12 selanjutnya kalian akan mempelajari pagination — offset-based pagination, cursor-based pagination dengan stable cursor, Relay Connection Specification dengan edge dan pageInfo, hingga best practices ukuran halaman dan loading states. Mengambil data dalam jumlah besar tidak akan lagi jadi masalah!