Mutations — GraphQL

Mutations Mutation mengubah data — create, update, delete. Seperti POST/PUT/DELETE di REST. Convention: mutations return objek yang diubah. Create mutation {…

Mutation adalah operasi GraphQL untuk mengubah data di server: membuat, mengubah, atau menghapus. Perannya mirip POST, PUT, PATCH, dan DELETE di REST, sedangkan query mirip GET. Bayangin kasir di toko buku. Kamu menyerahkan formulir perubahan ("tambah buku ini", "ubah stok buku itu"), kasir memprosesnya satu per satu sesuai urutan, lalu memberi struk. Isi struknya kamu sendiri yang pilih, sama seperti query.

🧪 Tebakan cepat: Satu request berisi dua mutation: first: updateStock(id: 3, stock: 4) lalu second: updateStock(id: 3, stock: 2). Stok akhir Cantik Itu Luka berapa, 4 atau 2? Jawabannya ada di bagian "Dua mutation dalam satu request".

Mutation di schema

Mutation adalah field di type Mutation. Di schema Toko Buku:

type Mutation {
  createBook(input: CreateBookInput!): Book!
  updateStock(id: ID!, stock: Int!): Book
}

input CreateBookInput {
  title: String!
  authorId: ID!
  price: Int!
  stock: Int!
}

Membuat buku: createBook

Ahmad Tohari belum punya buku di toko. Tambahkan Ronggeng Dukuh Paruk:

mutation {
  createBook(input: {
    title: "Ronggeng Dukuh Paruk"
    authorId: 4
    price: 95000
    stock: 3
  }) {
    id
    title
    author {
      name
    }
  }
}
{
  "data": {
    "createBook": {
      "id": "4",
      "title": "Ronggeng Dukuh Paruk",
      "author": { "name": "Ahmad Tohari" }
    }
  }
}

Mengubah stok: updateStock

Bumi Manusia habis (stok 0). Datang kiriman 7 buku:

mutation {
  updateStock(id: 2, stock: 7) {
    title
    stock
    status
  }
}
{ "data": { "updateStock": { "title": "Bumi Manusia", "stock": 7, "status": "IN_STOCK" } } }

status ikut berubah dari SOLD_OUT jadi IN_STOCK, karena resolver-nya menghitung status dari stok. Itu gunanya mutation mengembalikan object yang berubah: client langsung tahu keadaan barunya tanpa query kedua.

Dua mutation dalam satu request

Satu operation mutation boleh berisi beberapa field. Pakai alias supaya key-nya beda:

mutation {
  first: updateStock(id: 3, stock: 4) {
    stock
  }
  second: updateStock(id: 3, stock: 2) {
    stock
  }
}
{ "data": { "first": { "stock": 4 }, "second": { "stock": 2 } } }

Spesifikasi GraphQL mewajibkan field teratas sebuah mutation dijalankan berurutan (serial): first selesai seluruhnya, termasuk selection set-nya, baru second mulai. Jadi stok akhirnya 2. Itu jawaban tebakannya. Field teratas sebuah query boleh dijalankan bersamaan, karena membaca data gak saling mengganggu.

Kalau mutation gagal

Ada dua jenis kegagalan. Validasi menolak operation sebelum apa pun dijalankan, jadi gak ada data yang berubah dan response-nya cuma errors. Resolver bisa menolak waktu dijalankan, dan hasil field-nya jadi null.

Yang dikirim Ditolak oleh Response
updateStock(id: 2, stock: -1) resolver "Stock cannot be negative", data: { "updateStock": null }
createBook dengan authorId: 9 resolver "Author not found", data: null
createBook tanpa stock di input validasi Expected value of type "CreateBookInput" to include required field "stock", found: { title: "Ronggeng Dukuh Paruk", authorId: 4, price: 95000 }.
updateStock(id: 2, stock: "7") validasi Int cannot represent non-integer value: "7"
updateStock(id: 2, stock: 7) tanpa selection set validasi Field "updateStock" of type "Book" must have a selection of subfields. Did you mean "updateStock { ... }"?
createBook di dalam query { ... } validasi Cannot query field "createBook" on type "Query".

Lihat bedanya dua baris pertama. updateStock mengembalikan Book yang boleh null, jadi cuma field itu yang null. createBook mengembalikan Book!, jadi null-nya naik sampai data seluruhnya null. Pesan "Stock cannot be negative" dan "Author not found" ditulis resolver Toko Buku. Pesan validasi datang dari mesin GraphQL (di sini graphql-js 17.0.2).

Penamaan yang dipakai banyak API

⚠️ Jebakan yang sering ketemu

📌 Payload dengan pesan untuk pengguna

Banyak API besar tidak melempar error untuk kesalahan yang wajar dari pengguna, seperti stok negatif. Mutation-nya mengembalikan payload: type khusus yang berisi hasilnya dan daftar masalahnya, misalnya type UpdateStockPayload { book: Book, userErrors: [UserError!]! }. Client cukup membaca userErrors untuk menampilkan pesan di form, dan errors di response disisakan untuk kesalahan yang memang tak terduga. Shopify, misalnya, memakai pola userErrors ini di API GraphQL-nya. Pola ini dibahas lagi di pelajaran "Error Handling di GraphQL".

TL;DR: Mutation mengubah data di server (create, update, delete), seperti POST, PUT, dan DELETE di REST, dan wajib ditulis dengan kata mutation. Field-nya ada di type Mutation, menerima argument (sering satu input bertype input), dan mengembalikan object yang berubah. Kamu memilih field hasilnya sendiri: createBook(...) { id title } cuma mengembalikan id dan title buku baru. Beberapa field teratas di satu mutation dijalankan berurutan, jadi stok 4 lalu 2 berakhir di 2. Validasi menolak sebelum apa pun berubah; resolver yang menolak bikin field-nya null (dan data: null kalau hasilnya Book!). Namai dengan kata kerja + benda (createBook, updateStock), dan jangan pernah kirim mutation lewat GET.

Yang akan kamu pelajari