Blog

  • Aplikasi Kasir dan Stok Toko Bangunan: Apa yang Wajib Ada

    Aplikasi Kasir dan Stok Toko Bangunan: Apa yang Wajib Ada

    Aplikasi kasir untuk toko bangunan itu beda kebutuhannya dengan kasir minimarket atau kafe. Minimal harus bisa tiga hal: satuan campur dengan konversi otomatis, harga bertingkat untuk ecer, langganan, dan grosir, serta pencatatan piutang untuk pelanggan yang ambil barang pakai bon. Tiga hal ini hampir tidak pernah ada di aplikasi kasir umum. Dan tiga hal ini pula yang paling sering jadi penyebab stok di kertas tidak pernah cocok dengan stok di gudang.

    Kalau Anda pemilik toko bangunan atau toko material yang tiap kali cek stok hasilnya selalu selisih, kemungkinan besar masalahnya bukan di karyawan. Masalahnya di sistem pencatatan yang memang tidak dirancang untuk cara kerja toko bangunan. Mari kita bedah satu per satu.

    Satuan Campur dan Konversi: Sumber Selisih Stok Paling Besar

    Coba lihat barang di toko Anda. Semen dijual per sak. Besi beton kadang dihitung per batang, kadang per kg kalau pembelinya proyek. Cat ada kaleng 1 kg, galon 5 kg, dan pail 25 kg, padahal isinya produk yang sama. Pipa PVC dijual per batang 4 meter, tapi sesekali ada tukang yang cuma butuh 2 meter. Paku masuk gudang per dus, keluarnya per kg, bahkan per ons.

    Aplikasi kasir umum menganggap satu barang punya satu satuan. Akibatnya kasir terpaksa akal-akalan. Paku 1 kg dibuat jadi produk sendiri, paku setengah kg jadi produk lain lagi. Stoknya jalan sendiri-sendiri, padahal fisiknya dari dus yang sama. Dalam sebulan saja catatan sudah kacau.

    Sistem yang benar memakai konsep satuan dasar dan satuan turunan. Paku disimpan dalam satuan dasar kg. Satu dus sama dengan 25 kg. Mau jual per dus, per kg, atau per ons, semua transaksi otomatis mengurangi stok dalam kg. Begitu juga besi: satu batang ukuran tertentu punya berat standar, jadi penjualan per kg tetap bisa ditelusuri ke jumlah batang. Stok satu, satuan boleh banyak. Ini fondasi paling penting sebelum bicara fitur lain.

    Satu Barang, Tiga Harga: Ecer, Langganan, Grosir

    Di toko bangunan, harga semen untuk orang yang beli dua sak jelas beda dengan harga untuk kontraktor yang ambil dua ratus sak. Di tengahnya ada tukang dan pemborong langganan yang dapat harga khusus karena sudah bertahun-tahun belanja di tempat Anda.

    Kasir umum cuma mengenal satu harga jual per barang. Ujung-ujungnya harga langganan disimpan di kepala pemilik atau di buku tulis dekat kasir. Saat pemilik tidak di tempat, karyawan bingung, atau lebih parah, memberi harga grosir ke pembeli ecer.

    Sistem untuk toko bangunan seharusnya bisa mengatur beberapa level harga sekaligus. Harga ecer untuk pembeli umum, harga langganan untuk tukang dan kontraktor yang sudah terdaftar, dan harga grosir yang aktif otomatis kalau kuantitas mencapai batas tertentu. Pelanggan tinggal dipilih di kasir, harga menyesuaikan sendiri.

    Satu lagi yang khas toko bangunan: nego. Menghilangkan nego itu mustahil, jadi sistem yang realistis justru mewadahinya. Caranya dengan batas harga minimum per barang. Kasir boleh menurunkan harga sampai batas itu, di bawahnya harus lewat persetujuan pemilik. Anda tetap fleksibel di depan pelanggan, tapi tidak ada lagi transaksi yang diam-diam jual rugi.

    Bon Tukang dan Kontraktor: Piutang Harus Tercatat Rapi

    Penjualan tempo itu bagian dari hidup toko bangunan. Tukang ambil material dulu, bayar setelah terima pembayaran dari pemilik rumah. Kontraktor ambil barang untuk proyek, dibayar per termin. Menolak bon sama saja menolak sebagian besar omzet.

    Masalahnya bukan di bonnya, tapi di pencatatannya. Bon yang ditulis di kertas gampang hilang, gampang lupa, dan tidak ada yang mengingatkan kapan jatuh temponya. Banyak toko baru sadar piutangnya sudah puluhan juta setelah dihitung ulang, dan sebagian sudah tidak jelas siapa yang berutang.

    Di sistem yang layak, setiap penjualan tempo langsung tercatat atas nama pelanggan, lengkap dengan tanggal, rincian barang, dan jatuh tempo. Pembayaran sebagian juga tercatat, jadi sisa tagihan selalu jelas. Anda bisa buka daftar piutang kapan saja: siapa saja yang punya tagihan, berapa totalnya, mana yang sudah lewat jatuh tempo. Pencatatan seperti ini juga bikin laporan keuangan toko jauh lebih masuk akal, karena omzet di buku dan uang di laci memang tidak akan pernah sama kalau ada piutang. Soal ini pernah kami bahas lebih dalam di artikel laporan keuangan sederhana untuk usaha kecil.

    Ribuan Barang: Cari Cepat, Opname per Rak, Titipan Supplier

    Toko bangunan ukuran menengah gampang sekali punya dua sampai lima ribu jenis barang. Cat saja bisa ratusan varian kalau dihitung per merek, per warna, per ukuran kemasan. Belum keramik, pipa, sambungan, kunci, engsel, sampai amplas.

    Pencarian yang tidak bikin antrean panjang

    Dengan ribuan barang, kecepatan cari barang di kasir itu penentu. Sistem harus bisa dicari lewat nama, merek, ukuran, atau kode, dan hasilnya keluar dalam hitungan detik. Barang sejenis beda merek dan ukuran harus mudah dibedakan di layar, supaya kasir tidak salah pilih antara pipa setengah inci merek A dan merek B yang harganya beda jauh. Barcode membantu untuk barang bermerek, tapi banyak material seperti pasir, besi, atau paku kiloan tidak punya barcode, jadi pencarian teks yang cepat tetap wajib.

    Stok opname per rak, bukan tutup toko

    Menghitung ulang seluruh isi toko dalam sehari itu praktis mustahil, kecuali Anda rela tutup beberapa hari. Cara yang lebih waras adalah opname siklus: minggu ini hitung rak cat, minggu depan rak pipa, minggu berikutnya keramik. Sistem yang baik mendukung pola ini. Anda pilih kategori atau lokasi rak, sistem tampilkan stok tercatat, tim menghitung fisik, selisihnya langsung kelihatan dan bisa disesuaikan hari itu juga. Dalam dua sampai tiga bulan seluruh toko sudah terhitung tanpa pernah tutup.

    Barang titipan dan kiriman ke proyek

    Dua hal lagi yang perlu disebut. Pertama, barang konsinyasi alias titipan supplier, misalnya sales cat yang menitipkan produk dan baru menagih sesuai yang laku. Barang ini harus dipisah statusnya dari stok milik sendiri supaya nilai persediaan Anda tidak menggelembung. Kedua, pengiriman ke lokasi proyek. Penjualan besar ke kontraktor hampir selalu diantar, jadi sistem perlu bisa mencetak surat jalan dari transaksi dan mencatat ongkos kirim, supaya barang yang keluar dari gudang selalu ada jejak dokumennya.

    Laporan yang Benar-benar Berguna buat Pemilik

    Kasir dan stok yang rapi itu baru setengah manfaat. Setengahnya lagi ada di laporan. Bukan laporan tebal penuh angka, tapi beberapa yang langsung bisa jadi dasar keputusan:

    • Barang mati. Daftar barang yang tidak laku tiga atau enam bulan terakhir. Ini uang yang tidur di rak. Dari laporan ini Anda bisa memutuskan mana yang perlu diobral dan mana yang berhenti dipesan.
    • Margin per kategori. Omzet besar belum tentu untung besar. Bisa jadi semen menyumbang omzet terbesar tapi marginnya tipis, sementara alat dan aksesori marginnya jauh lebih sehat. Laporan ini menunjukkan kategori mana yang layak diperluas.
    • Piutang jatuh tempo. Daftar tagihan yang sudah dan akan jatuh tempo, urut dari yang paling lama. Menagih jadi pekerjaan rutin yang terjadwal, bukan kegiatan dadakan saat kas menipis.

    Tiga laporan ini saja, kalau dibuka rutin tiap minggu, biasanya sudah mengubah cara pemilik mengambil keputusan belanja dan menagih.

    Kapan Belum Butuh, dan Pilih Aplikasi Jadi atau Custom

    Jujur saja, tidak semua toko bangunan butuh sistem sekarang. Kalau barang Anda masih di bawah dua ratus jenis, hampir semua penjualan tunai, dan Anda sendiri yang jaga toko setiap hari, buku tulis yang disiplin ditambah catatan di ponsel masih bisa jalan. Sistem baru terasa nilainya saat ada karyawan yang ikut pegang kasir, saat bon langganan mulai menumpuk, atau saat Anda mulai sering kecolongan stok yang hilang entah ke mana.

    Kalau sudah sampai di titik itu, ada dua jalur. Aplikasi kasir jadi yang berlangganan bulanan cocok untuk mulai cepat dan murah, tapi kebanyakan dibuat generik untuk semua jenis toko. Fitur konversi satuan, batas harga nego, dan piutang bertingkat sering tidak ada atau setengah jadi. Jalur kedua adalah sistem custom yang dibangun mengikuti alur toko Anda: satuan mengikuti barang Anda, level harga mengikuti tipe pelanggan Anda, laporan mengikuti pertanyaan yang ingin Anda jawab tiap minggu. Biayanya lebih besar di awal, tapi Anda tidak perlu mengubah cara kerja toko demi menyesuaikan diri dengan aplikasi. Gambaran umum soal kapan bisnis layak membangun sistem aplikasi custom pernah kami tulis terpisah.

    Kami di Arrazy Inovasi Teknologi terbiasa membangun sistem kasir dan stok untuk kebutuhan operasional yang tidak bisa dilayani aplikasi generik. Kalau Anda ingin cerita dulu soal kondisi toko Anda, satuan barangnya seperti apa, bon langganannya bagaimana, silakan hubungi kami lewat halaman kontak. Diskusi awal tidak dipungut biaya, dan kalau ternyata kebutuhan Anda cukup dijawab aplikasi jadi, kami akan bilang begitu.

    Pertanyaan yang Sering Diajukan

    Apakah semua barang harus diberi barcode dulu sebelum pakai sistem?

    Tidak. Barang bermerek yang sudah ada barcode dari pabrik tinggal dipindai. Barang curah seperti paku kiloan, pasir, atau besi cukup diberi kode internal dan dicari lewat nama. Yang penting pencarian di kasir cepat, bukan semua barang berlabel.

    Berapa lama memasukkan ribuan data barang ke sistem baru?

    Ini bagian paling berat dan wajar kalau bertahap. Cara yang umum: masukkan dulu barang yang paling sering terjual, biasanya dua puluh persen barang yang menyumbang sebagian besar transaksi, lalu sisanya menyusul sambil jalan. Data awal bisa diimpor dari Excel supaya tidak diketik satu per satu. Praktiknya toko bisa mulai bertransaksi di sistem dalam satu sampai dua minggu.

    Kalau listrik atau internet mati, kasir ikut berhenti?

    Tergantung arsitektur sistemnya. Untuk toko bangunan kami sarankan sistem yang bisa tetap mencatat transaksi secara lokal saat internet putus, lalu sinkron otomatis begitu koneksi kembali. Soal listrik, komputer kasir dengan UPS sederhana biasanya cukup untuk bertahan sampai genset atau listrik menyala lagi.

  • Cara Menghitung SHU Koperasi: Contoh Angka dan Kesalahan Umum

    Cara Menghitung SHU Koperasi: Contoh Angka dan Kesalahan Umum

    Prinsip dasar pembagian SHU koperasi sebenarnya cuma satu kalimat: SHU dibagi ke anggota sesuai jasa masing-masing, bukan dibagi rata. Jasa itu ada dua bentuk. Pertama jasa modal, seberapa besar simpanan anggota di koperasi. Kedua jasa usaha, seberapa besar transaksi anggota dengan koperasi sepanjang tahun. Anggota yang simpanannya besar dan rajin bertransaksi akan menerima SHU lebih besar daripada anggota yang pasif. Itu adil, dan memang begitu aturannya.

    Rumus proporsionalnya bisa diringkas begini: SHU anggota = (simpanan anggota dibagi total simpanan seluruh anggota, dikali alokasi jasa modal) ditambah (transaksi anggota dibagi total transaksi seluruh anggota, dikali alokasi jasa usaha). Kalau kalimat itu masih terasa abstrak, tenang. Di bawah kita hitung pelan-pelan pakai contoh angka bulat yang gampang diikuti, lengkap dengan satu anggota bernama Bu Sari. Kita juga bahas kesalahan yang paling sering muncul menjelang RAT.

    SHU Itu Apa, dalam Bahasa Sederhana

    SHU adalah singkatan dari Sisa Hasil Usaha. Sederhananya, ini adalah pendapatan koperasi selama satu tahun buku dikurangi semua biaya, penyusutan, dan kewajiban lain termasuk pajak. Sisanya itulah yang disebut SHU. Kalau di perusahaan biasa istilahnya laba bersih, di koperasi namanya SHU.

    Bedanya dengan perusahaan ada di cara membaginya. Di perseroan, laba dibagi ke pemegang saham sesuai porsi saham. Di koperasi, SHU dibagi ke anggota sesuai jasa anggota terhadap koperasi. Ini bukan sekadar kebiasaan. UU Nomor 25 Tahun 1992 tentang Perkoperasian menyebut pembagian SHU dilakukan secara adil sebanding dengan besarnya jasa usaha masing-masing anggota. Jadi anggota bukan cuma penerima, tapi juga penentu besar kecilnya SHU yang dia terima lewat aktivitasnya sendiri.

    Satu hal penting sebelum lanjut: tidak semua SHU dibagikan ke anggota. Sebagian wajib disisihkan dulu untuk keperluan koperasi itu sendiri. Ini yang kita bahas berikutnya.

    Komponen Pembagian SHU Sesuai AD/ART

    Sebelum sampai ke kantong anggota, SHU dipotong dulu untuk beberapa pos. Pembagiannya diatur di AD/ART masing-masing koperasi, jadi persentase di bawah ini murni contoh yang umum dipakai. Koperasi Anda bisa berbeda, dan itu sah selama sudah disepakati di AD/ART dan diputuskan di rapat anggota.

    • Dana cadangan, misalnya 25 persen. Ini tabungan koperasi untuk memperkuat modal dan menutup kerugian kalau suatu saat terjadi. Cadangan tidak dibagikan ke anggota.
    • Jasa anggota, misalnya 40 persen. Inilah porsi yang benar-benar dibagikan ke anggota, dan nanti dipecah lagi jadi jasa modal dan jasa usaha.
    • Dana pengurus dan pengawas, misalnya 10 persen. Bentuk penghargaan atas kerja pengurus selama setahun.
    • Dana karyawan, misalnya 5 persen, untuk karyawan yang membantu operasional harian.
    • Dana pendidikan, misalnya 10 persen, untuk pelatihan anggota dan pengurus.
    • Dana sosial, misalnya 10 persen, untuk kegiatan sosial di lingkungan koperasi.

    Kalau dijumlah, contoh di atas pas 100 persen. Angka-angka ini yang akan kita pakai di contoh perhitungan nanti. Sekali lagi, cek AD/ART koperasi Anda sendiri sebelum menghitung, karena persentase tiap koperasi berbeda.

    Dua Jenis Jasa Anggota: Jasa Modal dan Jasa Usaha

    Porsi jasa anggota tadi masih harus dipecah dua. Ini bagian yang paling sering bikin bingung, padahal logikanya lurus saja.

    Jasa modal atau jasa simpanan

    Dibagi proporsional terhadap simpanan tiap anggota. Yang dihitung biasanya simpanan pokok, simpanan wajib, dan kadang simpanan sukarela, tergantung ketentuan koperasi. Anggota yang simpanannya 2 persen dari total simpanan seluruh anggota akan menerima 2 persen dari alokasi jasa modal. Sesederhana itu.

    Jasa usaha atau jasa transaksi

    Dibagi proporsional terhadap transaksi tiap anggota dengan koperasi. Bentuk transaksinya tergantung jenis koperasinya. Di koperasi simpan pinjam, yang dihitung biasanya bunga atau jasa pinjaman yang dibayar anggota selama setahun. Di koperasi konsumen, yang dihitung total belanja anggota di toko koperasi. Intinya sama: makin besar kontribusi anggota terhadap pendapatan koperasi, makin besar bagiannya dari alokasi jasa usaha.

    Perbandingan antara jasa modal dan jasa usaha juga diatur koperasi masing-masing. Ada yang membagi 50 banding 50, ada yang memberi porsi lebih besar ke jasa usaha supaya anggota terdorong aktif bertransaksi. Di contoh berikut kita pakai 50 banding 50 biar hitungannya enak diikuti.

    Contoh Perhitungan Lengkap: SHU Rp100 Juta

    Anggap sebuah koperasi simpan pinjam menutup tahun buku dengan SHU Rp100.000.000. Total simpanan seluruh anggota Rp500.000.000. Total pendapatan bunga pinjaman dari seluruh anggota selama setahun Rp200.000.000.

    Langkah 1: bagi SHU sesuai alokasi AD/ART

    Pakai persentase contoh di atas, SHU Rp100 juta dipecah jadi:

    • Dana cadangan 25 persen = Rp25.000.000
    • Jasa anggota 40 persen = Rp40.000.000
    • Dana pengurus dan pengawas 10 persen = Rp10.000.000
    • Dana karyawan 5 persen = Rp5.000.000
    • Dana pendidikan 10 persen = Rp10.000.000
    • Dana sosial 10 persen = Rp10.000.000

    Total tetap Rp100.000.000. Yang mengalir ke anggota hanya pos jasa anggota, yaitu Rp40.000.000.

    Langkah 2: pecah jasa anggota jadi dua

    Dengan perbandingan 50 banding 50, alokasi Rp40 juta terbagi jadi:

    • Jasa modal: Rp20.000.000, dibagi proporsional simpanan
    • Jasa usaha: Rp20.000.000, dibagi proporsional bunga pinjaman yang dibayar anggota

    Langkah 3: hitung SHU Bu Sari

    Bu Sari punya total simpanan Rp10.000.000 di koperasi. Sepanjang tahun dia juga meminjam, dan total bunga pinjaman yang dia bayar Rp6.000.000.

    Hitung jasa modalnya dulu. Proporsi simpanan Bu Sari = Rp10.000.000 dibagi Rp500.000.000 = 2 persen. Jasa modal Bu Sari = 2 persen dikali Rp20.000.000 = Rp400.000.

    Lalu jasa usahanya. Proporsi transaksi Bu Sari = Rp6.000.000 dibagi Rp200.000.000 = 3 persen. Jasa usaha Bu Sari = 3 persen dikali Rp20.000.000 = Rp600.000.

    SHU yang diterima Bu Sari = Rp400.000 + Rp600.000 = Rp1.000.000.

    Perhatikan satu hal menarik. Simpanan Bu Sari cuma 2 persen dari total, tapi karena dia aktif meminjam, porsi jasa usahanya 3 persen. Hasil akhirnya lebih besar daripada kalau dia cuma menyimpan tanpa bertransaksi. Di sinilah prinsip koperasi bekerja: yang aktif memutar roda usaha koperasi memang layak dapat lebih.

    Rumus yang sama tinggal diulang untuk semua anggota. Kalau anggotanya 50 orang, ya 50 kali hitung. Kalau 2.000 orang, ya 2.000 kali. Angka pembaginya selalu sama, yang berubah hanya simpanan dan transaksi tiap anggota.

    Kesalahan yang Sering Terjadi Saat Membagi SHU

    Perhitungannya kelihatan gampang di atas kertas. Praktiknya, ada beberapa kesalahan yang muncul berulang di banyak koperasi.

    Dibagi rata ke semua anggota. Ini kesalahan paling mendasar. Alasannya biasanya biar cepat atau biar tidak ada yang protes. Padahal bagi rata justru melanggar prinsip koperasi dan tidak adil buat anggota yang aktif. Anggota yang rajin menabung dan bertransaksi menerima sama dengan anggota yang setahun tidak pernah muncul. Lama-lama anggota aktif kehilangan alasan untuk tetap aktif.

    Lupa menyisihkan cadangan. Ada koperasi yang langsung membagi habis SHU ke anggota supaya angka yang diterima terasa besar. Akibatnya koperasi tidak punya bantalan modal. Sekali ada pinjaman macet besar, koperasi goyah karena tidak ada cadangan yang bisa menutup.

    Data transaksi anggota tidak lengkap. Ini penyakit yang paling umum. Jasa modal biasanya masih bisa dihitung karena data simpanan tercatat rapi. Tapi jasa usaha butuh rekap transaksi per anggota selama setahun penuh. Kalau catatannya tercecer di buku tulis, bon, dan file Excel yang beda-beda versi, angka jasa usaha akhirnya ditebak-tebak atau dipukul rata. Hasilnya SHU yang dibagikan tidak mencerminkan kontribusi anggota yang sebenarnya, dan anggota yang teliti bisa menghitung sendiri bahwa angkanya janggal.

    Pembagian molor berbulan-bulan. RAT sudah lewat, keputusan pembagian sudah diketok, tapi uangnya belum juga cair karena bendahara masih menyisir rekap manual satu per satu. Makin banyak anggota, makin lama prosesnya. Ada koperasi yang baru selesai membagi SHU tahun lalu ketika tahun buku berikutnya sudah hampir tutup.

    Pencatatan Sepanjang Tahun Menentukan RAT yang Lancar

    Kalau diperhatikan, tiga dari empat kesalahan di atas akarnya sama: pencatatan. Perhitungan SHU itu sebenarnya cuma pembagian proporsional biasa. Yang bikin berat bukan rumusnya, tapi menyiapkan bahan bakunya, yaitu data simpanan dan transaksi tiap anggota selama dua belas bulan.

    Koperasi yang mencatat rapi sepanjang tahun praktis tinggal menekan tombol rekap menjelang RAT. Saldo simpanan per anggota sudah ada. Total bunga yang dibayar tiap peminjam sudah terakumulasi. Perhitungan SHU per anggota selesai dalam hitungan hari, bukan bulan, dan setiap angka bisa ditelusuri asalnya kalau ada anggota yang bertanya. Transparansi seperti ini yang bikin RAT adem dan kepercayaan anggota terjaga.

    Sebaliknya, koperasi yang menunda pencatatan akan membayar utangnya menjelang RAT. Rekap setahun dikerjakan dalam beberapa minggu, di bawah tekanan, dengan data yang bolong di sana-sini. Di titik itulah godaan bagi rata atau tebak angka muncul.

    Maka kalau mau menarik satu pelajaran dari artikel ini, bukan rumusnya yang perlu dihafal. Rumusnya sudah ada di atas dan tidak akan berubah. Yang perlu dibenahi adalah kebiasaan mencatat transaksi anggota secara konsisten sejak awal tahun buku, bukan menjelang RAT.

    Untuk koperasi yang anggotanya sudah ratusan, rekap manual memang berat sekalipun sudah disiplin. Di titik ini banyak pengurus beralih ke sistem yang merekam simpanan dan transaksi otomatis, sehingga bahan perhitungan SHU selalu siap kapan pun dibutuhkan. Salah satu contohnya bisa dilihat di implementasi sistem koperasi Dwija Usaha yang kami kerjakan. Kalau ingin tahu fitur apa saja yang biasanya dipakai pengurus untuk urusan ini, kami sudah mengulasnya di artikel aplikasi simpan pinjam koperasi.

  • Belajar Golang dari Nol #13: Middleware dan Auth Sederhana

    Belajar Golang dari Nol #13: Middleware dan Auth Sederhana

    Di Belajar Golang dari Nol #12 kita sudah menyambungkan API produk ke database. Data sekarang tersimpan beneran, bukan hilang tiap server restart. Tapi ada satu masalah besar yang belum kita sentuh. API kita masih terbuka lebar. Siapa pun yang tahu alamatnya bisa POST produk baru, mengubah harga, bahkan menghapus data. Di bagian ini kita tutup lubang itu pakai dua konsep: middleware dan autentikasi sederhana.

    Masalahnya: API kita masih pintu tanpa kunci

    Coba bayangkan API produk dari bagian 12 sudah kamu deploy ke server. Alamatnya ketahuan orang. Apa yang terjadi?

    • Orang iseng bisa kirim POST /produk dan mengisi database kamu dengan data sampah.
    • Kompetitor bisa hapus semua produk lewat DELETE.
    • Kamu tidak tahu siapa yang mengakses, kapan, dan endpoint mana yang paling sering dipanggil.

    Jadi ada dua kebutuhan yang berbeda tapi berkaitan. Pertama, kita perlu tahu siapa pemanggil API dan menolak yang tidak berhak. Itu namanya autentikasi. Kedua, pengecekan ini harus jalan di banyak endpoint sekaligus. Kalau kita tulis kode cek di setiap handler satu per satu, itu copy paste yang bakal jadi mimpi buruk saat endpoint bertambah. Solusi untuk masalah kedua ini namanya middleware.

    Apa itu middleware

    Middleware adalah function yang membungkus handler. Dia berdiri di antara request masuk dan handler yang memprosesnya.

    Analoginya begini. Bayangkan gedung kantor dengan banyak ruangan. Setiap ruangan adalah handler: ruang produk, ruang laporan, ruang admin. Middleware adalah satpam di lobi. Semua tamu harus lewat dia dulu, mau ke ruangan mana pun. Satpam bisa mencatat tamu di buku (logging), memeriksa kartu identitas (auth), atau menolak tamu yang mencurigakan. Ruangan tidak perlu tahu urusan pemeriksaan itu. Mereka cukup fokus pada pekerjaannya.

    Bentuk middleware di Go

    Di Go, middleware punya bentuk yang khas:

    func namaMiddleware(next http.Handler) http.Handler

    Baca pelan. Function ini menerima sebuah http.Handler dan mengembalikan http.Handler juga. Masih ingat dua materi lama kita? http.Handler adalah interface, yaitu apa pun yang punya method ServeHTTP. Dan function di Go adalah nilai yang bisa dioper dan dikembalikan seperti angka atau string. Middleware menggabungkan keduanya. Dia menerima handler asli lewat parameter next, lalu mengembalikan handler baru yang menjalankan logika tambahan sebelum atau sesudah memanggil next.

    Karena input dan output sama-sama http.Handler, hasil bungkusan bisa dibungkus lagi. Ini yang bikin middleware bisa dirangkai.

    Middleware pertama: logging

    Kita mulai dari yang paling tidak berbahaya: mencatat setiap request. Method apa, path apa, dan berapa lama diproses.

    func logging(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            mulai := time.Now()
            next.ServeHTTP(w, r)
            log.Printf("%s %s selesai dalam %v", r.Method, r.URL.Path, time.Since(mulai))
        })
    }

    Perhatikan alurnya. Kita catat waktu mulai, panggil handler asli lewat next.ServeHTTP(w, r), lalu hitung durasinya pakai time.Since. Kode sebelum next.ServeHTTP jalan sebelum handler. Kode sesudahnya jalan setelah handler selesai.

    http.HandlerFunc di sini adalah adapter yang sudah kita kenal sejak bagian 11. Dia mengubah function biasa menjadi sesuatu yang memenuhi interface http.Handler.

    Cara pasangnya: bungkus mux sebelum diserahkan ke server.

    mux := http.NewServeMux()
    mux.HandleFunc("GET /produk", listProduk)
    
    log.Fatal(http.ListenAndServe(":8080", logging(mux)))

    Karena yang dibungkus adalah mux, semua endpoint otomatis tercatat. Satu function, berlaku di mana-mana. Tidak ada copy paste.

    Middleware kedua: API key

    Sekarang bagian auth. Cara paling sederhana adalah API key: sebuah string rahasia yang harus dikirim client lewat header. Kalau key cocok, request lolos. Kalau tidak, tolak dengan status 401 Unauthorized.

    func cekAPIKey(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            kunci := os.Getenv("API_KEY")
            if kunci == "" || r.Header.Get("X-API-Key") != kunci {
                w.Header().Set("Content-Type", "application/json")
                w.WriteHeader(http.StatusUnauthorized)
                json.NewEncoder(w).Encode(map[string]string{"error": "API key salah atau tidak ada"})
                return
            }
            next.ServeHTTP(w, r)
        })
    }

    Ada beberapa hal penting di sini.

    Pertama, key aslinya diambil dari environment variable lewat os.Getenv, bukan ditulis langsung di kode. Kenapa? Karena kode biasanya masuk Git. Kalau key kamu hardcode, siapa pun yang bisa baca repo tahu rahasianya. Dengan environment variable, key hidup di server, terpisah dari kode. Ganti key pun tidak perlu compile ulang.

    Kedua, kita cek kunci == "" dulu. Kalau server lupa diset API_KEY, semua request ditolak. Ini lebih aman daripada sebaliknya, yaitu semua request lolos karena membandingkan string kosong dengan string kosong.

    Ketiga, jangan lupa return setelah menulis respons 401. Tanpa itu, eksekusi lanjut ke next.ServeHTTP dan handler tetap jalan. Ini bug klasik middleware.

    Satu catatan jujur. API key statis seperti ini cocok untuk API internal atau komunikasi antar sistem, misalnya server kamu dipanggil oleh server kantor lain yang sudah kamu kenal. Untuk login user publik dengan ribuan akun, ini tidak cukup. Kita bahas alasannya di bawah.

    Merangkai beberapa middleware

    Karena middleware menerima handler dan mengembalikan handler, kita bisa menumpuknya:

    handler := logging(cekAPIKey(mux))
    log.Fatal(http.ListenAndServe(":8080", handler))

    Urutan bacanya dari luar ke dalam. Request masuk ke logging dulu, lalu cekAPIKey, baru sampai ke mux dan handler. Respons berjalan sebaliknya, dari dalam ke luar.

    Cara paling gampang membuktikan urutan adalah menambah log sementara di tiap middleware. Kalau kamu tambahkan log.Println("masuk logging") di awal logging dan log.Println("masuk cekAPIKey") di awal cekAPIKey, outputnya seperti ini:

    2026/07/26 10:15:02 masuk logging
    2026/07/26 10:15:02 masuk cekAPIKey
    2026/07/26 10:15:02 POST /produk selesai dalam 3.1ms

    Terlihat jelas. Middleware terluar jalan duluan. Baris durasi muncul terakhir karena dia ditulis setelah next.ServeHTTP selesai. Urutan ini penting saat middleware saling bergantung. Logging biasanya paling luar supaya request yang ditolak auth pun tetap tercatat.

    Middleware selektif: tidak semua endpoint butuh kunci

    Ada masalah baru. Kalau cekAPIKey membungkus seluruh mux, endpoint GET /produk ikut terkunci. Padahal daftar produk biasanya memang untuk publik. Yang perlu dijaga hanya operasi tulis: POST, PUT, DELETE.

    Solusinya, pasang middleware per route, bukan di mux. mux.Handle menerima http.Handler, jadi kita bisa membungkus handler tertentu saja:

    mux := http.NewServeMux()
    
    // publik, tanpa kunci
    mux.HandleFunc("GET /produk", listProduk)
    mux.HandleFunc("GET /produk/{id}", detailProduk)
    
    // dilindungi API key
    mux.Handle("POST /produk", cekAPIKey(http.HandlerFunc(tambahProduk)))
    mux.Handle("PUT /produk/{id}", cekAPIKey(http.HandlerFunc(ubahProduk)))
    mux.Handle("DELETE /produk/{id}", cekAPIKey(http.HandlerFunc(hapusProduk)))
    
    // logging tetap membungkus semua
    handler := logging(mux)

    Pola "POST /produk" dengan method di depan adalah fitur routing Go 1.22 yang sudah kita pakai sejak bagian 11. Sekarang terasa manfaatnya: satu path bisa punya perlakuan beda per method.

    Sekilas jujur soal auth user sungguhan

    API key cukup untuk API internal. Tapi begitu kamu bikin aplikasi dengan user yang mendaftar dan login sendiri, kebutuhannya beda jauh. Ada tiga hal yang minimal harus kamu tahu namanya.

    Pertama, password tidak boleh disimpan mentah di database. Password harus di-hash pakai algoritma yang memang dirancang untuk itu, misalnya bcrypt. Hash itu satu arah. Kalau database bocor, penyerang tidak langsung dapat password asli.

    Kedua, setelah user login, server perlu mengingat dia di request berikutnya. Ada dua pendekatan umum. Session: server menyimpan data login dan memberi user sebuah ID lewat cookie. Token JWT: server memberi user token berisi data yang sudah ditandatangani, dan server tinggal memverifikasi tanda tangannya tanpa menyimpan apa pun.

    Ketiga, masing-masing pendekatan punya konsekuensi soal logout, expiry, dan keamanan yang tidak bisa dijelaskan dua paragraf. Jadi kita tidak implement sekarang. Topik ini butuh bagian sendiri, dan enaknya dibahas setelah kita bisa menulis test. Kode auth tanpa test itu ngeri.

    Bonus singkat: CORS

    Satu lagi middleware yang cepat atau lambat kamu butuhkan. Kalau nanti API ini dipanggil dari frontend JavaScript yang berjalan di domain lain, misalnya localhost:3000 memanggil localhost:8080, browser akan memblokir responsnya. Ini kebijakan keamanan browser bernama same-origin policy. Supaya diizinkan, server harus mengirim header CORS yang menyatakan siapa boleh mengakses.

    func cors(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.Header().Set("Access-Control-Allow-Origin", "*")
            w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
            w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-API-Key")
            if r.Method == http.MethodOptions {
                w.WriteHeader(http.StatusNoContent)
                return
            }
            next.ServeHTTP(w, r)
        })
    }

    Tanda * berarti semua domain boleh. Untuk produksi, ganti dengan domain frontend kamu. Detail CORS panjang, tapi bentuk middleware-nya sama saja dengan yang sudah kita pelajari.

    Program utuh

    Sekarang kita gabungkan semuanya dengan API produk dari bagian 12. Struktur handler tidak berubah, kita hanya menambah lapisan di depannya.

    package main
    
    import (
        "database/sql"
        "encoding/json"
        "log"
        "net/http"
        "os"
        "time"
    
        _ "github.com/go-sql-driver/mysql"
    )
    
    type Produk struct {
        ID    int    `json:"id"`
        Nama  string `json:"nama"`
        Harga int    `json:"harga"`
    }
    
    var db *sql.DB
    
    func logging(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            mulai := time.Now()
            next.ServeHTTP(w, r)
            log.Printf("%s %s selesai dalam %v", r.Method, r.URL.Path, time.Since(mulai))
        })
    }
    
    func cekAPIKey(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            kunci := os.Getenv("API_KEY")
            if kunci == "" || r.Header.Get("X-API-Key") != kunci {
                w.Header().Set("Content-Type", "application/json")
                w.WriteHeader(http.StatusUnauthorized)
                json.NewEncoder(w).Encode(map[string]string{"error": "API key salah atau tidak ada"})
                return
            }
            next.ServeHTTP(w, r)
        })
    }
    
    func listProduk(w http.ResponseWriter, r *http.Request) {
        rows, err := db.Query("SELECT id, nama, harga FROM produk")
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        defer rows.Close()
    
        produk := []Produk{}
        for rows.Next() {
            var p Produk
            if err := rows.Scan(&p.ID, &p.Nama, &p.Harga); err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            produk = append(produk, p)
        }
    
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(produk)
    }
    
    func tambahProduk(w http.ResponseWriter, r *http.Request) {
        var p Produk
        if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
            http.Error(w, "body tidak valid", http.StatusBadRequest)
            return
        }
    
        hasil, err := db.Exec("INSERT INTO produk (nama, harga) VALUES (?, ?)", p.Nama, p.Harga)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    
        id, _ := hasil.LastInsertId()
        p.ID = int(id)
    
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusCreated)
        json.NewEncoder(w).Encode(p)
    }
    
    func main() {
        var err error
        db, err = sql.Open("mysql", os.Getenv("DSN"))
        if err != nil {
            log.Fatal(err)
        }
        if err := db.Ping(); err != nil {
            log.Fatal(err)
        }
    
        mux := http.NewServeMux()
        mux.HandleFunc("GET /produk", listProduk)
        mux.Handle("POST /produk", cekAPIKey(http.HandlerFunc(tambahProduk)))
    
        log.Println("Server jalan di :8080")
        log.Fatal(http.ListenAndServe(":8080", logging(mux)))
    }

    Jalankan dengan environment variable yang lengkap:

    export DSN="root:passwordmu@tcp(127.0.0.1:3306)/belajar_golang"
    export API_KEY="rahasia-123"
    go run main.go

    Uji dengan curl

    Coba POST tanpa API key dulu:

    curl -i -X POST http://localhost:8080/produk \
      -H "Content-Type: application/json" \
      -d '{"nama":"Kopi Arabika","harga":85000}'

    Responsnya ditolak:

    HTTP/1.1 401 Unauthorized
    Content-Type: application/json
    
    {"error":"API key salah atau tidak ada"}

    Sekarang ulangi dengan header X-API-Key:

    curl -i -X POST http://localhost:8080/produk \
      -H "Content-Type: application/json" \
      -H "X-API-Key: rahasia-123" \
      -d '{"nama":"Kopi Arabika","harga":85000}'

    Kali ini berhasil:

    HTTP/1.1 201 Created
    Content-Type: application/json
    
    {"id":4,"nama":"Kopi Arabika","harga":85000}

    Sementara itu GET /produk tetap bisa diakses siapa saja tanpa header apa pun. Dan di terminal server, setiap request tercatat rapi oleh middleware logging.

    Penutup

    API produk kita sekarang punya satpam. Logging mencatat semua tamu, API key menjaga pintu operasi tulis, dan tidak ada satu pun kode pengecekan yang di-copy paste antar handler. Kamu juga sudah paham kenapa pola func(http.Handler) http.Handler bisa dirangkai, dan kapan API key cukup serta kapan butuh auth yang lebih serius.

    Tapi jujur saja, dari bagian 11 sampai sekarang kita mengetes API cuma lewat curl dan mata. Begitu kode makin panjang, cara itu tidak bisa diandalkan. Di bagian 14 kita bahas “Testing di Go: Menguji Function dan Handler API” supaya setiap perubahan bisa diverifikasi otomatis.

    Kalau kamu sedang membangun API atau sistem internal untuk bisnis dan butuh bantuan yang serius, tim kami di Arrazy Inovasi menyediakan jasa pembuatan sistem aplikasi dari perancangan sampai deployment.

  • Kompleksitas Algoritma: Kenapa Struktur Data Menentukan Performa

    Kompleksitas Algoritma: Kenapa Struktur Data Menentukan Performa

    Kompleksitas algoritma adalah ukuran seberapa banyak operasi yang dibutuhkan sebuah kode ketika ukuran datanya bertambah. Bukan seberapa cepat CPU kamu, bukan seberapa mahal servernya. Kode yang butuh 1 juta operasi akan selalu kalah dari kode yang butuh 1 operasi, mau dijalankan di laptop kentang atau server sultan. Dan yang menentukan jumlah operasi itu, sebagian besar, adalah pilihan struktur data.

    Ini bagian kedua dari seri Belajar Struktur Data dari Nol. Di artikel ini kita buktikan klaim di atas lewat eksperimen langsung: mencari satu item di antara 1 juta data dengan dua cara berbeda, lalu mengukur selisihnya. Setelah itu kita belajar cara mengukur yang benar pakai micro-benchmark bawaan Go.

    Prasyarat Sebelum Mulai

    Kamu butuh Go yang sudah terpasang dan paham cara menjalankan file Go sederhana. Kalau belum, ikuti dulu bagian pertama: Belajar Struktur Data dari Nol: Pengenalan dan Persiapan Go. Semua kode di artikel ini saya uji dengan Go 1.23.1 di Linux, tapi jalan normal di Go 1.21 ke atas. Cek versi kamu dengan go version.

    Eksperimen: Mencari 1 Item di Antara 1 Juta Data

    Kita mulai dari eksperimen, bukan teori. Buat folder baru, lalu jalankan dua perintah ini:

    mkdir kompleksitas && cd kompleksitas
    go mod init kompleksitas

    Buat file main.go berisi kode berikut. Kode ini menyiapkan 1 juta ID user, lalu mencari satu ID yang sama dengan dua cara: loop satu per satu di slice, dan akses langsung di map.

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	const n = 1_000_000
    	target := "user-999999"
    
    	// Siapkan data: 1 juta ID di slice dan di map
    	daftar := make([]string, 0, n)
    	indeks := make(map[string]bool, n)
    	for i := 0; i < n; i++ {
    		id := fmt.Sprintf("user-%d", i)
    		daftar = append(daftar, id)
    		indeks[id] = true
    	}
    
    	// Cara 1: loop seluruh slice
    	mulai := time.Now()
    	ketemu := false
    	for _, id := range daftar {
    		if id == target {
    			ketemu = true
    			break
    		}
    	}
    	fmt.Println("slice:", ketemu, "butuh", time.Since(mulai))
    
    	// Cara 2: akses langsung ke map
    	mulai = time.Now()
    	_, ada := indeks[target]
    	fmt.Println("map  :", ada, "butuh", time.Since(mulai))
    }

    Jalankan dengan go run main.go. Di laptop yang saya pakai, hasilnya kira-kira seperti ini:

    slice: true butuh 13.003519ms
    map  : true butuh 819ns

    Angka di mesin kamu pasti berbeda, tapi polanya akan sama: map menang telak. Di contoh ini selisihnya ribuan kali lipat. Padahal dua-duanya menjawab pertanyaan yang sama persis: apakah user-999999 ada di dalam data.

    Kenapa Selisihnya Sejauh Itu: Jumlah Operasi, Bukan Kecepatan CPU

    Loop di slice memeriksa item satu per satu dari depan. Target kita ada di posisi terakhir, jadi Go harus melakukan sekitar 1 juta perbandingan string sebelum ketemu. Map bekerja beda: dia menghitung hash dari kata kunci, lalu langsung melompat ke lokasi penyimpanannya. Berapa pun jumlah datanya, operasinya tetap segelintir.

    Di sinilah inti kompleksitas algoritma. Yang kita hitung bukan detik, tapi jumlah operasi sebagai fungsi dari ukuran data. Perhatikan tabel ini:

    Jumlah data Perkiraan operasi loop slice (kasus terburuk) Perkiraan operasi akses map
    1.000 1.000 sekitar 1
    1.000.000 1.000.000 sekitar 1
    1.000.000.000 1.000.000.000 sekitar 1

    CPU yang dua kali lebih cepat cuma memotong waktu jadi setengah. Tapi mengganti slice ke map memotong 1 miliar operasi jadi satu. Tidak ada upgrade hardware yang bisa menyaingi itu. Makanya programmer berpengalaman lebih dulu bertanya “struktur datanya apa” sebelum bertanya “servernya apa”.

    Time Complexity vs Space Complexity

    Kompleksitas algoritma punya dua sisi yang selalu tarik-menarik:

    • Time complexity: berapa banyak operasi yang dibutuhkan seiring data membesar. Ini yang barusan kita lihat di eksperimen.
    • Space complexity: berapa banyak memori tambahan yang dipakai seiring data membesar.

    Eksperimen kita sebenarnya contoh klasik dari tarik-menarik ini. Map kita cepat karena dia menyimpan struktur tambahan di memori: bucket, hash, dan metadata lain. Kita membayar memori ekstra untuk membeli kecepatan. Loop slice hemat memori karena tidak butuh struktur tambahan apa pun, tapi bayarannya waktu.

    Tidak ada jawaban yang selalu benar. Kalau datanya cuma 20 item dan dicari sekali, loop slice sudah lebih dari cukup. Kalau datanya jutaan dan dicari ribuan kali per detik, struktur tambahan seperti map jadi wajib. Kemampuan menimbang ini yang akan terus kita asah sepanjang seri.

    Praktik: Menulis Micro-Benchmark dengan testing.B

    Mengukur pakai time.Since cocok untuk demo, tapi kurang bisa diandalkan untuk keputusan serius. Sekali jalan bisa kena gangguan: sistem operasi lagi sibuk, cache CPU masih dingin, atau garbage collector kebetulan aktif. Go menyediakan alat resmi untuk ini: benchmark di package testing.

    Buat file cari.go berisi dua fungsi yang mau kita adu:

    package main
    
    func CariDiSlice(data []string, target string) bool {
    	for _, v := range data {
    		if v == target {
    			return true
    		}
    	}
    	return false
    }
    
    func CariDiMap(data map[string]bool, target string) bool {
    	return data[target]
    }

    Lalu buat file cari_test.go. Nama file wajib berakhiran _test.go dan nama fungsi wajib diawali Benchmark:

    package main
    
    import (
    	"fmt"
    	"testing"
    )
    
    const jumlahData = 100_000
    
    // Variabel global sebagai penampung hasil,
    // supaya compiler tidak menghapus kode yang diukur.
    var hasil bool
    
    func siapkanSlice() []string {
    	data := make([]string, 0, jumlahData)
    	for i := 0; i < jumlahData; i++ {
    		data = append(data, fmt.Sprintf("user-%d", i))
    	}
    	return data
    }
    
    func siapkanMap() map[string]bool {
    	data := make(map[string]bool, jumlahData)
    	for i := 0; i < jumlahData; i++ {
    		data[fmt.Sprintf("user-%d", i)] = true
    	}
    	return data
    }
    
    func BenchmarkCariDiSlice(b *testing.B) {
    	data := siapkanSlice()
    	target := "user-99999"
    	b.ResetTimer()
    	for i := 0; i < b.N; i++ {
    		hasil = CariDiSlice(data, target)
    	}
    }
    
    func BenchmarkCariDiMap(b *testing.B) {
    	data := siapkanMap()
    	target := "user-99999"
    	b.ResetTimer()
    	for i := 0; i < b.N; i++ {
    		hasil = CariDiMap(data, target)
    	}
    }

    Dua detail penting di kode ini. Pertama, b.ResetTimer() dipanggil setelah data disiapkan, supaya waktu menyiapkan 100 ribu item tidak ikut terhitung. Kedua, hasil fungsi disimpan ke variabel global hasil, alasannya kita bahas di bagian troubleshooting. Jalankan dengan:

    go test -bench=. -benchmem

    Contoh keluaran di mesin saya:

    goos: linux
    goarch: amd64
    pkg: kompleksitas
    cpu: Intel(R) Core(TM) i5-10310U CPU @ 1.70GHz
    BenchmarkCariDiSlice-8        2326    537460 ns/op       0 B/op       0 allocs/op
    BenchmarkCariDiMap-8      86166330     22.97 ns/op       0 B/op       0 allocs/op
    PASS
    ok      kompleksitas    4.353s

    Cara Membaca Hasil Benchmark Go

    • Angka setelah nama fungsi (3021 dan 29847216) adalah b.N, yaitu berapa kali Go mengulang pengukuran. Go sendiri yang menentukan jumlah ini sampai hasilnya stabil secara statistik. Inilah bedanya dengan time.Since yang cuma mengukur sekali.
    • ns/op adalah rata-rata nanodetik per operasi. Di sini pencarian slice makan sekitar 537 ribu nanodetik, pencarian map sekitar 23 nanodetik.
    • B/op dan allocs/op muncul karena flag -benchmem, menunjukkan alokasi memori per operasi. Ini sisi space complexity dalam angka nyata.

    Kasus Nyata di Backend: Full Scan vs Lookup Terindeks

    Pola slice vs map ini bukan cuma soal latihan. Ini persis pola yang muncul di database. Query yang menyaring kolom tanpa indeks memaksa database membaca seluruh tabel baris per baris, istilahnya full table scan. Sama seperti loop slice kita. Query di kolom yang terindeks bisa melompat langsung ke baris yang dicari, sama seperti akses map.

    Tim Arrazy beberapa kali menemukan kasus ini saat membangun sistem aplikasi untuk klien: endpoint yang terasa cepat saat data masih ratusan mendadak lambat setelah data tumbuh ke ratusan ribu. Kodenya tidak berubah sama sekali. Yang berubah cuma ukuran datanya, dan kompleksitas yang tadinya tidak terasa mulai menagih. Solusinya hampir selalu sama polanya: ganti cara akses dari scan menyeluruh ke lookup terindeks, entah lewat indeks database, map di memori, atau cache.

    Pelajaran praktisnya: kode yang lolos testing dengan data kecil belum tentu selamat di produksi. Kompleksitas baru kelihatan giginya saat data membesar.

    Troubleshooting: Error yang Sering Dialami Pemula Saat Benchmark

    Benchmark terlihat mustahil cepat, misalnya 0.25 ns/op

    Penyebab: hasil fungsi tidak dipakai, jadi compiler Go menganggap kodenya percuma dan menghapusnya. Yang kamu ukur akhirnya loop kosong. Ini disebut dead code elimination, dan hasilnya benchmark bohong. Solusi: simpan hasil ke variabel global seperti hasil di contoh kita, atau pakai b.Loop() di Go 1.24 ke atas yang otomatis mencegah optimasi ini. Kalau kamu melihat angka di bawah 1 ns/op untuk operasi yang jelas berat, curigai masalah ini.

    go test tidak menjalankan benchmark sama sekali

    Penyebab paling umum ada dua. Pertama, menjalankan go test tanpa flag -bench, karena secara default Go hanya menjalankan fungsi Test, bukan Benchmark. Kedua, salah tulis pola, misalnya -bench tanpa nilai akan error flag needs an argument. Solusi: selalu sertakan pola, minimal -bench=. yang artinya jalankan semua benchmark.

    Muncul pesan “no test files”

    Penyebab: nama file tidak berakhiran _test.go, atau file test beda package dengan kode yang diuji. Solusi: pastikan nama file persis berpola nama_test.go dan baris package di file test sama dengan file utamanya.

    Angka hasil benchmark berubah-ubah tiap kali dijalankan

    Penyebab: mesin kamu mengerjakan hal lain saat benchmark jalan, misalnya browser dengan banyak tab atau proses build lain. Variasi 5 sampai 10 persen itu normal. Solusi: tutup aplikasi berat, lalu jalankan beberapa putaran dengan go test -bench=. -count=5 dan bandingkan hasilnya. Keputusan diambil dari pola beberapa putaran, bukan dari satu angka keramat.

    Latihan: Prediksi Dulu, Baru Buktikan

    Kebiasaan paling berharga dari bagian ini: sebelum menjalankan benchmark, tulis dulu prediksimu. Cocokkan setelahnya. Coba tiga latihan ini:

    1. Ubah target di benchmark menjadi "user-0", item paling depan. Prediksi dulu: apakah BenchmarkCariDiSlice jadi jauh lebih cepat, sama saja, atau lebih lambat? Bagaimana dengan map? Jalankan dan cocokkan.
    2. Ubah jumlahData dari 100.000 menjadi 1.000.000. Prediksi: waktu slice naik sekitar 10 kali lipat atau tetap? Waktu map bagaimana? Buktikan.
    3. Cari target yang tidak ada di data, misalnya "tidak-ada". Prediksi mana yang paling terpengaruh, lalu jalankan.

    Kalau prediksimu meleset, justru bagus. Di situ letak belajarnya: cari tahu kenapa angka nyata tidak sesuai bayanganmu. Latihan nomor 1 misalnya, sering mengejutkan pemula karena slice mendadak menang.

    Lanjut ke Bagian Berikutnya

    Kamu sekarang sudah merasakan sendiri bahwa jumlah operasi menentukan performa, dan sudah bisa mengukurnya dengan benar pakai benchmark. Yang belum kita punya adalah bahasa standar untuk menyebut perbedaan itu. Menyebut “sekitar 1 juta operasi” tiap kali jelas tidak praktis.

    Bahasa standar itu namanya notasi Big O, dan itu bahasan bagian ketiga: Big O Notation: Cara Mengukur Kompleksitas Algoritma. Artikelnya terbit menyusul, pantau daftar lengkapnya di halaman hub Belajar Struktur Data dari Nol.

    Referensi

  • Beda Domain dan Hosting, Dijelaskan untuk Pemilik Bisnis

    Beda Domain dan Hosting, Dijelaskan untuk Pemilik Bisnis

    Bedanya sederhana. Domain itu alamat toko, hosting itu ruko atau bangunannya, dan website adalah isi tokonya. Ketika orang mengetik namabisnismu.com di browser, mereka sedang membaca alamat. Alamat itu mengarahkan mereka ke sebuah “bangunan” berupa server tempat semua file website kamu disimpan. Isi bangunan itulah yang muncul di layar mereka, mulai dari halaman depan sampai katalog produk.

    Kamu tidak perlu paham server untuk menjalankan bisnis. Tapi ada satu hal yang wajib kamu pegang sejak awal: domain dan hosting harus terdaftar atas nama bisnismu, bukan atas nama vendor yang membuatkan website. Artikel ini menjelaskan keduanya dengan bahasa sehari-hari, supaya kamu tidak bengong saat vendor mulai bicara istilah teknis, dan tidak bisa dibodohi saat tagihan datang.

    Domain: alamat toko yang kamu sewa tiap tahun

    Domain adalah nama yang diketik orang untuk menemukan website kamu, misalnya tokokopimu.com. Sama seperti alamat toko, domain harus unik. Tidak ada dua bisnis yang bisa memakai alamat yang persis sama.

    Kenapa bayar tahunan? Karena domain sebenarnya bukan dibeli putus, tapi disewa dari lembaga pengelola nama domain dunia melalui perusahaan yang disebut registrar. Selama kamu rutin memperpanjang, alamat itu tetap milikmu. Biayanya relatif kecil, biasanya ratusan ribu rupiah per tahun tergantung akhirannya.

    Soal akhiran, tiga yang paling sering muncul di Indonesia:

    • .com paling umum dan paling mudah diingat. Siapa saja bisa mendaftar tanpa syarat.
    • .co.id khusus badan usaha di Indonesia. Pendaftarannya butuh dokumen legalitas seperti NIB. Justru karena ada syarat itu, .co.id memberi kesan lebih kredibel untuk perusahaan.
    • .id jalan tengah. Identitas Indonesia, syaratnya lebih ringan dari .co.id.

    Yang sering luput dari pemilik bisnis adalah urusan perpanjangan. Kalau domain telat diperpanjang, dia bisa hangus. Setelah masa tenggang lewat, alamat itu kembali ke pasar dan bisa didaftarkan siapa saja, termasuk kompetitor atau makelar domain. Menebusnya kembali sering kali jauh lebih mahal daripada biaya perpanjangan biasa, itu pun kalau masih bisa. Bayangkan alamat toko yang sudah dikenal pelanggan tiba-tiba dipakai orang lain. Jadi pastikan kamu tahu kapan domain jatuh tempo, dan notifikasi perpanjangannya masuk ke email kamu, bukan hanya ke email vendor.

    Hosting: ruko tempat website kamu berdiri

    Hosting adalah komputer server yang menyala 24 jam untuk menyimpan file website kamu dan melayani setiap pengunjung yang datang. Kalau domain itu alamatnya, hosting adalah bangunannya. Tanpa hosting, alamat kamu menunjuk ke tanah kosong.

    Harga hosting bervariasi jauh, dari belasan ribu sampai jutaan rupiah per bulan. Perbedaannya mirip perbedaan ruko:

    • Kapasitas. Ruko kecil cukup untuk toko kecil. Website company profile sederhana tidak butuh hosting mahal. Tapi kalau pengunjungmu ribuan per hari, “ruko” kecil akan sesak.
    • Kecepatan. Server yang bagus melayani pengunjung lebih cepat. Ini terasa langsung di pengalaman orang yang membuka website kamu.
    • Lokasi server. Server di Indonesia atau Singapura biasanya lebih cepat diakses pengunjung Indonesia daripada server di Amerika atau Eropa.

    Bagaimana tahu hosting kamu bermasalah? Dua tandanya gampang dikenali tanpa ilmu teknis. Pertama, website sering down alias tidak bisa dibuka sama sekali. Kedua, website terasa lambat, apalagi di jam ramai. Kalau dua hal itu sering terjadi, tanyakan ke vendor apa penyebabnya. Bisa jadi paket hostingnya terlalu kecil untuk trafik kamu, bisa juga penyedianya memang kurang bagus.

    Satu hal lagi yang sering ditanyakan: SSL. Itu yang membuat ikon gembok muncul di browser dan alamat website diawali https. Sekarang SSL sudah jadi standar dan biasanya sudah termasuk dalam paket hosting tanpa biaya tambahan. Kalau ada vendor menjualnya sebagai biaya terpisah yang mahal, patut kamu pertanyakan.

    Bagian paling penting: atas nama siapa semuanya terdaftar

    Ini bagian yang paling ingin kami tekankan. Bukan soal .com atau .co.id, bukan soal hosting murah atau mahal, tapi soal kepemilikan.

    Banyak vendor mendaftarkan domain dan hosting klien memakai akun milik vendor sendiri. Alasannya masuk akal di awal, biar praktis, klien tidak perlu repot. Masalahnya baru terasa belakangan. Kalau hubungan kerja sama berakhir, baik baik atau tidak, semua akses ada di tangan vendor. Kamu mau pindah ke pengembang lain, ternyata kamu tidak pegang apa apa. Domain atas nama vendor, hosting atas nama vendor, dan kamu tinggal menunggu itikad baik mereka.

    Kasus seperti ini nyata dan sering terjadi. Bisnis yang websitenya sudah jalan bertahun-tahun tiba-tiba “disandera” karena vendor lama minta bayaran tidak wajar untuk menyerahkan akses, atau sekadar sulit dihubungi. Domain yang sudah dikenal pelanggan tidak bisa dipindahkan karena bukan milikmu di atas kertas.

    Cara mengeceknya tidak sulit:

    • Tanya akses panel. Minta akses ke akun tempat domain dan hosting dikelola, atau minimal pastikan akun itu terdaftar dengan email bisnismu. Vendor yang benar tidak akan keberatan.
    • Cek data whois. Whois adalah data publik pemilik domain. Kamu bisa mengeceknya lewat situs pengecek whois gratis di internet. Sebagian data memang disembunyikan demi privasi, tapi kamu tetap bisa minta vendor menunjukkan bahwa nama bisnismu yang tercatat sebagai pemilik.
    • Minta invoice atas nama bisnismu. Bukti bayar dari registrar atau penyedia hosting yang mencantumkan nama bisnismu adalah pegangan tambahan kalau suatu saat terjadi sengketa.

    Urusan kepemilikan ini seharusnya beres di awal proyek, dan dicek ulang saat proyek selesai. Kami pernah menulis lebih detail soal apa saja yang wajib diserahkan vendor di akhir proyek dalam checklist serah terima proyek dari vendor. Prinsipnya sama: semua aset digital bisnismu harus benar-benar ada di tanganmu.

    Email profesional ikut menumpang di domain

    Email seperti nama@bisnismu.com bukan produk terpisah yang berdiri sendiri. Dia menumpang di domain kamu. Selama domain aktif dan atas namamu, email itu bisa dibuat, entah lewat fasilitas email di hosting atau lewat layanan seperti Google Workspace yang disambungkan ke domain.

    Konsekuensinya penting. Kalau domain hangus atau dikuasai vendor, email bisnismu ikut mati. Semua akun yang terdaftar memakai email itu, dari internet banking sampai media sosial bisnis, jadi susah dipulihkan. Satu alasan lagi kenapa domain harus atas nama bisnismu sendiri.

    Pertanyaan wajib sebelum bayar vendor

    Tidak perlu jadi orang teknis untuk melindungi diri. Cukup ajukan pertanyaan ini sebelum transfer pembayaran pertama:

    • Domain dan hosting didaftarkan atas nama siapa? Jawaban yang benar hanya satu: atas nama kamu atau bisnismu. Kalau vendor bilang “atas nama kami biar gampang”, minta diubah.
    • Kalau kerja sama berhenti, bagaimana? Pastikan ada jawaban jelas soal serah terima akses domain, hosting, dan file website. Lebih bagus lagi kalau tertulis di kesepakatan.
    • Biaya perpanjangan tahun kedua berapa? Harga tahun pertama sering promo. Ada vendor yang menaruh harga murah di depan lalu menaikkan biaya perpanjangan berlipat-lipat. Minta angkanya sejak awal supaya tidak kaget.

    Vendor yang sehat akan menjawab tiga pertanyaan itu tanpa berbelit. Kalau jawabannya mengambang, itu sinyal untuk berpikir ulang.

    Pertanyaan yang sering muncul

    Apakah saya bisa pindah hosting tanpa ganti domain?

    Bisa, dan ini hal yang wajar. Domain dan hosting adalah dua hal terpisah. Sama seperti toko yang pindah bangunan tapi tetap memakai papan nama yang sama, kamu bisa memindahkan website ke hosting lain lalu mengarahkan domain ke sana. Pelanggan tetap mengetik alamat yang sama seperti biasa.

    Domain saya terlanjur atas nama vendor. Harus bagaimana?

    Minta transfer kepemilikan baik-baik selagi hubungan masih bagus. Prosesnya bisa berupa pemindahan domain ke akun registrar milikmu, atau perubahan data pemilik. Jangan tunggu sampai ada masalah, karena posisi tawarmu paling kuat justru saat semuanya masih baik-baik saja.

    Lebih baik beli domain dan hosting sendiri atau lewat vendor?

    Dua-duanya boleh, asal kepemilikannya jelas. Kalau kamu mau paling aman, daftarkan sendiri dengan email bisnismu lalu beri vendor akses secukupnya untuk bekerja. Kalau lewat vendor pun tidak masalah, selama terdaftar atas nama bisnismu dan kamu pegang aksesnya.

    Intinya: pahami sedikit, selamat banyak

    Domain itu alamat, hosting itu bangunan, website itu isi tokonya. Ketiganya harus jelas milik siapa, dan jawabannya harus selalu bisnismu. Dengan bekal ini kamu sudah bisa menilai penawaran vendor dengan lebih tenang, tahu apa yang harus ditanyakan, dan tidak mudah disandera di kemudian hari.

    Kalau kamu sedang mencari vendor yang beres soal ini, paket jasa pembuatan website kami sudah termasuk domain dan hosting yang didaftarkan atas nama klien sejak hari pertama, lengkap dengan serah terima akses di akhir proyek. Ada yang masih bikin bingung soal domain atau hosting yang sekarang kamu pakai? Hubungi kami, kami bantu cek tanpa biaya dan tanpa harus jadi klien dulu.

  • Katalog Produk Digital: PDF, Web, atau WhatsApp? Cara Memilihnya

    Katalog Produk Digital: PDF, Web, atau WhatsApp? Cara Memilihnya

    Capek kirim foto produk satu per satu tiap ada yang tanya “ada apa aja kak”? Itu tanda kamu butuh katalog produk digital. Jawaban singkatnya begini. Kalau produkmu masih sedikit dan harganya jarang berubah, katalog PDF atau fitur Katalog WhatsApp Business sudah cukup. Kalau produkmu banyak, harga sering berubah, atau kamu ingin ditemukan pembeli baru lewat Google, katalog web adalah pilihan yang paling masuk akal.

    Artikel ini membandingkan tiga format itu dengan jujur. Kelebihan, kekurangan, sampai situasi mana yang cocok untuk masing-masing. Tidak ada format yang paling benar untuk semua orang. Yang ada, format yang paling pas dengan cara jualanmu sekarang.

    Katalog PDF: cepat dibuat, tapi cepat kadaluarsa

    Ini format yang paling banyak dipakai pemilik usaha. Buka Canva, pilih template, masukkan foto dan harga, ekspor jadi PDF. Satu jam juga jadi. Tinggal kirim ke pembeli lewat WhatsApp atau email.

    Kelebihannya jelas. Cepat dibuat, tidak butuh biaya, dan enak dikirim sekali jalan. Pembeli terima satu file berisi semua produk, bisa dibuka kapan saja tanpa internet. Untuk jualan ke reseller atau calon klien yang minta daftar produk lengkap, PDF terasa rapi dan profesional.

    Masalahnya muncul belakangan. Begitu harga berubah, semua file PDF yang sudah beredar langsung jadi kadaluarsa. Kamu tidak bisa menariknya kembali. Pembeli yang menyimpan katalog lama akan datang dengan harga lama, dan kamu yang repot menjelaskan. Makin sering harga berubah, makin sering kamu bikin ulang dan sebar ulang file baru.

    Ada dua kelemahan lain yang jarang disadari. Pertama, ukuran file. Katalog dengan banyak foto bagus gampang tembus belasan MB, dan tidak semua pembeli mau mengunduh file sebesar itu lewat kuota. Kedua, kamu tidak pernah tahu apakah katalog itu dibuka atau tidak. Kirim ke 50 orang, yang buka mungkin cuma lima. Kamu tidak punya datanya sama sekali.

    Kalau tetap mau pakai PDF, ada trik sederhana supaya kekacauan harga lebih terkendali. Beri nama file dengan bulan dan tahun, misalnya Katalog-Maret-2026.pdf, lalu cantumkan tulisan “harga berlaku sampai” di halaman pertama. Pembeli jadi tahu kapan harus minta katalog baru, dan kamu punya alasan yang enak saat harga di file lama sudah tidak berlaku.

    Katalog WhatsApp: nempel di profil, gampang diakses pembeli

    WhatsApp Business punya fitur Katalog bawaan. Kamu unggah foto produk, nama, harga, dan deskripsi langsung dari aplikasi. Katalog ini nempel di profil bisnismu, jadi setiap orang yang chat bisa lihat daftar produk tanpa kamu kirim apa pun.

    Untuk usaha yang jualannya memang lewat WhatsApp, ini praktis sekali. Pembeli tinggal buka profil, pilih produk, lalu langsung tanya di chat yang sama. Tidak ada file yang perlu diunduh. Dan saat kamu update harga di katalog, semua orang otomatis lihat versi terbaru. Ini keunggulan besar dibanding PDF.

    Tapi fitur ini punya batas yang cepat terasa. Katalog WA dirancang untuk daftar produk sederhana. Begitu produkmu punya banyak varian, misalnya satu model baju dengan lima warna dan enam ukuran, tampilannya jadi berantakan. Tidak ada filter, tidak ada kategori yang leluasa, tidak ada pencarian yang enak. Pembeli harus scroll panjang untuk menemukan yang dia cari.

    Kelemahan terbesarnya satu lagi. Katalog WhatsApp tidak bisa tampil di Google. Orang yang belum kenal bisnismu tidak akan pernah menemukannya. Katalog WA hanya bekerja untuk orang yang sudah masuk ke WhatsApp-mu. Untuk menjangkau pembeli baru, formatnya buntu.

    Katalog web: satu link yang selalu terbaru

    Format ketiga adalah halaman katalog di website sendiri. Semua produk tampil di satu alamat, misalnya namatokomu.com/katalog. Kamu cukup bagikan satu link itu ke mana saja. Bio Instagram, balasan WhatsApp, brosur, semuanya mengarah ke tempat yang sama.

    Keunggulan pertama, katalog web selalu terbaru. Ganti harga hari ini, semua orang yang buka link langsung lihat harga baru. Tidak ada file lama yang beredar, tidak ada salah paham soal harga. Keunggulan kedua, katalog web bisa ditemukan lewat Google. Orang yang mengetik nama produkmu plus kota bisa sampai ke katalogmu tanpa kenal kamu sebelumnya. PDF dan katalog WA tidak bisa melakukan ini.

    Keunggulan ketiga, kamu bisa lihat statistik. Berapa orang yang buka katalog, produk mana yang paling sering dilihat, dari mana mereka datang. Data ini berguna untuk memutuskan produk mana yang perlu distok lebih banyak atau dipromosikan.

    Kekurangannya juga perlu diakui. Katalog web butuh biaya dan waktu untuk dibuat. Ada biaya domain, hosting, dan pembuatan halamannya. Kalau dikerjakan sendiri tanpa pengalaman, prosesnya bisa berminggu-minggu. Kalau pakai jasa pembuatan website, ada biaya di awal, meskipun setelah jadi perawatannya ringan. Untuk usaha yang baru mulai dengan lima produk, investasi ini sering belum sepadan.

    Kalau ragu mulai dari mana, tidak harus langsung website besar. Satu halaman katalog sederhana lewat jasa landing page sudah cukup untuk menampilkan produk, harga, dan tombol order via WhatsApp. Biayanya jauh lebih ringan dan bisa dikembangkan nanti.

    Cara memilih: cocokkan dengan kondisi jualanmu

    Perbandingan ringkas empat faktor

    Supaya gampang memutuskan, bandingkan empat hal ini dengan kondisi usahamu.

    • Jumlah produk. Di bawah 20 produk, PDF dan katalog WA masih nyaman. Di atas itu, apalagi dengan banyak varian, katalog web jauh lebih rapi karena ada kategori dan pencarian.
    • Seberapa sering harga berubah. Harga stabil berbulan-bulan, PDF aman. Harga berubah tiap minggu atau mengikuti kurs dan bahan baku, pilih katalog WA atau web yang bisa diupdate sekali untuk semua orang.
    • Siapa pembelinya. Pembeli lama yang sudah kenal, katalog WA paling praktis. Reseller yang butuh daftar lengkap, PDF enak dikirim. Pembeli baru yang belum kenal bisnismu, hanya katalog web yang bisa menjangkau mereka lewat Google.
    • Budget. PDF dan katalog WA gratis. Katalog web butuh biaya, mulai dari ratusan ribu untuk satu halaman sederhana sampai jutaan untuk katalog lengkap dengan banyak kategori.

    Rekomendasi per situasi

    Kalau masih bingung, cari situasi yang paling mirip dengan kondisimu di daftar ini.

    • Baru mulai, produk di bawah 20. Pakai katalog WA sebagai etalase utama, plus PDF untuk dikirim saat ada yang minta daftar lengkap. Gratis, cepat, cukup. Simpan uangmu untuk stok dan foto produk dulu.
    • Produk sering ganti harga. Langsung ke katalog web. Setiap perubahan harga cukup diedit sekali dan semua orang lihat versi terbaru. Berhenti bikin ulang PDF tiap ada perubahan.
    • Mau ditemukan pembeli baru. Katalog web satu-satunya format yang bisa muncul di hasil pencarian Google. Kalau targetmu bukan cuma melayani pembeli lama tapi mendatangkan yang baru, dua format lain tidak bisa membantu.
    • Jualan ke reseller. Kombinasi. Katalog web untuk harga retail yang bisa dilihat semua orang, ditambah PDF terpisah per segmen berisi harga khusus reseller. Harga reseller memang sebaiknya tidak dipajang terbuka.

    Perhatikan juga bahwa formatnya boleh berlapis. Banyak usaha memakai katalog web sebagai pusat, lalu katalog WA untuk pembeli yang sudah chat, dan PDF untuk keperluan penawaran resmi. Yang penting sumber harganya satu, yaitu yang paling mudah kamu update.

    Kesalahan umum yang bikin katalog sepi orderan

    Apa pun formatnya, tiga kesalahan ini paling sering bikin katalog tidak menghasilkan apa-apa.

    • Katalog tanpa harga. Niatnya biar orang chat dulu, hasilnya malah sebaliknya. Sebagian besar orang malas bertanya dan memilih pindah ke penjual lain yang harganya jelas. Kalau harga memang bervariasi, tulis rentangnya atau harga mulai dari berapa.
    • Foto seadanya. Foto gelap, latar berantakan, atau ukuran tidak seragam membuat produk bagus terlihat murahan. Tidak perlu studio. Cukup cahaya jendela, latar polos, dan sudut pengambilan yang sama untuk semua produk.
    • Tidak ada cara order yang jelas. Pembeli sudah tertarik, lalu bingung harus ke mana. Pastikan setiap halaman katalog punya petunjuk order. Di PDF, cantumkan nomor WhatsApp di tiap halaman. Di katalog web, pasang tombol order yang langsung membuka chat dengan nama produk sudah terisi.

    Kapan katalog web naik kelas jadi toko online

    Katalog web tugasnya menampilkan produk dan mengarahkan pembeli untuk order lewat chat. Kalau orderanmu sudah ramai sampai kewalahan membalas satu per satu, atau pembeli mulai minta bisa bayar dan checkout sendiri, itu tanda katalogmu perlu naik kelas. Kami sudah membahas tanda-tandanya lebih lengkap di artikel kapan bisnis perlu upgrade dari katalog produk ke toko online.

    Untuk sekarang, mulai dari yang sesuai kondisimu. Produk sedikit dan harga stabil, pakai PDF atau katalog WA hari ini juga. Produk banyak, harga sering berubah, atau ingin dijangkau pembeli baru, pertimbangkan katalog web. Kalau butuh teman diskusi untuk menentukan bentuk katalog yang pas, tim Arrazy terbuka untuk ngobrol dulu tanpa komitmen apa pun.

  • Belajar Golang dari Nol #12: Koneksi Database dengan database/sql

    Belajar Golang dari Nol #12: Koneksi Database dengan database/sql

    Di Belajar Golang dari Nol #11 kita sudah membuat REST API produk yang berjalan. Handler-nya rapi, JSON-nya jalan, endpoint-nya lengkap. Tapi ada satu masalah besar yang sengaja saya tunda. Data produk disimpan di slice, alias di memory. Begitu server dimatikan atau restart, semua data hilang. Di bagian ini kita selesaikan masalah itu. Kita sambungkan API ke database sungguhan pakai package standar database/sql.

    Kenapa Harus Database

    Menyimpan data di slice itu cukup untuk belajar. Untuk aplikasi nyata, tidak. Ada tiga alasan utama kenapa kita butuh database.

    • Data bertahan setelah restart. Database menyimpan data ke file di disk, bukan ke RAM. Server mati, data tetap ada.
    • Data bisa dicari dan difilter. Mau cari produk dengan stok di bawah 5? Satu query selesai. Kalau pakai slice, Anda harus tulis loop sendiri setiap kali.
    • Aman diakses banyak proses. Database dirancang untuk menangani banyak koneksi sekaligus. Urusan lock dan konsistensi sudah diurus di level database.

    Di Go, akses database lewat package database/sql. Package ini menarik karena isinya hampir semuanya interface, materi yang sudah kita bahas di bagian 9. database/sql hanya mendefinisikan kontrak: cara buka koneksi, cara kirim query, cara baca hasil. Implementasi aslinya disediakan oleh driver, yaitu package terpisah yang tahu cara bicara dengan database tertentu. Satu kontrak, banyak implementasi. Kode Anda relatif sama mau databasenya apa pun.

    Di tutorial ini kita pakai SQLite lewat driver modernc.org/sqlite. Alasannya praktis. SQLite menyimpan seluruh database dalam satu file, jadi Anda tidak perlu install dan menjalankan server database apa pun. Polanya persis sama untuk MySQL atau PostgreSQL, tinggal ganti driver dan connection string-nya.

    Install Driver dan Buka Koneksi

    Masuk ke folder proyek API dari bagian 11, lalu install driver.

    go get modernc.org/sqlite

    Driver ini ditulis murni dalam Go, jadi tidak butuh compiler C. Sekarang buka koneksi di main.go.

    package main
    
    import (
    	"database/sql"
    	"log"
    
    	_ "modernc.org/sqlite"
    )
    
    var db *sql.DB
    
    func main() {
    	var err error
    	db, err = sql.Open("sqlite", "toko.db")
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer db.Close()
    
    	if err := db.Ping(); err != nil {
    		log.Fatal("gagal konek ke database:", err)
    	}
    
    	log.Println("database siap")
    }

    Ada dua hal yang perlu diperhatikan di sini. Pertama, import driver pakai underscore. Kita tidak memanggil fungsi apa pun dari package itu secara langsung. Import dengan underscore hanya menjalankan kode registrasinya, sehingga driver mendaftarkan diri ke database/sql dengan nama sqlite. Nama itulah yang kita pakai sebagai argumen pertama sql.Open.

    Kedua, sql.Open tidak langsung membuat koneksi. Fungsi ini hanya menyiapkan objek *sql.DB dan memvalidasi argumennya. Koneksi sebenarnya baru dibuat saat dibutuhkan. Karena itu kita panggil db.Ping() untuk memaksa satu koneksi terbentuk. Kalau file database tidak bisa dibuat atau konfigurasi salah, kita tahu sejak awal, bukan saat request pertama masuk.

    Membuat Tabel

    SQLite akan membuat file toko.db otomatis saat pertama diakses. Tapi tabelnya harus kita buat sendiri. Untuk perintah SQL yang tidak mengembalikan baris data, pakai db.Exec.

    func buatTabel() error {
    	_, err := db.Exec(`CREATE TABLE IF NOT EXISTS produk (
    		id INTEGER PRIMARY KEY AUTOINCREMENT,
    		nama TEXT,
    		harga REAL,
    		stok INTEGER
    	)`)
    	return err
    }

    Klausa IF NOT EXISTS membuat fungsi ini aman dipanggil berulang. Tabel hanya dibuat sekali, panggilan berikutnya tidak melakukan apa-apa. Kolom id memakai AUTOINCREMENT, jadi database yang mengurus penomoran. Anda tidak perlu lagi menghitung ID sendiri seperti di bagian 11. Panggil buatTabel() di main tepat setelah db.Ping() berhasil.

    INSERT dan Bahaya SQL Injection

    Menyimpan data juga pakai db.Exec. Perhatikan tanda tanya di dalam query.

    hasil, err := db.Exec(
    	"INSERT INTO produk (nama, harga, stok) VALUES (?, ?, ?)",
    	"Kopi Arabika", 45000, 10,
    )
    if err != nil {
    	log.Fatal(err)
    }
    
    id, _ := hasil.LastInsertId()
    log.Println("produk tersimpan dengan id", id)

    Tanda tanya itu namanya placeholder. Nilai aslinya dikirim terpisah dari teks query, lalu database yang menggabungkannya dengan aman. Ini bukan sekadar gaya penulisan. Ini keharusan.

    Bandingkan dengan cara yang salah: menggabungkan input user langsung ke string query.

    // JANGAN PERNAH seperti ini
    query := "SELECT * FROM produk WHERE nama = '" + input + "'"

    Kelihatannya jalan. Sampai ada user iseng yang mengirim input berisi ' OR '1'='1. Query yang sampai ke database berubah menjadi SELECT * FROM produk WHERE nama = '' OR '1'='1'. Kondisi '1'='1' selalu benar, jadi semua baris ikut terambil. Dengan input yang lebih jahat, penyerang bisa menghapus tabel atau membaca data user lain. Teknik ini namanya SQL injection, dan sampai sekarang masih jadi salah satu celah keamanan paling umum di dunia. Dengan placeholder, input tadi diperlakukan murni sebagai teks biasa, bukan bagian dari perintah SQL. Serangannya mati sebelum sempat jalan.

    Membaca Data: Query dan QueryRow

    Untuk mengambil banyak baris, pakai db.Query. Hasilnya berupa *sql.Rows yang kita telusuri satu per satu.

    func ambilSemuaProduk() ([]Produk, error) {
    	rows, err := db.Query("SELECT id, nama, harga, stok FROM produk")
    	if err != nil {
    		return nil, err
    	}
    	defer rows.Close()
    
    	var daftar []Produk
    	for rows.Next() {
    		var p Produk
    		if err := rows.Scan(&p.ID, &p.Nama, &p.Harga, &p.Stok); err != nil {
    			return nil, err
    		}
    		daftar = append(daftar, p)
    	}
    	return daftar, rows.Err()
    }

    Alurnya selalu sama. rows.Next() maju ke baris berikutnya dan mengembalikan false saat baris habis. rows.Scan menyalin nilai kolom ke variabel Anda, urutannya harus cocok dengan urutan kolom di SELECT. Dan defer rows.Close() wajib ada. Tanpa itu, koneksi database tertahan terus dan lama-lama aplikasi kehabisan koneksi. Terakhir, rows.Err() menangkap error yang mungkin terjadi di tengah iterasi.

    Untuk mengambil tepat satu baris, ada jalur yang lebih pendek: QueryRow.

    func ambilProdukByID(id int) (Produk, error) {
    	var p Produk
    	err := db.QueryRow(
    		"SELECT id, nama, harga, stok FROM produk WHERE id = ?", id,
    	).Scan(&p.ID, &p.Nama, &p.Harga, &p.Stok)
    	return p, err
    }

    Kalau tidak ada baris yang cocok, Scan mengembalikan error khusus bernama sql.ErrNoRows. Error ini bukan tanda ada yang rusak. Artinya cuma satu: datanya memang tidak ada. Nanti di handler, error inilah yang kita terjemahkan menjadi respons 404.

    UPDATE dan DELETE

    Keduanya memakai db.Exec, sama seperti INSERT. Yang menarik adalah cara mengecek apakah perintahnya benar-benar mengubah sesuatu.

    hasil, err := db.Exec(
    	"UPDATE produk SET stok = ? WHERE id = ?", 25, 3,
    )
    if err != nil {
    	log.Fatal(err)
    }
    
    jumlah, _ := hasil.RowsAffected()
    if jumlah == 0 {
    	log.Println("tidak ada produk dengan id itu")
    }

    UPDATE ke ID yang tidak ada itu bukan error di mata database. Query-nya valid, hanya saja tidak ada baris yang kena. Karena itu kita cek RowsAffected(). Nol artinya tidak ada yang berubah, dan di API biasanya itu diterjemahkan jadi 404. DELETE polanya persis sama.

    hasil, err := db.Exec("DELETE FROM produk WHERE id = ?", 3)

    Menyambungkan ke API Bagian 11

    Sekarang bagian yang ditunggu. Kita ganti penyimpanan slice di API bagian 11 dengan database. Tiga fungsi tadi, ambilSemuaProduk, ambilProdukByID, dan satu lagi simpanProduk di bawah ini, jadi satu-satunya tempat kode SQL berada.

    func simpanProduk(p Produk) (int64, error) {
    	hasil, err := db.Exec(
    		"INSERT INTO produk (nama, harga, stok) VALUES (?, ?, ?)",
    		p.Nama, p.Harga, p.Stok,
    	)
    	if err != nil {
    		return 0, err
    	}
    	return hasil.LastInsertId()
    }

    Memisahkan akses database ke fungsi sendiri seperti ini adalah kebiasaan yang layak dipelihara sejak awal. Handler jadi fokus mengurus HTTP: baca request, panggil fungsi data, tulis respons. Fungsi data fokus mengurus SQL. Kalau suatu hari pindah dari SQLite ke PostgreSQL, yang berubah hanya lapisan fungsi data, handler tidak tersentuh. Di dunia kerja pola ini punya nama sendiri, tapi intinya sesederhana itu: satu lapisan, satu tanggung jawab.

    Berikut tiga handler yang berubah. Routing, struct Produk, dan sisanya tetap sama seperti bagian 11, jadi tidak saya ulang.

    func handleDaftarProduk(w http.ResponseWriter, r *http.Request) {
    	daftar, err := ambilSemuaProduk()
    	if err != nil {
    		http.Error(w, "gagal membaca data", http.StatusInternalServerError)
    		return
    	}
    	w.Header().Set("Content-Type", "application/json")
    	json.NewEncoder(w).Encode(daftar)
    }
    func handleDetailProduk(w http.ResponseWriter, r *http.Request) {
    	id, err := strconv.Atoi(r.PathValue("id"))
    	if err != nil {
    		http.Error(w, "id tidak valid", http.StatusBadRequest)
    		return
    	}
    
    	p, err := ambilProdukByID(id)
    	if errors.Is(err, sql.ErrNoRows) {
    		http.Error(w, "produk tidak ditemukan", http.StatusNotFound)
    		return
    	}
    	if err != nil {
    		http.Error(w, "gagal membaca data", http.StatusInternalServerError)
    		return
    	}
    
    	w.Header().Set("Content-Type", "application/json")
    	json.NewEncoder(w).Encode(p)
    }

    Perhatikan urutan pengecekan error. sql.ErrNoRows dicek dulu pakai errors.Is, materi error dari bagian sebelumnya, lalu diterjemahkan jadi 404. Error lain berarti ada masalah di sisi server, jadi 500.

    func handleTambahProduk(w http.ResponseWriter, r *http.Request) {
    	var p Produk
    	if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
    		http.Error(w, "body tidak valid", http.StatusBadRequest)
    		return
    	}
    
    	id, err := simpanProduk(p)
    	if err != nil {
    		http.Error(w, "gagal menyimpan data", http.StatusInternalServerError)
    		return
    	}
    	p.ID = int(id)
    
    	w.Header().Set("Content-Type", "application/json")
    	w.WriteHeader(http.StatusCreated)
    	json.NewEncoder(w).Encode(p)
    }

    Ada bonus yang enak di refactor ini. Mutex dari bagian 11 boleh dihapus. Objek *sql.DB memang dirancang aman dipakai banyak goroutine sekaligus, lengkap dengan connection pool di dalamnya. Satu kerumitan hilang.

    Satu catatan kecil untuk nanti. Setiap fungsi di atas punya versi ber-context: QueryContext, ExecContext, dan kawan-kawannya. Context berguna untuk membatalkan query yang kelamaan, misalnya saat client keburu menutup koneksi. Kita bahas layak-layaknya di bagian lanjutan, sekarang cukup tahu dulu bahwa versi itu ada.

    Uji Coba: Momen Kemenangannya

    Jalankan server, lalu tambah produk lewat curl.

    go run .
    
    curl -X POST http://localhost:8080/produk \
      -H "Content-Type: application/json" \
      -d '{"nama":"Teh Melati","harga":12000,"stok":30}'

    Cek daftarnya.

    curl http://localhost:8080/produk

    Produk muncul. Sekarang bagian pentingnya. Matikan server dengan Ctrl+C. Di bagian 11, langkah ini menghapus semua data. Jalankan lagi go run ., lalu panggil endpoint daftar sekali lagi.

    curl http://localhost:8080/produk

    Datanya masih ada. Teh Melati tetap di sana, lengkap dengan ID-nya. Inilah bedanya API mainan dan API yang bisa dipakai sungguhan. Semua tersimpan di file toko.db, dan Anda bisa buka file itu kapan saja pakai tool SQLite mana pun.

    Rangkuman dan Bagian Selanjutnya

    Hari ini Anda belajar banyak hal penting. database/sql sebagai interface standar dan driver sebagai implementasinya. sql.Open yang ternyata tidak langsung konek, sehingga perlu Ping. Exec untuk perintah tulis, Query dan QueryRow untuk baca, Scan untuk menyalin hasil, RowsAffected untuk memastikan ada yang berubah. Plus satu aturan yang tidak boleh ditawar: selalu pakai placeholder, jangan pernah menyambung input user ke string SQL.

    API Anda sekarang punya ingatan permanen. Tapi siapa pun masih bisa menambah dan menghapus produk seenaknya. Itu masalah berikutnya. Di bagian 13 kita bahas Middleware dan Auth Sederhana untuk API.

    Kalau Anda sedang butuh aplikasi bisnis yang datanya rapi dan tidak hilang-hilangan, tim kami juga mengerjakan pengembangan sistem aplikasi dari desain database sampai API-nya.

  • Cara Kerja Internet: Perjalanan Paket Saat Membuka Website

    Cara Kerja Internet: Perjalanan Paket Saat Membuka Website

    Cara kerja internet paling mudah dipahami lewat satu skenario nyata: kamu mengetik alamat website di browser, menekan Enter, dan halaman muncul kurang dari satu detik kemudian. Di balik layar, data dipotong menjadi paket-paket kecil, dikirim dari laptop ke router rumah, diteruskan ke jaringan ISP, lalu melompat dari router ke router lintas kota dan lintas negara sampai tiba di server tujuan. Server membalas dengan paket berisi konten halaman, dan browser merakitnya kembali menjadi tampilan yang kamu lihat.

    Artikel ini bagian kedua dari seri Belajar Jaringan Komputer dari Nol. Kita tidak berhenti di teori. Kamu akan membuktikan sendiri rute perjalanan paket itu memakai dua perintah klasik di Linux: ping dan traceroute.

    Prasyarat Sebelum Praktik

    Kalau kamu belum tahu apa itu jaringan, IP, atau belum punya terminal Linux yang siap dipakai, baca dulu bagian pertama: Apa itu Jaringan Komputer + Setup Lab Praktik di Linux. Praktik di artikel ini memakai Ubuntu 24.04 dengan ping dari paket iputils (versi 20240117) dan traceroute versi 2.1.5. Distro lain juga bisa, perintahnya sama, hanya nomor versinya yang mungkin beda. Yang penting kamu punya koneksi internet aktif.

    Perjalanan Data Saat Membuka Website

    Anggap kamu membuka detik.com dari laptop yang terhubung WiFi rumah. Ini yang terjadi, urut dari awal:

    1. Browser mencari alamat server. Komputer tidak mengerti nama seperti detik.com. Nama itu harus diterjemahkan dulu menjadi alamat numerik (IP address). Proses penerjemahan ini namanya DNS. Kita bahas tuntas di bagian 11 seri ini, sekarang cukup tahu bahwa langkah ini terjadi paling awal.
    2. Data dipecah menjadi paket. Permintaan “kirim halaman utama” tidak dikirim sebagai satu bongkahan besar. Ia dipotong menjadi paket-paket kecil, masing-masing membawa alamat pengirim dan alamat tujuan, mirip amplop surat.
    3. Paket keluar lewat router rumah. Laptop mengirim paket lewat WiFi ke router di rumahmu. Router ini gerbang tunggal: semua perangkat di rumah menitipkan paketnya ke sini untuk diteruskan keluar.
    4. Router rumah meneruskan ke ISP. Dari rumah, paket berjalan lewat kabel fiber atau jaringan seluler menuju perangkat milik ISP (Indihome, Biznet, First Media, dan sebagainya). ISP punya jaringan router besar yang saling terhubung dengan ISP lain.
    5. Paket melompat antar router sampai ke server. Tidak ada kabel langsung dari rumahmu ke server detik.com. Paket berpindah tangan dari router ke router, kadang belasan kali. Setiap router membaca alamat tujuan lalu memutuskan lompatan berikutnya. Satu lompatan ini disebut hop. Kalau servernya di luar negeri, paket ikut lewat kabel bawah laut.
    6. Server membalas. Server menerima permintaan, menyiapkan konten halaman, memotongnya jadi paket, lalu mengirim balik lewat rute yang bisa jadi berbeda. Browser di laptopmu menyusun paket-paket itu kembali menjadi halaman utuh.

    Semua ini terjadi dalam hitungan puluhan sampai ratusan milidetik. Sekarang kita ukur.

    Praktik ping: Mengukur Latency, Packet Loss, dan TTL

    ping mengirim paket kecil ke tujuan dan menghitung berapa lama balasannya kembali. Jalankan ini di terminal:

    ping -c 5 detik.com

    Opsi -c 5 artinya kirim 5 paket lalu berhenti. Tanpa opsi ini, ping jalan terus sampai kamu tekan Ctrl+C. Contoh output:

    PING detik.com (203.190.242.69) 56(84) bytes of data.
    64 bytes from 203.190.242.69: icmp_seq=1 ttl=55 time=21.4 ms
    64 bytes from 203.190.242.69: icmp_seq=2 ttl=55 time=20.8 ms
    64 bytes from 203.190.242.69: icmp_seq=3 ttl=55 time=23.1 ms
    64 bytes from 203.190.242.69: icmp_seq=4 ttl=55 time=21.0 ms
    64 bytes from 203.190.242.69: icmp_seq=5 ttl=55 time=22.6 ms
    
    --- detik.com ping statistics ---
    5 packets transmitted, 5 received, 0% packet loss, time 4006ms
    rtt min/avg/max/mdev = 20.812/21.780/23.104/0.902 ms

    Angka di outputmu pasti berbeda, tergantung ISP dan lokasi. Yang penting kamu bisa membaca tiga hal ini:

    Membaca latency (time dalam ms)

    time=21.4 ms artinya paket butuh 21,4 milidetik untuk pergi ke server dan kembali lagi. Ini disebut round trip time, dan istilah sehari-harinya latency. Patokan kasar: di bawah 30 ms terasa instan, 50 sampai 100 ms masih nyaman untuk browsing, di atas 200 ms mulai terasa lambat, terutama untuk video call dan game online. Server di Indonesia biasanya memberi latency lebih kecil daripada server di Amerika atau Eropa, karena jarak fisiknya memang lebih dekat.

    Membaca packet loss

    Baris 0% packet loss artinya semua paket yang dikirim mendapat balasan. Kalau angkanya 20%, berarti 1 dari 5 paket hilang di jalan. Packet loss yang konsisten di atas 1 sampai 2% biasanya menandakan masalah: sinyal WiFi jelek, kabel bermasalah, atau jaringan ISP sedang padat. Gejalanya di dunia nyata: video call patah-patah dan halaman kadang gagal dimuat.

    Membaca TTL

    ttl=55 singkatan dari Time To Live. Setiap paket lahir dengan nilai TTL awal, umumnya 64 di server Linux dan 128 di Windows. Setiap kali paket melewati satu router, nilainya dikurangi satu. Kalau TTL sampai nol, paket dibuang. Ini mencegah paket nyasar berputar-putar selamanya di internet. Dari TTL kamu bisa menebak jumlah hop: nilai 55 dengan asumsi awal 64 berarti paket melewati sekitar 9 router untuk sampai ke kamu.

    Kebiasaan cek latency ini kepakai terus di kerjaan nyata. Saat sistem aplikasi yang tim Arrazy bangun untuk klien terasa lambat, ping dan traceroute selalu jadi pemeriksaan pertama. Sering kali masalahnya di jaringan atau lokasi server, bukan di kode backend-nya.

    Praktik traceroute: Melihat Rute Hop demi Hop

    Kalau ping hanya memberi tahu total waktu tempuh, traceroute membongkar rutenya: router mana saja yang dilewati paket. Install dulu karena Ubuntu tidak menyertakannya secara default:

    sudo apt update
    sudo apt install traceroute

    Lalu coba ke situs dalam negeri:

    traceroute detik.com

    Contoh output (dipersingkat):

    traceroute to detik.com (203.190.242.69), 30 hops max, 60 byte packets
     1  _gateway (192.168.1.1)  1.213 ms  1.105 ms  1.087 ms
     2  180.252.x.x (180.252.x.x)  4.891 ms  4.702 ms  4.655 ms
     3  * * *
     4  180.240.204.75 (180.240.204.75)  12.480 ms  12.331 ms  12.279 ms
     5  180.240.191.14 (180.240.191.14)  15.702 ms  14.988 ms  15.213 ms
     6  203.190.242.69 (203.190.242.69)  21.145 ms  20.897 ms  21.033 ms

    Cara membacanya baris per baris:

    • Hop 1 selalu router rumahmu sendiri. Alamat 192.168.1.1 adalah alamat lokal yang umum dipakai router. Waktunya kecil, sekitar 1 ms, karena jaraknya cuma beberapa meter.
    • Hop 2 sampai beberapa hop berikutnya adalah jaringan ISP: perangkat di kompleksmu, lalu router agregasi di kota, lalu backbone antar kota.
    • Hop terakhir adalah server tujuan. Tiga angka waktu di tiap baris muncul karena traceroute mengetes tiap hop tiga kali.

    Sekarang bandingkan dengan situs yang servernya di luar negeri:

    traceroute one.one.one.one
    traceroute github.com

    Perhatikan dua hal. Pertama, one.one.one.one (layanan DNS Cloudflare) biasanya selesai dalam sedikit hop dengan latency kecil, karena Cloudflare menaruh server di Jakarta. Kedua, github.com biasanya butuh hop lebih banyak dengan lonjakan latency yang jelas di tengah rute, misalnya dari belasan ms tiba-tiba jadi 150 sampai 200 ms. Lonjakan itu momen paket menyeberangi kabel bawah laut ke Singapura lalu ke Amerika. Kamu sedang melihat geografi bumi lewat terminal.

    Bandwidth vs Latency vs Throughput: Analogi Jalan Tol

    Tiga istilah ini sering tertukar padahal artinya beda. Bayangkan jalan tol antar kota:

    • Bandwidth = jumlah lajur tol. Tol 8 lajur bisa menampung lebih banyak mobil per jam daripada tol 2 lajur. Bandwidth adalah kapasitas maksimum, diukur dalam Mbps.
    • Latency = waktu tempuh satu mobil dari gerbang masuk ke gerbang keluar. Mau lajurnya 100 pun, kalau jaraknya 500 km ya tetap butuh berjam-jam. Diukur dalam ms.
    • Throughput = jumlah mobil yang benar-benar sampai tujuan per jam. Ini angka nyata setelah kena macet, kecelakaan, dan gerbang tol yang antre. Throughput selalu lebih kecil atau sama dengan bandwidth.
    Istilah Analogi tol Satuan Cara mengukur
    Bandwidth Jumlah lajur Mbps Paket langganan ISP
    Latency Waktu tempuh ms ping
    Throughput Mobil yang sampai per jam Mbps Speedtest, kecepatan download nyata

    Ini menjelaskan kenapa langganan internet 100 Mbps tetap terasa lambat saat main game dengan server di luar negeri. Game online butuh latency kecil, bukan bandwidth besar, karena data yang dikirim per detik sebenarnya kecil tapi harus bolak-balik cepat. Sebaliknya, download file besar butuh bandwidth, dan latency 200 ms hampir tidak terasa.

    Troubleshooting: Error yang Sering Dialami Pemula

    traceroute menampilkan * * * di beberapa hop

    Baris seperti 3 * * * bukan berarti jaringan putus. Artinya router di hop itu memilih tidak membalas paket pemeriksaan (ICMP), biasanya karena alasan keamanan atau karena membalas ICMP bukan prioritasnya. Selama hop setelahnya tetap muncul dan hop terakhir tercapai, rute kamu sehat. Kalau semua hop setelah titik tertentu jadi * * * sampai habis 30 hop, baru itu tanda paket diblokir atau tujuan tidak membalas. Coba mode lain: sudo traceroute -I detik.com (pakai ICMP echo seperti ping) atau sudo traceroute -T detik.com (pakai TCP, lebih jarang diblokir).

    ping 100% packet loss padahal website-nya bisa dibuka

    Banyak server sengaja memblokir ping. Contoh yang terkenal, ping microsoft.com tidak pernah dibalas padahal situsnya jelas hidup. Jadi ping gagal tidak otomatis berarti server mati. Verifikasi dengan cara lain, misalnya curl -I https://www.microsoft.com. Kalau curl mendapat respons berisi HTTP/2 200, server hidup dan hanya menutup ICMP.

    ping: detik.com: Temporary failure in name resolution

    Komputer gagal menerjemahkan nama ke alamat IP. Dua kemungkinan: koneksi internetmu memang putus, atau pengaturan DNS bermasalah. Tes dengan ping -c 3 ke 1.1.1.1. Kalau ping ke angka berhasil tapi ke nama gagal, masalahnya di DNS, bukan koneksi. Solusi cepat: restart koneksi, atau ganti DNS di pengaturan jaringan ke 1.1.1.1.

    traceroute: command not found

    Ubuntu dan Debian tidak memasang traceroute secara bawaan. Install dengan sudo apt install traceroute. Alternatifnya, tracepath detik.com sudah tersedia tanpa install dan fungsinya mirip, hanya opsinya lebih sedikit.

    Lanjut ke Bagian Berikutnya

    Kamu sekarang paham rute yang ditempuh paket dari laptop sampai server, dan bisa mengukurnya sendiri dengan ping dan traceroute. Pertanyaan berikutnya wajar muncul: perangkat apa saja sebenarnya yang dilewati paket itu, dan apa beda switch dengan router? Itu materi bagian ketiga, “Perangkat Jaringan Komputer: Switch, Router, dan Topologi”, yang terbit menyusul. Pantau daftar lengkapnya di halaman hub seri Belajar Jaringan Komputer dari Nol.

    Referensi

  • Cara Mengumpulkan Testimoni Pelanggan Jadi Aset Jualan

    Cara Mengumpulkan Testimoni Pelanggan Jadi Aset Jualan

    Testimoni pelanggan paling sering berakhir di satu tempat: galeri HP. Di-screenshot, disimpan, lalu dilupakan. Padahal testimoni itu salah satu alat jualan paling murah yang bisa dimiliki usaha kecil. Cara mengubahnya jadi aset sebenarnya sederhana. Minta testimoni tepat di momen pelanggan sedang puas, pancing dengan pertanyaan yang spesifik, minta izin sebelum dipajang, lalu taruh di tempat yang benar-benar dilihat calon pembeli, terutama di dekat tombol order atau kontak.

    Artikel ini membahas langkahnya satu per satu. Mulai dari kapan waktu terbaik meminta, contoh kalimat yang tidak bikin canggung, bedanya testimoni yang menjual dan yang hambar, sampai cara mengarsipnya supaya tidak hilang ditelan tumpukan chat.

    Kenapa Testimoni di Galeri HP Itu Aset Nganggur

    Coba buka galeri HP Anda sekarang. Kemungkinan besar ada beberapa screenshot chat pelanggan yang bilang puas, produknya bagus, atau layanannya membantu. Pertanyaannya: siapa yang pernah melihat screenshot itu selain Anda?

    Testimoni hanya bekerja kalau dilihat oleh orang yang sedang ragu. Calon pembeli yang belum kenal usaha Anda tidak akan percaya klaim “produk kami berkualitas” begitu saja. Tapi mereka percaya omongan sesama pembeli. Itu sebabnya orang scroll ulasan dulu sebelum checkout di marketplace, dan baca review Google dulu sebelum datang ke tempat makan baru.

    Screenshot yang cuma tersimpan di galeri tidak pernah sampai ke mata orang yang butuh melihatnya. Sama seperti punya stok barang laris tapi disimpan di gudang dan tidak pernah dipajang di etalase. Barangnya ada, nilainya nol.

    Kapan Waktu Terbaik Minta Testimoni

    Waktu terbaik meminta testimoni adalah tepat setelah pelanggan merasakan hasilnya, saat rasa puasnya masih hangat. Bukan tiga bulan kemudian saat Anda tiba-tiba ingat butuh materi promosi.

    Kalau telat, dua hal terjadi. Pertama, detailnya memudar. Pelanggan sudah lupa masalah awalnya apa dan apa yang berubah, jadi jawabannya generik. Kedua, permintaannya terasa aneh. Chat sudah lama sepi, tiba-tiba Anda muncul minta testimoni. Pelanggan bisa merasa cuma dimanfaatkan.

    Masalahnya, banyak pemilik usaha tidak sadar kapan momen puas itu datang. Padahal tandanya cukup jelas:

    • Pelanggan mengucapkan terima kasih lebih dari sekadar basa-basi, misalnya sampai cerita panjang.
    • Pelanggan mengirim foto atau video produk yang sudah dipakai tanpa diminta.
    • Pelanggan repeat order, apalagi dengan jumlah lebih besar.
    • Pelanggan bilang sudah merekomendasikan ke teman atau keluarganya.
    • Pelanggan bertanya soal produk atau layanan lain yang Anda punya.

    Begitu salah satu tanda ini muncul, itu lampu hijau. Balas dulu dengan tulus, baru selipkan permintaan testimoni di chat berikutnya. Jangan tunggu minggu depan.

    Cara Minta Testimoni yang Tidak Canggung

    Banyak pemilik usaha malu meminta testimoni karena takut terkesan memaksa. Padahal pelanggan yang puas umumnya senang membantu, asal caranya enak dan tidak merepotkan.

    Berikut tiga contoh kalimat yang bisa langsung dipakai di chat:

    • “Kak, seneng banget dengar hasilnya cocok. Boleh minta tolong satu hal? Ceritain singkat aja pengalaman kakak, nanti saya pajang buat bantu calon pembeli lain yang masih ragu.”
    • “Makasih banyak kak udah order lagi. Kalau berkenan, boleh tulis dua tiga kalimat soal pengalaman kakak selama ini? Jawaban kakak bakal sangat membantu usaha kecil kami.”
    • “Kak, boleh tanya-tanya sebentar? Dulu sebelum pakai ini kendalanya apa, terus sekarang gimana? Jawabannya mau saya jadikan testimoni kalau kakak izinkan.”

    Perhatikan pola ketiganya. Ada apresiasi dulu, permintaannya ringan, dan ada alasan kenapa testimoninya berguna. Orang lebih mudah membantu kalau tahu bantuannya berarti.

    Trik pertanyaan pancingan

    Ini bagian yang paling sering dilewatkan. Kalau Anda bertanya “gimana kak, bagus nggak?”, jawabannya hampir pasti “bagus kok kak”. Sopan, tapi tidak bisa dipakai jualan.

    Ganti dengan pertanyaan yang memancing cerita:

    • “Apa yang berubah setelah pakai produk ini?”
    • “Dulu sebelum pesan di sini, kendala paling nyebelin apa?”
    • “Bagian mana yang paling kerasa manfaatnya?”
    • “Kalau ada teman yang ragu mau order, kakak bakal bilang apa ke dia?”

    Pertanyaan seperti ini memaksa pelanggan mengingat kondisi sebelum dan sesudah. Dari situlah lahir testimoni yang punya cerita, bukan sekadar pujian satu kata.

    Testimoni yang Menjual vs yang Hambar

    Tidak semua testimoni bernilai sama. Bandingkan dua contoh ini.

    Testimoni hambar: “Pelayanannya bagus, recommended seller.”

    Testimoni yang menjual: “Awalnya saya ragu karena pernah kecewa pesan di tempat lain, hasilnya asal jadi. Di sini prosesnya jelas, saya dikabari tiap tahap, dan hasil akhirnya rapi. Yang biasanya saya kerjakan manual berjam-jam sekarang selesai jauh lebih cepat.”

    Keduanya sama-sama positif. Tapi yang kedua jauh lebih dipercaya, dan alasannya masuk akal. Testimoni spesifik menyebut masalah awal, proses, dan hasil akhir. Calon pembeli yang punya masalah serupa akan merasa “ini kayak saya”. Sementara “pelayanannya bagus” bisa ditulis siapa saja, bahkan oleh penjual sendiri, jadi otak pembaca otomatis menganggapnya angin lalu.

    Semakin spesifik detailnya, semakin sulit dipalsukan, dan pembaca tahu itu. Makanya pertanyaan pancingan di atas penting. Anda tidak bisa mengontrol isi testimoni, tapi Anda bisa mengarahkan pelanggan untuk bercerita, bukan sekadar memuji.

    Etika: Minta Izin Dulu, Jangan Pernah Mengarang

    Sebelum memajang testimoni, selalu minta izin. Chat pelanggan itu percakapan pribadi. Memajang nama, foto profil, atau isi chat tanpa persetujuan bisa membuat pelanggan tidak nyaman, dan Anda kehilangan kepercayaan justru dari orang yang paling puas.

    Cara mintanya singkat saja: “Kak, boleh saya pajang testimoninya di website dan Instagram? Mau pakai nama lengkap, inisial, atau tanpa nama?” Beri opsi inisial atau anonim untuk pelanggan yang tidak mau tampil. Testimoni berinisial tetap jauh lebih baik daripada tidak ada sama sekali.

    Satu lagi yang tidak bisa ditawar: jangan pernah mengarang testimoni. Godaannya memang ada, apalagi saat usaha masih baru dan testimoni asli belum banyak. Tapi risikonya tidak sebanding. Testimoni palsu biasanya terbaca dari gayanya yang seragam dan terlalu mulus. Sekali ada yang curiga lalu membuktikannya, cerita itu menyebar lebih cepat daripada promosi mana pun. Reputasi yang dibangun bertahun-tahun bisa habis oleh satu screenshot karangan. Lebih baik punya tiga testimoni asli yang jujur daripada tiga puluh yang dibuat-buat.

    Di Mana Memajang Testimoni Biar Kerja Keras

    Testimoni yang sudah dikumpulkan harus ditaruh di tempat yang dilalui calon pembeli saat mereka sedang menimbang. Ini empat tempat yang paling efektif.

    Website, di dekat tombol CTA

    Kesalahan umum: membuat halaman “Testimoni” terpisah yang jarang dibuka orang. Tempat terbaik testimoni justru di dekat titik keputusan, misalnya tepat di atas tombol “Hubungi Kami” atau form pemesanan. Saat calon pembeli hampir mengambil keputusan, keraguan terakhirnya dijawab oleh suara pelanggan lain. Kalau website Anda belum menampung testimoni dengan layak, atau malah belum punya website sama sekali, tim jasa pembuatan website Arrazy bisa bantu merancang penempatannya sekalian.

    Google Business Profile

    Saat momen puas datang, sekalian minta review Google, bukan cuma testimoni chat. Bedanya penting. Testimoni chat itu materi yang Anda kurasi dan pajang sendiri, sedangkan review Google tampil publik di hasil pencarian dan tidak bisa Anda edit, sehingga bobot kepercayaannya lebih tinggi di mata orang asing. Kirim saja tautan langsung ke halaman review supaya pelanggan tinggal klik.

    Highlight Instagram

    Buat satu highlight khusus berisi kumpulan testimoni, beri nama yang jelas seperti “Testimoni” atau “Kata Mereka”. Orang yang baru menemukan akun Anda hampir selalu mengecek highlight sebelum memutuskan DM. Perbarui isinya secara berkala supaya terlihat usaha Anda memang hidup.

    Katalog dan proposal penawaran

    Selipkan satu dua testimoni terkuat di katalog produk atau proposal yang Anda kirim ke calon klien. Testimoni juga senjata yang bagus saat menghubungi kembali calon pembeli yang sempat tanya harga lalu menghilang. Daripada sekadar menanyakan “jadi order kak?”, kirimkan cerita pelanggan yang dulunya ragu juga. Cara lengkapnya pernah kami bahas di artikel cara follow up pelanggan yang tanya harga lalu hilang.

    Cara Mengarsip Testimoni Biar Tidak Hilang

    Terakhir, rapikan sistem penyimpanannya. Tanpa arsip, testimoni terbaik Anda tenggelam di chat dan hilang saat ganti HP.

    Tidak perlu rumit. Dua langkah ini cukup:

    • Buat folder atau album khusus “Testimoni” di HP, atau pakai fitur label dan pesan berbintang di WhatsApp. Begitu ada chat bagus, langsung simpan saat itu juga.
    • Buat satu spreadsheet sederhana dengan kolom: nama pelanggan, tanggal, isi testimoni, produk atau layanan yang dibeli, status izin (boleh nama lengkap, inisial, atau anonim), dan di mana saja sudah dipajang.

    Kolom izin itu penting. Enam bulan lagi Anda tidak akan ingat siapa yang mengizinkan namanya dipajang dan siapa yang minta inisial. Spreadsheet ini juga memudahkan saat butuh testimoni spesifik, misalnya mencari cerita pelanggan untuk produk tertentu ketika menyusun proposal.

    Mulailah dari yang sudah ada. Sisir chat WA tiga bulan terakhir, kumpulkan pujian yang pernah masuk, lalu hubungi pelanggannya untuk minta izin memajang. Setelah itu jadikan kebiasaan: setiap ada tanda pelanggan puas, minta testimoninya hari itu juga. Dalam beberapa bulan, Anda punya stok bukti sosial yang terus bekerja menjual, bahkan saat Anda sedang tidur.

  • Aplikasi Pesanan Catering: Kalender Produksi, DP, dan Menu

    Aplikasi Pesanan Catering: Kalender Produksi, DP, dan Menu

    Aplikasi pesanan catering pada dasarnya menjawab tiga masalah sekaligus. Pertama, kalender pesanan yang memperlihatkan tanggal mana saja yang sudah terisi, jadi dua pesanan besar tidak jatuh di hari yang sama dan kapasitas dapur tetap terjaga. Kedua, pencatatan DP dan pelunasan yang menempel di setiap pesanan, jadi jelas siapa sudah bayar berapa dan sisa tagihannya tinggal berapa. Ketiga, rincian menu dan jumlah porsi yang tersimpan rapi di satu tempat, bukan tersebar di chat yang isinya bisa berubah kapan saja.

    Kalau tiga hal itu beres, urusan lain ikut rapi. Daftar belanja bahan bisa ditarik langsung dari rincian menu. Pengingat pelunasan jalan sendiri beberapa hari sebelum acara. Omzet bulanan tinggal dibuka, bukan dihitung ulang dari buku tulis. Artikel ini membahas cara kerja sistem seperti itu, dari pesanan masuk sampai makanan dikirim. Termasuk jawaban jujur soal kapan usaha catering sebenarnya belum perlu aplikasi.

    Masalah yang hampir semua pemilik catering pernah alami

    Ceritanya biasanya mirip. Pesanan masuk lewat WhatsApp, dicatat di buku. Beberapa hari kemudian pelanggan telepon, minta ganti ayam bakar jadi ayam goreng dan tambah 20 porsi. Perubahan itu cuma diingat karena sedang sibuk di dapur. Pas hari H, yang dimasak tetap menu lama dengan jumlah lama.

    Selama pesanan masih dua tiga per minggu, cara ini masih jalan. Begitu pesanan mulai ramai, retaknya kelihatan satu per satu:

    • Pesanan tercatat di tiga tempat berbeda: buku, chat WhatsApp, dan ingatan. Tidak ada satu sumber yang bisa dipegang.
    • Dua pesanan besar ternyata jatuh di tanggal yang sama. Baru ketahuan seminggu sebelum acara, saat sudah terlanjur terima DP dua-duanya.
    • DP diterima lewat transfer tapi lupa dicatat. Saat pelanggan bilang sudah bayar, harus scroll mutasi rekening sambil menebak ini transfer yang mana.
    • Menu berubah lewat telepon dan tidak pernah ditulis ulang. Versi terakhir yang disepakati tidak ada bentuk fisiknya.
    • Belanja bahan mengandalkan perkiraan. Kadang sisa banyak, kadang kurang dan harus lari ke pasar pagi-pagi.

    Tidak ada satu pun dari masalah ini yang soal rasa masakan. Semuanya soal pencatatan. Dan pencatatan adalah hal yang paling mudah dibereskan dengan sistem.

    Alur kerja dengan sistem, dari pesanan masuk sampai kirim

    Sistem pesanan catering yang baik tidak mengubah cara Anda berjualan. Pelanggan tetap menghubungi lewat WhatsApp atau telepon seperti biasa. Yang berubah adalah apa yang terjadi setelah obrolan itu selesai:

    • Pesanan masuk. Admin membuat entri pesanan baru: nama pelanggan, tanggal acara, alamat kirim, rincian menu, jumlah porsi, dan harga yang disepakati. Sekali ketik, jadi acuan semua orang.
    • Cek kalender. Sebelum menyanggupi tanggal, buka kalender dulu. Kalau tanggal itu sudah penuh, kelihatan saat itu juga. Bukan seminggu sebelum acara.
    • Catat DP. Begitu DP masuk, dicatat menempel di pesanan itu. Status pesanan otomatis berubah dari tanya-tanya jadi terkonfirmasi.
    • Perubahan menu. Pelanggan minta ganti lauk atau tambah porsi, admin mengubah rincian di sistem. Versi terbaru selalu satu, dan itu yang dipakai dapur.
    • Persiapan produksi. H-2 atau H-3, tim dapur membuka rincian pesanan untuk menyusun belanja bahan dan pembagian kerja.
    • Pelunasan dan kirim. Sisa tagihan tertera jelas. Setelah lunas dan makanan terkirim, pesanan ditutup dan angkanya masuk ke rekap bulanan.

    Perhatikan satu hal. Setiap informasi hanya ditulis sekali, lalu dipakai berulang: oleh admin, oleh dapur, oleh yang pegang uang. Itu inti dari satu tempat untuk semua.

    Kalender produksi: bukan sekadar tanggal, tapi kapasitas

    Kalender di sistem catering beda dengan kalender biasa. Yang dilihat bukan cuma tanggal kosong atau terisi, tapi berapa total porsi yang sudah disanggupi di hari itu.

    Misalnya dapur Anda sanggup masak maksimal 500 porsi sehari dengan tim yang ada. Tanggal 14 sudah ada pesanan 300 porsi. Saat ada calon pelanggan minta 250 porsi di tanggal yang sama, sistem langsung menunjukkan sisa kapasitas tinggal 200. Anda bisa memutuskan dengan sadar: tolak, tawar geser tanggal, atau tambah tenaga khusus hari itu.

    Keputusan seperti ini yang selama ini diambil dengan perasaan. Padahal taruhannya besar. Menyanggupi pesanan melebihi kapasitas artinya masakan terlambat atau kualitas turun, dan dua-duanya merusak nama catering lebih cepat daripada apa pun.

    Batas kapasitas per hari ini sebaiknya bisa diatur sendiri. Musim ramai seperti bulan haji atau akhir tahun, kapasitas bisa dinaikkan karena ada tenaga tambahan. Hari biasa, diturunkan lagi.

    Dari rincian menu ke daftar belanja bahan

    Ini bagian yang paling terasa manfaatnya di dapur. Kalau setiap menu di sistem punya daftar bahan bakunya, misalnya satu porsi ayam bakar butuh sekian gram ayam, bumbu, dan minyak, maka daftar belanja untuk satu hari produksi bisa dihitung otomatis dari semua pesanan di tanggal itu.

    Hasilnya bukan perkiraan lagi. Untuk hari Sabtu ada dua pesanan, totalnya butuh 45 kilogram ayam, 30 kilogram beras, dan seterusnya. Belanja jadi pas, sisa bahan berkurang, dan modal tidak menguap di bahan yang akhirnya terbuang.

    Tidak semua catering perlu sampai sedetail gramasi. Versi sederhananya pun sudah menolong: daftar bahan per menu tanpa takaran persis, sekadar supaya tidak ada yang terlewat saat belanja. Tingkat detailnya bisa disesuaikan dengan cara kerja dapur masing-masing.

    Pengingat pelunasan sebelum hari H

    DP yang tercatat rapi membuka satu kemampuan lagi: pengingat pelunasan. Sistem tahu pesanan mana yang belum lunas dan kapan acaranya. Jadi H-3 atau H-5, muncul daftar pesanan yang perlu ditagih, lengkap dengan sisa tagihannya. Menagih jadi enteng karena angkanya jelas, bukan hasil mengingat-ingat.

    Pencatatan DP dan pelunasan ini sebenarnya satu keluarga dengan pembukuan usaha. Kalau selama ini uang masuk dan keluar masih campur aduk, mulai dulu dari yang dasar. Kami pernah menulis soal laporan keuangan sederhana untuk usaha kecil tanpa akuntan yang bisa jadi titik berangkat.

    Laporan sederhana yang benar-benar dipakai

    Laporan di sistem catering tidak perlu rumit. Tiga angka saja sudah mengubah cara mengambil keputusan: omzet per bulan, jumlah pesanan per bulan, dan menu apa yang paling sering dipesan. Dari situ kelihatan bulan mana yang sepi dan perlu promo, menu mana yang layak dijadikan andalan, dan menu mana yang sebenarnya jarang laku tapi bikin repot dapur.

    Kapan buku catatan masih cukup

    Jujur saja, tidak semua catering butuh aplikasi hari ini. Kalau pesanan masih di bawah lima per bulan, satu orang yang pegang semua urusan, dan belum pernah ada kejadian tanggal bentrok atau DP tercecer, buku tulis dan WhatsApp masih sangat bisa jalan. Uang untuk sistem lebih baik dipakai memperkuat dapur atau pemasaran dulu.

    Tanda bahwa Anda mulai butuh sistem biasanya muncul sendiri. Pesanan mulai dobel di tanggal yang sama. Ada lebih dari satu orang yang menerima pesanan, jadi catatan terpecah. Anda mulai takut buka chat lama karena tidak yakin kesepakatan terakhirnya yang mana. Atau di akhir bulan Anda tidak bisa menjawab pertanyaan sederhana: bulan ini untung berapa. Kalau dua atau tiga tanda itu sudah terasa, saatnya berbenah.

    Pertanyaan yang sering muncul

    Apakah sistem seperti ini bisa dipakai di HP saja?

    Bisa, dan sebaiknya memang begitu. Pemilik catering jarang duduk di depan laptop. Sistem berbasis web yang tampilannya nyaman di HP biasanya cukup: cek kalender sambil belanja di pasar, catat DP begitu notifikasi transfer masuk, buka rincian menu dari dapur. Laptop tetap berguna untuk urusan yang lebih panjang seperti menyusun menu baru atau melihat laporan, tapi operasional harian cukup dari genggaman.

    Bagaimana dengan pesanan mendadak?

    Pesanan H-1 atau bahkan hari yang sama justru alasan kuat pakai kalender kapasitas. Tanpa sistem, keputusan menerima pesanan mendadak itu tebak-tebakan. Dengan sistem, Anda buka kalender, lihat sisa kapasitas hari itu, lalu jawab dalam hitungan detik: sanggup atau tidak. Kalau sanggup, pesanan langsung dicatat seperti biasa dan dapur langsung tahu ada tambahan. Justru pesanan mendadak paling berbahaya kalau hanya diingat.

    Berapa kisaran biaya membuat sistem seperti ini?

    Tergantung kedalaman fiturnya. Versi inti yang berisi kalender pesanan, pencatatan DP, dan rincian menu jelas lebih terjangkau dibanding versi lengkap dengan hitungan bahan baku otomatis, banyak pengguna, dan laporan mendalam. Cara paling masuk akal adalah mulai dari fitur yang menyelesaikan masalah paling mahal dulu, biasanya kalender dan DP, lalu menambah fitur lain setelah terasa manfaatnya. Gambaran umum soal cara kami membangun sistem aplikasi custom bisa dibaca di halaman layanan kami, termasuk kenapa sistem yang dibuat mengikuti alur kerja usaha Anda hampir selalu lebih awet dipakai daripada aplikasi jadi yang memaksa Anda mengubah kebiasaan.

    Mulai dari masalah yang paling sering bikin rugi

    Tidak perlu langsung membayangkan sistem besar. Tanyakan dulu ke diri sendiri: kejadian apa yang paling sering bikin rugi atau bikin malu ke pelanggan. Tanggal bentrok, DP tercecer, atau menu yang salah masak. Sistem yang baik dibangun mulai dari titik itu.

    Kalau mau mengobrol dulu soal alur pesanan di catering Anda, hubungi kami lewat halaman kontak. Ceritakan saja cara kerja sekarang apa adanya. Dari situ biasanya sudah kelihatan bagian mana yang paling layak dibereskan lebih dulu.