Pembukaan: Kenapa Streaming dan Validasi Penting

Ketika aplikasi menerima file Excel dari klien, tantangan utamanya adalah menjaga keamanan, menghemat memori, dan memastikan data terproses dengan benar. Go Fiber v3 menawarkan performa tinggi untuk API, tetapi tanpa strategi streaming dan validasi yang tepat, sebuah unggahan besar bisa menghabiskan RAM, memicu DoS, atau memasukkan data kotor ke sistem.

Artikel ini membahas praktik yang dapat langsung Anda terapkan: upload multipart via API, validasi ukuran dan tipe file sebelum pembacaan, membaca konten dengan streaming melalui Excelize, memetakan kolom ke struct, melakukan validasi baris demi baris, mengelola partial success, serta menambahkan ringkasan hasil import. Kita juga menyorot aspek keamanan seperti batas memori atau pencegahan file berbahaya.

1. Desain Endpoint Upload dan Pertimbangan Sinkron vs Asinkron

Gunakan endpoint yang jelas: POST /api/import/excel untuk sinkron (response langsung) atau POST /api/import/excel-job untuk asinkron (mengembalikan ID pekerjaan). Untuk sinkron, batasi ukuran file di middleware Fiber, dan block request jika melebihi batas mantap (misal 10MB). Untuk asinkron, terima file, simpan ke storage sementara (disk, S3), lalu buat job queue yang membaca file dengan worker. Begitu job selesai, kirim notifikasi atau klien dapat memanggil endpoint status.

Contoh desain:

  • Sinkron: Ideal untuk file kecil (< 5MB) atau validasi cepat.
  • Asinkron: Cocok untuk file besar (> 10K baris) agar tidak memblokir worker Fiber.

2. Multipart Upload dan Validasi Awal

Validasi middleware Fiber

Gunakan middleware Fiber untuk membatasi ukuran request body. Dalam app.Use(), panggil func(c *fiber.Ctx) error yang memeriksa header Content-Type dan Content-Length.

app.Use(func(c *fiber.Ctx) error {
    max := int64(10 << 20) // 10MB
    if c.Request().Header.ContentLength() > max {
        return fiber.ErrRequestEntityTooLarge
    }
    if !strings.HasPrefix(string(c.Request().Header.ContentType()), "multipart/form-data") {
        return fiber.ErrBadRequest
    }
    return c.Next()
})

Validasi tipe file

Pada handler, pemeriksaan tambahan dapat dilakukan dengan mengecek ekstensi dari file.Filename dan mime.TypeByExtension. Namun, jangan hanya mengandalkan nama karena bisa dipalsukan. Selalu baca beberapa byte awal (magic number) sebelum memproses lebih lanjut.

Contoh memverifikasi header:

func isExcel(content []byte) bool {
    return bytes.HasPrefix(content, []byte("PK")) // Excel xlsx memakai zip
}

// Setelah mengambil file multipart:
buffer := make([]byte, 512)
file.Read(buffer)
if !isExcel(buffer) {
    return fiber.NewError(fiber.StatusBadRequest, "Tipe file tidak didukung")
}

3. Membaca Excel dengan Streaming Efisien

Pustaka github.com/xuri/excelize/v2 menyediakan API streaming untuk membaca file besar tanpa mengonsumsi memori besar. Gunakan file.NewStreamReader(sheetName) agar baris dibaca satu per satu.

f, err := excelize.OpenReader(file)
if err != nil {
    return fiber.NewError(fiber.StatusBadRequest, "Gagal membuka file Excel")
}
streamReader, err := f.NewStreamReader("Sheet1")
if err != nil {
    return err
}
for {
    row, err := streamReader.Read()
    if err == io.EOF {
        break
    }
    if err != nil {
        return err
    }
    processRow(row)
}

Dengan streaming, hanya baris yang sedang diproses berada di memori. Pastikan Anda menutup file dan reader setelah selesai untuk menghindari kebocoran descriptor.

4. Mapping Kolom ke Struct dan Validasi Per Baris

Struktur data target

Definisikan struct yang mewakili data yang diinginkan. Tambahkan tag validation jika perlu.

type ImportCustomer struct {
    Name    string `validate:"required"`
    Email   string `validate:"required,email"`
    Phone   string `validate:"omitempty,e164"`
    Balance int64  `validate:"min=0"`
}

Mapping otomatis

Jika kolom Excel konsisten (misal A=Name, B=Email), Anda bisa buat helper untuk mengisi struct dari slice:

func mapRowToStruct(row []string) ImportCustomer {
    return ImportCustomer{
        Name:    strings.TrimSpace(row[0]),
        Email:   strings.TrimSpace(row[1]),
        Phone:   strings.TrimSpace(row[2]),
        Balance: parseInt(row[3]),
    }
}

Gunakan package github.com/go-playground/validator/v10 untuk validasi field.

Validasi per baris

Untuk setiap baris, lakukan validasi dan catat statusnya. Dengan pendekatan ini Anda dapat mengizinkan partial success—beberapa baris valid diproses sementara baris lainnya dilaporkan sebagai error.

validate := validator.New()
for idx, row := range rows {
    data := mapRowToStruct(row)
    if err := validate.Struct(data); err != nil {
        result.Errors = append(result.Errors, fmt.Sprintf("Baris %d: %v", idx+1, err))
        continue
    }
    // simpan ke database
    if err := repo.CreateCustomer(data); err != nil {
        result.Errors = append(result.Errors, fmt.Sprintf("Baris %d: gagal simpan %v", idx+1, err))
        continue
    }
    result.Success++
}

5. Partial Success dan Ringkasan Hasil

Selain menghentikan import jika ada error kritis, ciptakan response yang mengomunikasikan hasil secara transparan:

  • Total baris: berapa baris diproses.
  • Berhasil: jumlah sukses.
  • Error: array error per baris.
  • Waktu proses: durasi agar klien tahu berapa lama.

Contoh response sinkron:

{
  "total": 120,
  "success": 115,
  "errors": [
    "Baris 13: email tidak valid",
    "Baris 45: gagal simpan duplicate key"
  ],
  "duration" : "2.3s"
}

Untuk mode asinkron, simpan ringkasan ini di database atau cache dan sediakan endpoint status berdasarkan job ID.

6. Keamanan File Upload dan Batas Memori

Perlindungan terhadap file berbahaya

Jangan langsung menyimpan file ke path yang bisa dieksekusi. Simpan di folder khusus dengan permission terbatas, lalu hapus setelah selesai. Validasi file dengan content sniffing dan jangan jalankan file tersebut.

Batas memori

Fiber menggunakan goroutine per request. Batasi goroutine terbuka dan gunakan streaming agar tidak memori intensive. Jika menangani file besar, simpan sementara ke disk kemudian proses dengan worker, atau gunakan io.LimitReader untuk menghindari pembacaan tanpa batas.

Debugging

Tambahkan logging contextual: nama file, ukuran, user ID. Jika error muncul saat parsing Excel, bantu user dengan memberikan baris dan kolom yang bermasalah. Gunakan context timeout untuk menghindari proses import menggantung.

Kesimpulan

Membangun API import Excel yang aman di Go Fiber v3 membutuhkan perpaduan validasi awal, pembacaan streaming, pemetaan dan validasi per baris, serta dukungan partial success agar pengguna dapat memperbaiki data yang bermasalah tanpa kehilangan hasil lain.

Dengan desain endpoint yang memisahkan mode sinkron dan asinkron, serta mencatat ringkasan hasil import, tim Anda dapat mengelola data batch secara andal. Tetap utamakan keamanan file upload, batasi memori, dan buat respons yang membantu klien menghadapi kegagalan parsial.