Skip to content

Export XLSX dari Go dan Tebakan Binary yang Meleset

Adityo Guni Waluyo

openapi-fetch ternyata punya parseAs blob. Yang bikin gesek itu typing format: binary dan alur unduhan DOM.

Ringkasan

Awalnya dikira openapi-fetch nggak bisa handle file binary jadi pakai fetch manual aja. Ternyata bisa return blob, cuma typing-nya jadi string jadi tetep ribet plus urusan download-nya emang kerjaan DOM. Di backend export-nya pake excelize, filter modul ketat dan kalau data kosong tetep ngasih file dengan header biar nggak error.

Rapat butuh data yang bisa dibawa pulang. Stakeholder di sebuah project sistem informasi dinas minta file Excel yang kebuka offline, bukan link dashboard yang nunggu koneksi. Alurnya emang gitu: angka di layar buat pantau, file buat dilipat.

Tebakan pertama saya soal bagian yang paling "nggak seru": respons binary. Saya asumsikan openapi-fetch nggak dukung respons binary, jadi langsung gas nulis unduhan pakai fetch manual.

Pas iseng buka source paket versi 0.17 yang terpasang, tebakan itu jebol. Ada opsi respons di-parse sebagai blob. Tipe tubuh responsnya jelas nyebut blob dan arrayBuffer di samping json dan text [11]. Client-nya bisa. Yang bikin gesek itu dua hal lain.

Pertama, typing. Skema OpenAPI dengan format binary dipetakan jadi string di tipe respons yang di-generate. Jadi di mata kompiler, endpoint itu "ngembaliin string", dan nge-as ke Blob rasanya kayak bohongin dia.

Kedua, setelah dapet Blob-nya, urusan berikutnya memang bukan tugas client HTTP: bikin object URL, tempel ke , picu klik, bersihin setelahnya. URL.createObjectURL() itu yang bikin string blob URL ke objek [10], dan harus dibalas revokeObjectURL() biar nggak nyidam memori [10]. Bagian ini memang ditulis sendiri, tinggal menempel di mana.

Jadi keputusan akhirnya tetap fetch manual, tapi alasan yang saya catat di commit terlalu kasar. Versi yang benar: dukungan binary ada, tapi typing generated-nya nyaru jadi string dan sisa alurnya kerjaan DOM. Reputasi tebakan saya: nol. Untung dicek sebelum ditulis ke dokumentasi.

Sisi backend: file kosong tetap file

Di Go saya pakai excelize/v2, library pure Go buat nulis dan baca file XLSX [9]. Dua keputusan yang menurut saya bikin export "rasa admin" ini beda dari tutorial Excel biasa:

Satu, ?module= itu filter enum, bukan free text. Enam nilai yang sah, sisanya 400. Lebih baik user kena error jelas daripada nerima file yang isinya nggak sesuai ekspektasi.

Dua, hasil query kosong nggak boleh jadi 404 atau 500. Filenya tetap dibikin, minimal berisi header di dua sheet, Entities dan Announcements. Pengguna yang nge-export di awal bulan (data masih tipis) tetap dapet file yang kebuka normal. Empat integration test nge-lock perilaku ini: filtered, unfiltered, unknown module, sama empty-database yang harus tetap valid.

if module != "" && !validModules[module] {
    return nil, fmt.Errorf("%w: invalid module", ErrValidation)
}

f := excelize.NewFile()
defer f.Close()
// excelize menamai sheet pertama "Sheet1" - pakai sebagai sheet entitas
_ = f.SetSheetName("Sheet1", "Entities")

Detail yang gampang kelewat: migration 000051 udah drop announcements.category_id, jadi export Announcements nyambungin modul ke announcement_type, bukan ke relasi lama. Export yang nyimpen relasi basi bakal jebol pas data baru masuk lewat jalur yang beda.

Sisi frontend: pola unduhan yang panjangnya tetap

const res = await fetch(`${API_BASE_URL}/admin/export/xlsx`, {
  headers: authHeaders(),
});
if (!res.ok) {
  const body = await res.json().catch(() => null);
  throw new AdminApiError(res.status, body?.error ?? "Export failed");
}
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `report-export-${new Date().toISOString().slice(0, 10)}.xlsx`;
a.click();
URL.revokeObjectURL(url);

Konsekuensi kecilnya: client.ts sekarang nge-ekspor API_BASE_URL, supaya konstanta itu bisa dipakai di luar wrapper openapi-fetch. Satu pintu buat JSON, satu pintu kecil buat binary. Nggak estetis, tapi jujur ke kompiler dan ke yang baca.

Kalau besok ada endpoint binary lagi, pertimbangan saya bukan "support nggak", tapi: typing-nya jelas nggak, dan alur unduhannya butuh DOM nggak. Jawaban dua-duanya "iya" berarti fetch manual emang pilihan yang berdiri sendiri, bukan jalan pintas karena nggak baca source.

Sources

[9] https://github.com/xuri/excelize/blob/master/README.md

[10] https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static

[11] https://github.com/openapi-ts/openapi-typescript/blob/openapi-fetch%400.17.0/packages/openapi-fetch/src/index.d.ts

Artikel terkait