Blog

  • Belajar Golang dari Nol #11: REST API Pertamamu dengan net/http

    Belajar Golang dari Nol #11: REST API Pertamamu dengan net/http

    Di Belajar Golang dari Nol #10 kamu sudah kenal goroutine dan channel. Materi itu terasa abstrak buat sebagian orang. Hari ini abstraksi itu langsung kepakai. Kita akan membuat REST API pertamamu dengan package bawaan Go, yaitu net/http. Tanpa framework, tanpa library tambahan. Di akhir artikel, kamu punya API produk mini yang bisa diakses lewat curl, browser, atau aplikasi frontend.

    Apa itu REST API

    Bayangkan kamu bikin aplikasi kasir. Ada aplikasi Android untuk pelayan, ada dashboard web untuk pemilik toko. Dua aplikasi itu butuh data yang sama: daftar produk, harga, stok. Data itu tidak disimpan di HP pelayan atau di browser pemilik. Data itu ada di satu tempat, yaitu backend. Nah, REST API adalah cara aplikasi lain ngobrol dengan backendmu. Mereka kirim permintaan lewat HTTP, backendmu balas dengan data dalam format JSON. Frontend minta “kasih daftar produk”, backend jawab dengan array JSON berisi produk. Sesederhana itu konsepnya.

    Kenapa ini penting buat kamu yang lagi belajar Go? Karena hampir semua lowongan backend developer intinya ya ini: membuat dan merawat API. Login, checkout, notifikasi, laporan, semuanya jalan lewat API. Kalau kamu paham cara menerima request, memproses data, dan mengembalikan JSON dengan status code yang benar, kamu sudah pegang gerbang masuk dunia kerja backend. Sisanya tinggal memperdalam.

    Server HTTP Pertamamu

    Kita mulai dari yang paling kecil. Buat folder baru, jalankan go mod init belajar-api, lalu buat file main.go:

    package main
    
    import (
    	"fmt"
    	"net/http"
    )
    
    func main() {
    	http.HandleFunc("GET /produk", func(w http.ResponseWriter, r *http.Request) {
    		fmt.Fprintln(w, "daftar produk")
    	})
    
    	fmt.Println("Server jalan di http://localhost:8080")
    	http.ListenAndServe(":8080", nil)
    }
    

    Ada dua pemain utama di sini. Pertama, http.HandleFunc mendaftarkan handler, yaitu function yang dipanggil setiap ada request masuk ke pola tertentu. Pola "GET /produk" artinya handler ini hanya melayani request GET ke path /produk. Kedua, http.ListenAndServe(":8080", nil) menyalakan server di port 8080 dan membuatnya menunggu request terus menerus. Program tidak akan berhenti sendiri, dan memang itu yang kita mau.

    Setiap handler menerima dua parameter. w http.ResponseWriter adalah tempat kamu menulis balasan. r *http.Request berisi semua detail request yang masuk: method, path, header, dan body. Perhatikan juga, r di sini pointer. Materi pointer di bagian 7 kepakai lagi.

    Jalankan dengan go run main.go, lalu buka terminal kedua dan tes dengan curl:

    curl http://localhost:8080/produk
    

    Hasilnya:

    daftar produk
    

    Selamat, kamu baru saja membuat web server. Serius, itu saja kodenya. Go memang menyertakan HTTP server production-grade di standard library.

    Kalau muncul error address already in use, artinya port 8080 sedang dipakai program lain. Matikan program itu, atau ganti port di kodemu jadi ":8081" misalnya. Kamu juga bisa tes lewat browser dengan membuka http://localhost:8080/produk, karena browser mengirim request GET secara default. Untuk POST nanti kita tetap butuh curl.

    Membalas dengan JSON

    Teks polos tidak berguna buat frontend. Mereka butuh JSON. Di Go, cara paling umum adalah mendefinisikan struct, lalu meng-encode struct itu jadi JSON dengan package encoding/json:

    type Produk struct {
    	ID    int    `json:"id"`
    	Nama  string `json:"nama"`
    	Harga int    `json:"harga"`
    }
    

    Bagian `json:"nama"` disebut struct tag. Tag ini memberi tahu package encoding/json: saat struct ini diubah jadi JSON, pakai nama field nama, bukan Nama. Tanpa tag, field akan tampil persis seperti nama di struct, huruf besar di depan. Konvensi JSON umumnya huruf kecil, jadi tag ini hampir selalu dipakai. Ingat juga aturan dari bagian 4: field harus diawali huruf besar supaya terlihat oleh package lain, termasuk encoding/json. Field huruf kecil akan diabaikan diam diam.

    Sekarang ubah handler kita supaya membalas JSON:

    http.HandleFunc("GET /produk", func(w http.ResponseWriter, r *http.Request) {
    	daftar := []Produk{
    		{ID: 1, Nama: "Kopi Arabika", Harga: 45000},
    		{ID: 2, Nama: "Teh Melati", Harga: 20000},
    	}
    
    	w.Header().Set("Content-Type", "application/json")
    	json.NewEncoder(w).Encode(daftar)
    })
    

    Dua hal baru. w.Header().Set("Content-Type", "application/json") memberi tahu klien bahwa isi balasan adalah JSON, bukan teks biasa. Banyak library frontend bergantung pada header ini untuk mem-parsing respons secara otomatis. Lalu json.NewEncoder(w).Encode(daftar) mengubah slice kita jadi JSON dan langsung menulisnya ke respons. Praktis, satu baris selesai. Jangan lupa tambahkan "encoding/json" di import.

    Tes lagi dengan curl, hasilnya sudah JSON:

    [{"id":1,"nama":"Kopi Arabika","harga":45000},{"id":2,"nama":"Teh Melati","harga":20000}]
    

    Routing dengan Method dan Path Parameter

    Sejak Go 1.22, router bawaan (ServeMux) jadi jauh lebih enak dipakai. Kamu bisa menulis method langsung di pola, dan bisa menangkap bagian path yang dinamis:

    http.HandleFunc("GET /produk", listProduk)
    http.HandleFunc("POST /produk", tambahProduk)
    http.HandleFunc("GET /produk/{id}", detailProduk)
    

    Di sinilah gaya REST mulai terlihat. Konvensinya sederhana: method HTTP menentukan jenis aksinya. GET untuk membaca data, POST untuk membuat data baru, PUT untuk mengubah, DELETE untuk menghapus. Path-nya menunjuk ke sumber datanya, biasanya kata benda jamak seperti /produk atau /pelanggan. Jadi kamu tidak perlu bikin path seperti /ambilSemuaProduk atau /hapusProdukById. Kombinasi method dan path sudah cukup menjelaskan maksudnya, dan developer lain langsung paham tanpa baca dokumentasi panjang.

    Tiga baris ini artinya: GET ke /produk masuk ke listProduk, POST ke path yang sama masuk ke tambahProduk, dan GET ke /produk/5 masuk ke detailProduk. Bagian {id} disebut path parameter. Di dalam handler, nilainya diambil dengan r.PathValue("id"):

    func detailProduk(w http.ResponseWriter, r *http.Request) {
    	id := r.PathValue("id") // untuk /produk/5, id berisi "5"
    	// ...
    }
    

    Nilai yang dikembalikan selalu string, jadi kalau butuh angka kamu konversi dulu dengan strconv.Atoi. Satu catatan: di Go versi lama sebelum 1.22, pola method dan {id} belum ada, jadi orang mengecek r.Method manual atau pakai router pihak ketiga seperti chi dan gorilla/mux. Pastikan saja Go kamu minimal 1.22, cek dengan go version.

    Menerima Data Lewat POST

    GET dipakai untuk membaca data. POST dipakai untuk mengirim data baru. Data dari klien dikirim lewat body request dalam bentuk JSON, dan tugas kita membacanya dengan json.NewDecoder:

    func tambahProduk(w http.ResponseWriter, r *http.Request) {
    	var p Produk
    	if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
    		w.Header().Set("Content-Type", "application/json")
    		w.WriteHeader(http.StatusBadRequest)
    		json.NewEncoder(w).Encode(map[string]string{"error": "body bukan JSON yang valid"})
    		return
    	}
    
    	if p.Nama == "" || p.Harga <= 0 {
    		w.Header().Set("Content-Type", "application/json")
    		w.WriteHeader(http.StatusBadRequest)
    		json.NewEncoder(w).Encode(map[string]string{"error": "nama wajib diisi dan harga harus lebih dari 0"})
    		return
    	}
    
    	// simpan datanya, lalu balas 201
    	w.Header().Set("Content-Type", "application/json")
    	w.WriteHeader(http.StatusCreated)
    	json.NewEncoder(w).Encode(p)
    }
    

    Polanya kebalikan dari encoder tadi. json.NewDecoder(r.Body).Decode(&p) membaca body request dan mengisi struct p. Kita kirim pointer supaya function bisa mengubah isi struct aslinya, persis seperti yang kamu pelajari di bagian pointer. Kalau body-nya bukan JSON yang valid, Decode mengembalikan error dan kita hentikan proses di situ.

    Setelah decode berhasil, jangan langsung percaya datanya. Klien bisa saja kirim nama kosong atau harga minus. Validasi sederhana seperti di atas sudah cukup untuk tahap ini.

    Perhatikan juga w.WriteHeader. Ini cara kita menentukan status code respons. Status code itu bahasa universal HTTP. http.StatusCreated (201) artinya data berhasil dibuat. http.StatusBadRequest (400) artinya request-nya bermasalah dari sisi klien. Kenapa penting? Karena frontend mengambil keputusan berdasarkan angka ini. Kalau semua respons kamu 200 padahal isinya error, frontend harus menebak-nebak dari isi body, dan itu resep bug. Status yang benar bikin API-mu jujur dan mudah dipakai orang lain.

    Menyimpan Data di Memory, dan Kenapa Butuh Mutex

    API kita butuh tempat menyimpan produk. Untuk sekarang, kita pakai slice biasa yang hidup di memory. Tapi ada satu jebakan. Ingat pelajaran bagian 10: server HTTP di Go menjalankan setiap request di goroutine terpisah. Kalau dua request POST datang bersamaan, dua goroutine bisa menulis ke slice yang sama di saat yang sama. Itu race condition, dan hasilnya data bisa rusak. Solusinya sync.Mutex: kunci dulu sebelum menyentuh data, lepas setelah selesai. Satu goroutine masuk, yang lain antre.

    Di program utuh nanti, kamu akan lihat polanya selalu sama: mu.Lock() di awal, lalu defer mu.Unlock() tepat di bawahnya. Pakai defer supaya kunci pasti dilepas saat function selesai, lewat jalur mana pun function itu keluar. Lupa melepas kunci akibatnya fatal, semua request berikutnya menggantung menunggu kunci yang tidak pernah dilepas.

    Satu kejujuran penting: data di memory hilang setiap server restart. Kamu matikan program, semua produk yang ditambahkan lenyap. Untuk latihan ini tidak masalah. Penyimpanan permanen pakai database akan kita bahas di bagian 12.

    Program Utuh: API Produk Mini

    Sekarang kita rakit semuanya jadi satu file. API ini punya tiga endpoint: GET daftar produk, GET satu produk berdasarkan id (balas 404 kalau tidak ketemu), dan POST tambah produk. Supaya tidak mengulang kode, kita buat dua helper kecil untuk menulis respons:

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"net/http"
    	"strconv"
    	"sync"
    )
    
    type Produk struct {
    	ID    int    `json:"id"`
    	Nama  string `json:"nama"`
    	Harga int    `json:"harga"`
    }
    
    var (
    	mu     sync.Mutex
    	produk = []Produk{
    		{ID: 1, Nama: "Kopi Arabika", Harga: 45000},
    		{ID: 2, Nama: "Teh Melati", Harga: 20000},
    	}
    	nextID = 3
    )
    
    func tulisJSON(w http.ResponseWriter, status int, data any) {
    	w.Header().Set("Content-Type", "application/json")
    	w.WriteHeader(status)
    	json.NewEncoder(w).Encode(data)
    }
    
    func tulisError(w http.ResponseWriter, status int, pesan string) {
    	tulisJSON(w, status, map[string]string{"error": pesan})
    }
    
    func listProduk(w http.ResponseWriter, r *http.Request) {
    	mu.Lock()
    	defer mu.Unlock()
    	tulisJSON(w, http.StatusOK, produk)
    }
    
    func detailProduk(w http.ResponseWriter, r *http.Request) {
    	id, err := strconv.Atoi(r.PathValue("id"))
    	if err != nil {
    		tulisError(w, http.StatusBadRequest, "id harus angka")
    		return
    	}
    
    	mu.Lock()
    	defer mu.Unlock()
    	for _, p := range produk {
    		if p.ID == id {
    			tulisJSON(w, http.StatusOK, p)
    			return
    		}
    	}
    	tulisError(w, http.StatusNotFound, "produk tidak ditemukan")
    }
    
    func tambahProduk(w http.ResponseWriter, r *http.Request) {
    	var p Produk
    	if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
    		tulisError(w, http.StatusBadRequest, "body bukan JSON yang valid")
    		return
    	}
    	if p.Nama == "" || p.Harga <= 0 {
    		tulisError(w, http.StatusBadRequest, "nama wajib diisi dan harga harus lebih dari 0")
    		return
    	}
    
    	mu.Lock()
    	defer mu.Unlock()
    	p.ID = nextID
    	nextID++
    	produk = append(produk, p)
    	tulisJSON(w, http.StatusCreated, p)
    }
    
    func main() {
    	http.HandleFunc("GET /produk", listProduk)
    	http.HandleFunc("GET /produk/{id}", detailProduk)
    	http.HandleFunc("POST /produk", tambahProduk)
    
    	fmt.Println("Server jalan di http://localhost:8080")
    	http.ListenAndServe(":8080", nil)
    }
    

    Jalankan dengan go run main.go, lalu tes ketiga endpoint. Pertama, ambil daftar produk:

    curl http://localhost:8080/produk
    
    [{"id":1,"nama":"Kopi Arabika","harga":45000},{"id":2,"nama":"Teh Melati","harga":20000}]
    

    Kedua, ambil satu produk. Coba id yang ada dan yang tidak ada:

    curl http://localhost:8080/produk/1
    
    {"id":1,"nama":"Kopi Arabika","harga":45000}
    
    curl http://localhost:8080/produk/99
    
    {"error":"produk tidak ditemukan"}
    

    Ketiga, tambah produk baru lewat POST. Flag -X POST menentukan method, -d mengisi body:

    curl -X POST http://localhost:8080/produk \
      -H "Content-Type: application/json" \
      -d '{"nama":"Gula Aren","harga":15000}'
    
    {"id":3,"nama":"Gula Aren","harga":15000}
    

    Panggil lagi GET /produk, dan Gula Aren sudah muncul di daftar. API-mu hidup dan datanya nyambung antar endpoint.

    Satu Kebiasaan Baik: Struktur Respons yang Konsisten

    Perhatikan satu detail dari program di atas. Semua respons kita JSON, termasuk saat error. Sukses balas data, gagal balas {"error": "..."}. Klien jadi selalu tahu format apa yang akan diterima, apa pun hasilnya. Kebiasaan kecil ini membedakan API yang enak dipakai dengan API yang bikin frontend developer mengelus dada. Pertahankan sejak proyek pertamamu.

    Penutup

    Hari ini kamu sudah membuat REST API lengkap hanya dengan standard library: server HTTP, routing dengan method dan path parameter, encode dan decode JSON, validasi input, status code yang benar, plus mutex supaya aman dari race condition. Coba modifikasi sendiri sebagai latihan, misalnya tambah endpoint DELETE /produk/{id} atau field stok di struct Produk.

    Masalah yang tersisa jelas: data hilang saat server restart. Di bagian 12, “Menghubungkan API ke Database dengan database/sql”, kita selesaikan itu dengan menyimpan produk ke database sungguhan. Pola API seperti inilah yang kami pakai sehari hari saat membangun sistem aplikasi untuk bisnis dan instansi, jadi kuasai dasarnya baik baik. Sampai ketemu di bagian 12.

  • Belajar Database dari Nol #2: Konsep Database Relasional

    Belajar Database dari Nol #2: Konsep Database Relasional

    Database relasional adalah database yang menyimpan data dalam bentuk tabel-tabel yang saling berhubungan. Setiap tabel punya baris dan kolom, mirip lembar Excel, tapi dengan aturan yang jauh lebih ketat: tipe data dikunci, setiap baris wajib punya identitas unik, dan hubungan antar tabel dijaga oleh sistem, bukan oleh ingatan manusia. Model inilah yang dipakai MySQL, PostgreSQL, SQL Server, dan hampir semua aplikasi yang menyimpan data transaksi.

    Artikel ini bagian kedua dari seri Belajar Database dari Nol. Di bagian ini kita belum banyak mengetik perintah. Fokusnya memahami cara berpikir database relasional dulu, supaya waktu mulai praktik membuat tabel di bagian berikutnya kamu tahu alasan di balik setiap keputusan, bukan sekadar meniru kode.

    Prasyaratnya cuma satu: MySQL 8.4 sudah terpasang dan kamu bisa masuk ke prompt mysql>. Kalau belum, kerjakan dulu Belajar Database dari Nol #1: Kenalan & Install MySQL 8.4, lalu kembali ke sini.

    Tabel, Baris, Kolom, dan Skema: Dipetakan dari Data Toko Nyata

    Bayangkan kamu punya toko alat tulis dan mencatat pelanggan di buku. Setiap pelanggan dicatat namanya, nomor HP, dan kotanya. Dalam database relasional, catatan itu jadi sebuah tabel seperti ini:

    id nama no_hp kota
    1 Budi Santoso 081234567890 Semarang
    2 Siti Aminah 085612345678 Banjarnegara
    3 Andi Wijaya 089876543210 Purwokerto

    Dari tabel itu kita bisa memetakan empat istilah dasar:

    • Tabel: satu kumpulan data sejenis. Di sini tabel pelanggan. Nanti akan ada tabel lain seperti produk dan pesanan.
    • Baris (record atau row): satu entitas utuh. Baris pertama adalah data lengkap milik Budi Santoso.
    • Kolom (field atau column): satu jenis informasi yang sama untuk semua baris. Kolom kota berisi kota untuk setiap pelanggan.
    • Skema: rancangan strukturnya. Skema menjawab pertanyaan tabel apa saja yang ada, kolomnya apa, tipe datanya apa, dan bagaimana tabel-tabel itu berhubungan. Di MySQL, istilah schema dan database sering dipakai bergantian untuk hal yang sama.

    Perbedaan penting dengan buku catatan biasa: di database relasional, struktur ditetapkan lebih dulu. Kamu mendeklarasikan bahwa no_hp itu teks maksimal 15 karakter, baru kemudian mengisi datanya. Data yang tidak cocok dengan struktur akan ditolak. Kaku, tapi justru kekakuan ini yang membuat data tetap rapi setelah bertahun-tahun dipakai.

    Primary Key dan UNIQUE: Kenapa Setiap Baris Butuh Identitas

    Perhatikan kolom id di tabel tadi. Kenapa perlu, padahal sudah ada nama? Karena nama bisa kembar. Kalau ada dua Budi Santoso dan kamu ingin menghapus salah satunya, database harus tahu persis Budi yang mana. Di sinilah primary key berperan: satu kolom (atau kombinasi kolom) yang nilainya dijamin unik dan tidak boleh kosong untuk setiap baris.

    Aturan primary key sederhana:

    • Nilainya wajib unik. Tidak boleh ada dua baris dengan id sama.
    • Tidak boleh NULL alias kosong.
    • Satu tabel hanya boleh punya satu primary key.
    • Idealnya nilainya tidak pernah berubah. Karena itu praktik umum memakai angka yang naik otomatis (AUTO_INCREMENT), bukan data asli seperti nomor HP yang bisa ganti.

    Lalu bagaimana dengan kolom yang memang tidak boleh kembar tapi bukan identitas utama, misalnya no_hp atau email? Untuk itu ada constraint UNIQUE. Bedanya dengan primary key: UNIQUE boleh lebih dari satu dalam satu tabel dan nilainya boleh NULL.

    Sebagai gambaran, beginilah bentuk deklarasinya dalam SQL. Detail praktiknya kita bahas di bagian 3, jadi sekarang cukup dibaca dulu:

    CREATE TABLE pelanggan (
        id INT AUTO_INCREMENT PRIMARY KEY,
        nama VARCHAR(100) NOT NULL,
        no_hp VARCHAR(15) UNIQUE,
        kota VARCHAR(50)
    );

    Kalau kamu mencoba memasukkan dua pelanggan dengan no_hp sama, MySQL akan menolak dengan pesan seperti ini:

    ERROR 1062 (23000): Duplicate entry '081234567890' for key 'pelanggan.no_hp'

    Penolakan ini bukan gangguan. Ini database sedang melindungi kamu dari data ganda yang di spreadsheet biasanya baru ketahuan setelah jadi masalah.

    Relasi Antar Tabel: Gambaran Besar Pelanggan dan Pesanan

    Sekarang bagian yang membuat kata “relasional” bermakna. Toko tadi mulai mencatat pesanan. Cara naif: tambahkan kolom pesanan di tabel pelanggan. Masalahnya, satu pelanggan bisa memesan berkali-kali. Mau bikin kolom pesanan_1, pesanan_2, pesanan_3? Sampai berapa? Struktur seperti ini cepat berantakan.

    Solusi relasional: pisahkan jadi dua tabel, lalu hubungkan lewat primary key. Tabel pesanan cukup menyimpan id_pelanggan, yaitu rujukan ke baris di tabel pelanggan:

    id id_pelanggan tanggal total
    1 2 2026-07-20 45000
    2 1 2026-07-21 120000
    3 2 2026-07-25 78000

    Baca baris pertama: pesanan nomor 1 dibuat oleh pelanggan dengan id 2, yaitu Siti Aminah. Nama, nomor HP, dan kota Siti tidak perlu ditulis ulang. Cukup disimpan sekali di tabel pelanggan, dan semua pesanannya menunjuk ke sana. Kalau Siti ganti nomor HP, kamu update satu baris, dan semua data pesanan otomatis tetap konsisten.

    Kolom id_pelanggan ini nantinya akan kita resmikan sebagai foreign key, yaitu aturan yang membuat MySQL menolak pesanan dari id pelanggan yang tidak ada. Cara menggabungkan dua tabel ini dalam satu hasil query namanya JOIN. Keduanya dapat porsi bahasan sendiri di bagian 11 sampai 13 seri ini. Untuk sekarang, yang penting kamu pegang gambaran besarnya: data dipecah ke tabel-tabel kecil yang fokus, lalu dihubungkan lewat kunci.

    Database Relasional vs Spreadsheet: Kapan Excel Tidak Cukup

    Pertanyaan yang wajar: kalau bentuknya sama-sama tabel, kenapa tidak pakai Excel atau Google Sheets saja? Untuk catatan pribadi atau data kecil yang dipegang satu orang, spreadsheet memang cukup. Tapi ada tiga batas yang membuat aplikasi serius selalu pindah ke RDBMS (Relational Database Management System) seperti MySQL.

    Integritas data

    Spreadsheet menerima apa saja. Kolom tanggal bisa terisi “besok”, nomor HP bisa terisi nama, dan baris pelanggan bisa terhapus padahal masih punya pesanan. RDBMS menolak semua itu lewat tipe data, constraint NOT NULL, UNIQUE, dan foreign key. Kesalahan dicegah saat data masuk, bukan ditemukan saat laporan sudah kacau.

    Banyak pengguna sekaligus

    Dua kasir yang mengedit satu file Excel bersamaan itu resep bencana. MySQL dirancang untuk ratusan koneksi bersamaan, lengkap dengan mekanisme penguncian dan transaksi supaya dua perubahan yang bertabrakan tidak saling menimpa. Konsep transaksinya kita bahas di bagian 19.

    Skala dan kecepatan pencarian

    Spreadsheet mulai berat di puluhan ribu baris. Tabel MySQL yang diberi index tetap bisa menjawab pencarian dalam hitungan milidetik meski berisi jutaan baris. Di proyek klien Arrazy, tabel transaksi sistem aplikasi yang kami bangun rutin tumbuh terus setiap hari, dan itu skenario normal yang memang jadi habitat database relasional, bukan kondisi darurat.

    Ringkasnya dalam satu tabel perbandingan:

    Aspek Spreadsheet RDBMS (MySQL)
    Validasi data Opsional, mudah dilanggar Dipaksa lewat tipe data dan constraint
    Pengguna bersamaan Rawan konflik Dirancang untuk banyak koneksi
    Jutaan baris Lambat atau crash Normal, dibantu index
    Hubungan antar data Manual lewat VLOOKUP Bawaan lewat foreign key dan JOIN
    Diakses aplikasi lain Sulit dan rapuh Standar lewat SQL

    Istilah yang Akan Sering Muncul: SQL, Query, DDL, dan DML

    Sebelum lanjut ke praktik, kenali dulu empat istilah yang akan muncul terus sepanjang seri ini.

    • SQL (Structured Query Language): bahasa standar untuk berbicara dengan database relasional. MySQL, PostgreSQL, dan SQL Server semuanya memakai SQL dengan sedikit perbedaan dialek.
    • Query: satu perintah SQL yang kamu kirim ke database. Bisa berupa permintaan data, bisa juga perintah mengubah sesuatu.
    • DDL (Data Definition Language): kelompok perintah untuk mengatur struktur. Contohnya CREATE TABLE, ALTER TABLE, DROP TABLE. DDL mengubah wadahnya.
    • DML (Data Manipulation Language): kelompok perintah untuk mengelola isi. Contohnya INSERT, SELECT, UPDATE, DELETE. DML mengubah atau membaca isinya.

    Analogi sederhananya: DDL itu membangun dan merenovasi rak arsip, DML itu memasukkan, membaca, mengganti, dan membuang berkas di rak tersebut. Bagian 3 sampai 5 seri ini urutannya persis mengikuti logika itu: buat wadahnya dulu (DDL), baru isi datanya (DML).

    Untuk memastikan lingkunganmu siap, masuk ke MySQL dan jalankan satu query pertama:

    mysql -u root -p

    Lalu di prompt mysql>:

    SHOW DATABASES;

    Output yang diharapkan pada instalasi MySQL 8.4 yang masih bersih:

    +--------------------+
    | Database           |
    +--------------------+
    | information_schema |
    | mysql              |
    | performance_schema |
    | sys                |
    +--------------------+
    4 rows in set (0.00 sec)

    Empat database itu bawaan sistem, tempat MySQL menyimpan konfigurasi dan metadata dirinya sendiri. Jangan diutak-atik. Database milikmu akan kita buat sendiri di bagian berikutnya.

    Troubleshooting: Error yang Sering Dialami Pemula

    ERROR 2002 (HY000): Can’t connect to local MySQL server through socket

    Penyebab: service MySQL belum jalan. Klien mencoba menyambung, tapi tidak ada server yang mendengarkan. Solusi di Ubuntu atau Debian:

    sudo systemctl start mysql
    sudo systemctl status mysql

    Pastikan statusnya active (running). Kalau ingin MySQL otomatis jalan setiap komputer menyala, jalankan sudo systemctl enable mysql.

    ERROR 1045 (28000): Access denied for user ‘root’@’localhost’

    Penyebab: password salah, atau kamu masuk tanpa opsi -p padahal akun root sudah diberi password saat instalasi di bagian 1. Solusi: ulangi dengan mysql -u root -p dan ketik password dengan teliti. Ingat, saat mengetik password di terminal memang tidak muncul karakter apa pun. Itu normal, bukan keyboard rusak.

    ERROR 1064 (42000): You have an error in your SQL syntax

    Penyebab: salah ketik perintah. Yang paling sering dialami pemula: lupa titik koma di akhir perintah, salah eja kata kunci seperti DATABSE, atau memakai tanda kutip keriting hasil copy dari dokumen Word. Solusi: baca pesan errornya, MySQL menunjukkan potongan teks di dekat lokasi salahnya. Ketik ulang perintah secara manual, jangan copy paste dari sumber yang formatnya tidak jelas.

    Prompt berubah jadi tanda panah -> dan perintah tidak jalan

    Penyebab: kamu menekan Enter sebelum menutup perintah, biasanya karena lupa titik koma. MySQL mengira perintahmu belum selesai dan menunggu lanjutannya. Solusi: kalau memang tinggal kurang titik koma, ketik ; lalu Enter. Kalau mau membatalkan perintah yang setengah jadi, ketik \c lalu Enter, dan prompt kembali normal ke mysql>.

    Rangkuman dan Lanjut ke Bagian 3

    Sampai sini kamu sudah memegang fondasi konsepnya. Database relasional menyimpan data dalam tabel berisi baris dan kolom, strukturnya ditetapkan lewat skema, setiap baris diberi identitas lewat primary key, dan tabel-tabel saling terhubung lewat kunci. Dibanding spreadsheet, RDBMS menang di integritas data, dukungan banyak pengguna, dan skala. Kamu juga sudah kenal peta istilah SQL, query, DDL, dan DML yang jadi bahasa sehari-hari sepanjang seri ini.

    Di bagian berikutnya, konsep ini mulai kita eksekusi: Belajar Database dari Nol #3: Membuat Database & Tabel MySQL. Kita akan membuat database toko dari nol, lengkap dengan tabel pelanggan dan aturan-aturannya. Artikelnya terbit menyusul, pantau daftar lengkapnya di halaman hub Belajar Database.

    Referensi

  • Sistem Pendaftaran Event Lari Online: Kuota, Pembayaran, E-Ticket

    Sistem Pendaftaran Event Lari Online: Kuota, Pembayaran, E-Ticket

    Sistem pendaftaran event lari online pada dasarnya mengurus empat hal. Pertama, form pendaftaran per kategori, misalnya 5K, 10K, dan half marathon, lengkap dengan harga masing-masing. Kedua, kuota yang otomatis menutup pendaftaran begitu slot habis, jadi tidak ada peserta kelebihan. Ketiga, pembayaran online yang terverifikasi otomatis lewat payment gateway, tanpa panitia harus memelototi bukti transfer. Keempat, e-ticket dengan kode QR yang dipakai peserta saat pengambilan race pack di hari H.

    Kalau Anda panitia fun run, night run, atau race lokal yang sedang menimbang mau buka pendaftaran lewat apa, artikel ini untuk Anda. Kita bahas kenapa cara manual cepat kewalahan, seperti apa alur peserta dan panitia di sistem online, fitur apa saja yang sering kelupaan, plus satu studi kasus nyata dari event lari di Berau.

    Kenapa Google Form dan transfer manual cepat kewalahan

    Hampir semua panitia event lari pertama kali mulai dari kombinasi yang sama. Google Form untuk data peserta, transfer bank untuk pembayaran, dan WhatsApp untuk kirim bukti transfer. Untuk 50 peserta, cara ini masih jalan. Begitu target naik ke ratusan atau ribuan, masalahnya muncul satu per satu.

    • Verifikasi bukti transfer satu per satu. Setiap peserta kirim screenshot mutasi. Panitia harus cocokkan nama, nominal, dan tanggal secara manual. Seratus peserta berarti seratus kali buka gambar dan cek rekening. Salah centang satu saja, ada orang yang sudah bayar tapi tidak masuk daftar.
    • Kuota jebol. Google Form tidak tahu kuota Anda berapa. Form tetap menerima isian ke-501 padahal slot cuma 500. Ujungnya panitia harus refund dan minta maaf, atau memaksakan jumlah peserta melebihi kapasitas race pack dan medali.
    • Data ukuran jersey acak-acakan. Peserta salah pilih ukuran, minta ganti lewat chat, lalu datanya tersebar di spreadsheet, WhatsApp, dan ingatan panitia. Saat vendor jersey minta rekap final, tidak ada yang yakin angkanya benar.
    • Antrian race pack kacau. Tanpa e-ticket, pengambilan race pack mengandalkan pencarian nama di spreadsheet. Satu peserta bisa makan waktu satu sampai dua menit. Kalikan dengan seribu orang, antriannya mengular sejak siang.

    Intinya, cara manual memindahkan beban dari sistem ke tenaga panitia. Makin besar event, makin besar pula tim admin yang harus begadang.

    Alur peserta: dari daftar sampai ambil race pack

    Di sistem pendaftaran online, pengalaman peserta jadi jauh lebih pendek. Alurnya kira-kira begini.

    • Pilih kategori. Peserta buka halaman pendaftaran, lihat kategori yang tersedia beserta sisa kuota dan harganya. Kategori yang penuh otomatis tampil sebagai habis dan tidak bisa dipilih.
    • Isi data diri. Nama sesuai identitas, tanggal lahir, ukuran jersey, kontak darurat, dan data lain yang panitia butuhkan. Form bisa dibuat wajib per kolom, jadi tidak ada data bolong.
    • Bayar online. Peserta memilih metode bayar, mulai dari virtual account, QRIS, sampai e-wallet. Status pembayaran terverifikasi otomatis dalam hitungan detik. Detail soal cara kerjanya bisa Anda baca di halaman jasa website payment gateway.
    • Terima e-ticket. Begitu pembayaran masuk, sistem mengirim e-ticket berisi kode QR ke email peserta. Ini bukti resmi bahwa slot sudah aman.
    • Ambil race pack. Di hari pengambilan, peserta cukup tunjukkan QR. Petugas scan, data muncul di layar, race pack dan BIB diserahkan. Selesai dalam hitungan detik per orang.

    Dari sisi peserta, seluruh proses dari daftar sampai pegang e-ticket bisa selesai dalam lima menit. Tidak ada kirim bukti transfer, tidak ada menunggu balasan admin.

    Alur panitia: dashboard, verifikasi otomatis, export data BIB

    Bagian yang jarang terlihat peserta justru yang paling menghemat tenaga panitia. Sistem pendaftaran yang baik minimal memberi tiga hal ini di sisi admin.

    Dashboard pendaftar real time

    Panitia bisa lihat jumlah pendaftar per kategori, siapa yang sudah bayar, siapa yang masih menunggu pembayaran, dan berapa sisa kuota. Semua dalam satu layar, tanpa membuka tiga spreadsheet berbeda. Keputusan seperti perlu tidaknya perpanjang periode early bird jadi berbasis data, bukan perasaan.

    Verifikasi pembayaran tanpa campur tangan manusia

    Karena pembayaran lewat payment gateway, status lunas masuk otomatis ke sistem. Tidak ada lagi sesi malam panitia mencocokkan mutasi rekening dengan daftar nama. Risiko salah verifikasi juga hilang, karena tidak ada langkah manual yang bisa keliru.

    Export data untuk BIB, jersey, dan timing chip

    Menjelang hari H, panitia butuh rekap untuk banyak pihak. Vendor jersey butuh jumlah per ukuran. Percetakan BIB butuh daftar nama dan nomor. Penyedia timing system butuh data peserta per kategori. Dengan sistem online, semua tinggal export dari dashboard dalam format yang rapi, bukan hasil copy paste dari berbagai sumber.

    Fitur yang sering dilupakan panitia baru

    Ada beberapa fitur yang jarang terpikir di awal, tapi terasa sekali saat event berjalan. Kalau Anda sedang menyusun kebutuhan sistem, masukkan poin-poin ini ke daftar sejak hari pertama.

    • Batas waktu pembayaran. Ini yang paling sering kelupaan. Tanpa batas waktu, satu orang bisa mengunci slot berjam-jam tanpa pernah bayar. Padahal ada peserta lain yang siap bayar detik itu juga. Sistem yang baik memberi jendela bayar, misalnya satu atau dua jam. Lewat dari itu, slot otomatis dilepas kembali ke kuota.
    • Ukuran jersey terkunci per pendaftaran. Ukuran dipilih saat daftar dan tersimpan rapi per peserta per kategori. Rekap untuk vendor tinggal ditarik dari sistem, bukan disusun ulang dari chat.
    • Kontak darurat. Event lari adalah aktivitas fisik. Kalau ada peserta cedera atau pingsan di lintasan, tim medis butuh nomor keluarga yang bisa dihubungi saat itu juga. Kolom ini wajib ada di form, dan datanya harus mudah dicari petugas.
    • Field pernyataan kesehatan. Persetujuan bahwa peserta dalam kondisi fit dan memahami risiko. Selain melindungi peserta, ini juga melindungi panitia secara administratif.

    Studi kasus: B.Night Run Party di Berau

    Supaya tidak berhenti di teori, ini contoh sistem yang pernah kami kerjakan. B.Night Run Party adalah website pendaftaran untuk event marathon yang digelar 28 Maret 2026 di Berau, Kalimantan Timur.

    Karakter event lari seperti ini punya satu momen kritis, yaitu saat pendaftaran dibuka dan banyak orang masuk bersamaan untuk berebut kuota. Karena itu sistemnya kami rancang bukan sekadar tampil menarik, tapi siap menghadapi lonjakan traffic. Alur pendaftarannya dibuat singkat supaya calon peserta tidak batal di tengah proses. Pembayaran terintegrasi payment gateway supaya verifikasi berjalan cepat tanpa pengecekan manual oleh panitia.

    Yang menarik, event ini memakai mekanisme antrian digital untuk rebutan kuota. Saat pendaftar membludak di waktu yang sama, sistem mengatur giliran supaya prosesnya tertib dan terasa fair bagi semua peserta. Panitia sendiri mendapat dashboard manajemen pendaftar untuk memantau data peserta, ditambah CMS untuk kelola konten dan fitur auto sertifikat online untuk peserta setelah event. Website event ini memang sudah tidak aktif karena eventnya selesai, dan itu wajar untuk website event yang umurnya mengikuti umur acara.

    Kapan cukup pakai form manual

    Jujur saja, tidak semua event butuh sistem seperti ini. Kalau Anda mengadakan fun run internal kantor atau komunitas dengan peserta di bawah seratus orang, semua saling kenal, dan pembayaran bisa dikoordinasi lewat bendahara, Google Form plus transfer manual masih masuk akal. Biayanya nol dan bebannya masih tertangani.

    Sistem pendaftaran online mulai layak dipertimbangkan saat salah satu kondisi ini terpenuhi. Target peserta ratusan ke atas. Pendaftaran terbuka untuk publik yang tidak Anda kenal. Ada beberapa kategori dengan kuota dan harga berbeda. Atau Anda tidak punya cukup relawan untuk verifikasi pembayaran dan melayani antrian race pack. Di titik itu, biaya sistem biasanya lebih murah daripada biaya kekacauan yang dicegahnya.

    Pertanyaan yang sering diajukan

    Payment gateway apa saja yang bisa diintegrasikan?

    Secara umum sistem pendaftaran bisa terhubung ke payment gateway populer di Indonesia. Lewat satu integrasi, peserta bisa bayar pakai virtual account berbagai bank, QRIS, e-wallet, sampai kartu. Pilihan penyedianya bisa disesuaikan dengan rekening dan kebutuhan legalitas penyelenggara.

    Bagaimana proses check-in di hari H?

    Peserta menunjukkan e-ticket berisi kode QR dari email atau ponsel mereka. Petugas scan pakai kamera ponsel atau scanner, data peserta langsung muncul, lalu race pack dan BIB diserahkan. Satu QR hanya bisa dipakai sekali, jadi tidak ada tiket ganda.

    Berapa lama waktu setup sistemnya?

    Tergantung kompleksitas fitur. Sistem pendaftaran standar dengan kategori, kuota, pembayaran, dan e-ticket biasanya selesai dalam hitungan minggu, bukan bulan. Yang penting, mulai diskusi jauh sebelum tanggal buka pendaftaran supaya ada waktu untuk uji coba.

    Event lari yang sukses dimulai jauh sebelum garis start, yaitu dari pendaftaran yang lancar. Peserta senang karena daftar dan bayar cuma butuh beberapa menit. Panitia tenang karena kuota, pembayaran, dan data peserta terkelola sendiri oleh sistem.

    Kalau Anda sedang menyiapkan fun run, night run, atau race lokal dan ingin tahu sistem seperti apa yang pas untuk skala event Anda, silakan hubungi tim Arrazy. Ceritakan target peserta dan kategorinya, nanti kita bahas kebutuhannya dari sana.

  • Berapa Lama Bikin Website? Timeline Realistis per Jenis Proyek

    Berapa Lama Bikin Website? Timeline Realistis per Jenis Proyek

    Jawaban singkatnya begini. Landing page satu halaman biasanya selesai dalam 3 sampai 7 hari kerja. Company profile standar dengan 5 sampai 8 halaman butuh 2 sampai 4 minggu. Toko online butuh 3 sampai 6 minggu, tergantung jumlah produk dan payment gateway. Sistem atau aplikasi web custom dihitung dalam bulan, bukan minggu.

    Rentangnya memang lebar. Wajar, karena durasi proyek website tidak cuma ditentukan oleh developer. Kesiapan konten, kecepatan approval, dan jumlah revisi punya andil besar. Di artikel ini kami bedah timeline tiap jenis proyek berdasarkan pengalaman mengerjakan puluhan website klien, termasuk fase yang sering bikin molor tapi jarang dihitung di awal.

    Timeline realistis per jenis proyek

    Landing page satu halaman: 3 sampai 7 hari kerja

    Landing page adalah proyek paling cepat. Satu halaman, satu tujuan, biasanya untuk promosi produk atau kampanye iklan. Hari pertama dipakai untuk memahami brief dan menyusun struktur halaman. Hari kedua sampai keempat untuk desain dan development. Sisanya untuk revisi dan publish.

    Catatannya satu. Estimasi ini berlaku kalau materi sudah siap. Teks, foto produk, logo, dan nomor kontak sudah di tangan developer sejak hari pertama. Kalau materi masih dicari sambil jalan, seminggu bisa jadi tiga minggu.

    Company profile standar: 2 sampai 4 minggu

    Company profile 5 sampai 8 halaman biasanya berisi beranda, tentang kami, layanan atau produk, portofolio, dan kontak. Minggu pertama dipakai untuk brief, struktur sitemap, dan desain halaman utama. Minggu kedua untuk desain halaman turunan dan mulai development. Minggu ketiga untuk pengisian konten, revisi, dan testing di berbagai perangkat. Kalau semua lancar, minggu ketiga sudah bisa tayang.

    Kenapa ada yang sampai empat minggu atau lebih? Hampir selalu karena konten. Profil perusahaan belum ditulis, foto belum ada, atau daftar layanan masih didiskusikan internal. Websitenya sendiri sudah jadi, tinggal menunggu isinya.

    Toko online: 3 sampai 6 minggu

    Toko online lebih lama karena ada dua variabel besar. Pertama, jumlah produk. Mengunggah 20 produk dengan foto dan deskripsi jelas berbeda jauh dengan 500 produk yang datanya masih tersebar di catatan Excel. Kedua, payment gateway. Integrasi Midtrans atau Xendit butuh proses pendaftaran, verifikasi dokumen usaha, dan testing transaksi. Proses verifikasi ini di pihak penyedia payment gateway, bukan di developer, jadi tidak bisa dipercepat sesuka hati.

    Di luar itu masih ada pengaturan ongkir, alur checkout, notifikasi email atau WhatsApp, dan testing pesanan dari awal sampai akhir. Fase testing ini jangan dipangkas. Lebih baik mundur beberapa hari daripada pembeli pertama gagal bayar.

    Sistem atau web app custom: hitungan bulan

    Sistem custom seperti aplikasi inventaris, sistem informasi sekolah, atau dashboard internal adalah kategori yang paling sering disalahpahami. Banyak yang mengira ini seperti company profile versi besar. Padahal beda jenis pekerjaan.

    Di proyek custom, sebagian besar waktu justru habis sebelum coding dimulai. Ada fase analisis kebutuhan, pemetaan alur kerja pengguna, dan perancangan struktur data. Setelah itu baru development, yang biasanya dibagi per modul. Tiap modul harus dites, dipakai oleh calon pengguna, lalu disesuaikan lagi. Proyek custom paling sederhana butuh 2 sampai 3 bulan. Yang kompleks bisa 6 bulan atau lebih, dan itu normal. Vendor yang berani janji sistem custom selesai dua minggu justru perlu dicurigai.

    Fase yang memakan waktu tapi jarang dihitung

    Ini bagian yang jarang dibahas di halaman penawaran, tapi paling sering menentukan molor tidaknya proyek. Kami jujur saja di sini.

    • Menunggu konten dari klien. Ini juara satu penyebab molor, hampir di semua proyek. Teks profil perusahaan, foto tim, daftar harga, deskripsi produk. Developer bisa menyiapkan seluruh kerangka website, tapi tidak bisa mengarang isi bisnis Anda. Banyak proyek company profile yang websitenya selesai di minggu kedua, lalu diam sebulan menunggu konten.
    • Revisi bolak-balik. Revisi itu bagian normal dari proses. Yang bikin lama adalah revisi yang datang bertahap. Hari ini minta ganti warna, besok ganti foto, lusa ganti urutan section. Tiap putaran revisi butuh waktu pengerjaan plus waktu tunggu review. Lebih hemat kalau masukan dikumpulkan dulu, lalu dikirim sekaligus.
    • Approval yang lambat. Kalau tiap keputusan harus melewati tiga orang yang jadwalnya padat, satu pertanyaan sederhana bisa menggantung seminggu. Proyek dengan satu pengambil keputusan hampir selalu selesai lebih cepat.
    • Integrasi pihak ketiga. Verifikasi payment gateway, propagasi domain, akses hosting lama, atau menunggu approval API dari platform lain. Bagian ini di luar kendali developer maupun klien, jadi sebaiknya diurus paling awal, bukan di ujung proyek.

    Faktor yang bikin proyek selesai cepat

    Kabar baiknya, proyek yang selesai di batas bawah estimasi itu polanya mirip semua. Ada tiga hal yang hampir selalu hadir.

    • Brief yang jelas sejak awal. Developer tahu tujuan website, target pengunjung, halaman yang dibutuhkan, dan referensi tampilan yang disukai. Tidak ada tebak-tebakan, jadi tidak ada pengerjaan ulang. Kalau belum tahu cara menyusunnya, kami sudah membuat template brief website sebelum ketemu developer yang bisa langsung dipakai.
    • Konten siap sebelum development dimulai. Teks, foto, logo, dan data produk sudah terkumpul di satu folder. Ini satu langkah sederhana yang efeknya paling besar ke timeline.
    • Satu pengambil keputusan. Satu orang yang berhak bilang oke atau revisi. Masukan dari tim tetap boleh, tapi dikumpulkan lewat satu pintu.

    Hati-hati dengan janji yang terlalu cepat

    Di iklan sering muncul janji seperti website company profile jadi satu hari. Secara teknis itu mungkin. Pertanyaannya, apa yang dikorbankan supaya bisa secepat itu.

    Biasanya yang dipangkas adalah hal yang tidak kelihatan di layar. Tidak ada penggalian kebutuhan, jadi struktur halaman asal comot template. Teks diisi tulisan generik yang tidak menjual apa-apa. Tidak ada optimasi kecepatan, tidak ada pengaturan SEO dasar, dan testing di perangkat mobile dilewati. Hasilnya website yang tayang cepat tapi tidak menghasilkan apa-apa, lalu beberapa bulan kemudian dibangun ulang dari nol. Total waktunya justru lebih lama daripada mengerjakan dengan benar sejak awal.

    Ini bukan berarti semua yang cepat pasti buruk. Landing page memang bisa selesai hitungan hari. Yang perlu diwaspadai adalah estimasi yang jauh di bawah rentang wajar untuk jenis proyeknya, tanpa penjelasan proses apa saja yang tetap dijalankan.

    Tips memadatkan timeline dari sisi klien

    Ada beberapa hal yang bisa Anda lakukan supaya proyek selesai di batas bawah estimasi, bahkan sebelum kontrak ditandatangani.

    • Siapkan konten sebelum kontrak. Tulis profil perusahaan, kumpulkan foto, dan rapikan data produk selagi masih tahap diskusi dengan vendor. Begitu proyek jalan, developer bisa langsung ngebut.
    • Blok waktu untuk review. Sepakati jadwal review sejak awal, misalnya tiap Jumat sore. Masukkan ke kalender seperti meeting penting lainnya, karena memang penting.
    • Kumpulkan revisi dalam satu daftar. Catat semua masukan, lalu kirim sekaligus. Satu putaran revisi yang lengkap jauh lebih cepat daripada lima putaran kecil.
    • Urus akun pihak ketiga lebih awal. Daftar payment gateway, siapkan akses domain dan hosting, dan lengkapi dokumen usaha di minggu pertama proyek, bukan di akhir.
    • Tanyakan timeline per fase, bukan cuma total. Vendor yang baik bisa menjelaskan minggu ini ngapain, minggu depan ngapain, dan kapan giliran Anda harus menyerahkan sesuatu. Dari situ Anda juga tahu bagian mana yang bergantung pada Anda.

    Pertanyaan yang sering muncul

    Apakah website bisa tayang dulu walau kontennya belum lengkap?

    Bisa, dan kadang ini pilihan yang masuk akal. Tayangkan dulu halaman inti seperti beranda, layanan, dan kontak. Halaman lain menyusul bertahap. Dengan begitu website mulai bekerja untuk bisnis Anda tanpa menunggu semuanya sempurna. Sepakati saja dulu daftar halaman yang wajib ada di hari peluncuran.

    Kalau proyek molor, salah siapa?

    Seringnya bukan soal salah siapa, tapi soal ekspektasi yang tidak disepakati di awal. Timeline yang sehat mencantumkan tanggung jawab dua pihak. Developer punya tenggat pengerjaan, klien punya tenggat penyerahan konten dan review. Kalau dua-duanya tertulis, mudah dilihat bagian mana yang tersendat dan solusinya apa.

    Apakah menambah orang bisa mempercepat proyek?

    Tidak selalu. Untuk toko online dan proyek custom, ada urutan kerja yang memang harus berjalan berurutan. Desain dulu, baru development, baru testing. Menambah orang di tengah jalan kadang justru menambah waktu koordinasi. Cara yang lebih efektif adalah mengurangi waktu tunggu, yaitu konten yang siap dan approval yang cepat.

    Mau tahu estimasi untuk proyek Anda?

    Rentang di artikel ini adalah gambaran umum. Estimasi yang akurat baru bisa keluar setelah kebutuhan Anda dipetakan, karena dua proyek dengan jenis yang sama pun bisa beda kompleksitasnya. Kalau Anda sedang merencanakan website, lihat dulu cakupan layanan jasa pembuatan website kami, lalu hubungi tim Arrazy untuk konsultasi. Ceritakan kebutuhan Anda, nanti kami bantu susun timeline yang realistis, lengkap dengan pembagian tugas tiap fase. Gratis, tanpa kewajiban lanjut.

  • Belajar Golang dari Nol #10: Goroutine dan Channel

    Belajar Golang dari Nol #10: Goroutine dan Channel

    Selamat datang lagi. Di Belajar Golang dari Nol #9 kita sudah kenalan dengan interface, cara Go membuat kode jadi fleksibel. Sampai bagian itu, semua program yang kita tulis berjalan satu jalur. Baris pertama selesai dulu, baru baris berikutnya jalan. Kali ini kita belajar hal yang membuat Go terkenal: menjalankan banyak pekerjaan sekaligus. Namanya concurrency.

    Bayangkan Antrean di Minimarket

    Bayangkan minimarket dengan satu kasir. Ada sepuluh pembeli mengantre. Kasir melayani satu per satu. Pembeli kesepuluh harus menunggu sembilan orang di depannya. Ini yang disebut sequential. Satu pekerjaan selesai, baru pekerjaan berikutnya mulai.

    Sekarang bayangkan minimarket itu membuka tiga kasir. Antrean terpecah. Sepuluh pembeli selesai jauh lebih cepat. Ini gambaran concurrent. Beberapa pekerjaan berjalan dalam waktu yang beririsan.

    Program kita juga sering menghadapi antrean seperti ini. Ambil data dari database, panggil API pembayaran, kirim email notifikasi. Kalau semua dikerjakan bergiliran, pengguna menunggu lama. Go menyediakan cara yang sangat sederhana untuk membuka kasir tambahan. Inilah salah satu alasan besar kenapa perusahaan seperti Google, Uber, dan Gojek memilih Go untuk backend mereka.

    Goroutine: Kasir Tambahan di Programmu

    Goroutine adalah function yang berjalan bersamaan dengan function lain. Membuatnya gampang sekali. Cukup tambahkan kata go di depan pemanggilan function.

    package main
    
    import "fmt"
    
    func sapa() {
    	fmt.Println("Halo dari goroutine")
    }
    
    func main() {
    	go sapa()
    	fmt.Println("Main selesai")
    }

    Coba jalankan. Kemungkinan besar outputnya cuma satu baris:

    Main selesai

    Tulisan dari sapa tidak muncul sama sekali. Kenapa?

    Function main sendiri sebenarnya juga goroutine. Namanya main goroutine. Saat main selesai, program langsung berhenti. Semua goroutine lain ikut mati, tidak peduli sudah sempat jalan atau belum. Di contoh tadi, go sapa() hanya menitipkan pekerjaan. Main tidak menunggu. Dia lanjut ke baris berikutnya, mencetak “Main selesai”, lalu program tutup. Goroutine sapa belum sempat kebagian giliran.

    Kalau pakai analogi kasir: kamu membuka kasir kedua, tapi sebelum kasirnya sempat melayani siapa pun, tokonya sudah kamu tutup.

    Menunggu dengan sync.WaitGroup

    Kita butuh cara untuk bilang ke main: tunggu dulu, masih ada pekerjaan yang belum selesai. Alat paling umum untuk itu adalah sync.WaitGroup. Cara pakainya tiga langkah:

    • Add(1) dipanggil setiap kali kamu menitipkan satu pekerjaan
    • Done() dipanggil goroutine saat pekerjaannya selesai
    • Wait() membuat main menunggu sampai hitungannya kembali nol

    Biar manfaatnya terasa, kita simulasikan mengunduh tiga data. Setiap unduhan kita anggap butuh dua detik, ditiru dengan time.Sleep. Versi sekuensial dulu.

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func unduhData(nama string) {
    	fmt.Println("Mulai mengunduh", nama)
    	time.Sleep(2 * time.Second)
    	fmt.Println("Selesai mengunduh", nama)
    }
    
    func main() {
    	mulai := time.Now()
    
    	unduhData("laporan.pdf")
    	unduhData("foto.zip")
    	unduhData("video.mp4")
    
    	fmt.Println("Total waktu:", time.Since(mulai))
    }

    Total waktunya sekitar enam detik. Wajar. Tiga pekerjaan, masing-masing dua detik, dikerjakan bergiliran seperti satu kasir melayani tiga pembeli. Sekarang versi concurrent.

    package main
    
    import (
    	"fmt"
    	"sync"
    	"time"
    )
    
    func unduhData(nama string, wg *sync.WaitGroup) {
    	defer wg.Done()
    	fmt.Println("Mulai mengunduh", nama)
    	time.Sleep(2 * time.Second)
    	fmt.Println("Selesai mengunduh", nama)
    }
    
    func main() {
    	mulai := time.Now()
    
    	var wg sync.WaitGroup
    	daftar := []string{"laporan.pdf", "foto.zip", "video.mp4"}
    
    	for _, nama := range daftar {
    		wg.Add(1)
    		go unduhData(nama, &wg)
    	}
    
    	wg.Wait()
    	fmt.Println("Total waktu:", time.Since(mulai))
    }

    Jalankan dan perhatikan angkanya. Total waktunya sekitar dua detik saja. Tiga unduhan berjalan bersamaan, seperti tiga kasir melayani tiga pembeli sekaligus. Perhatikan juga defer wg.Done(). Dengan defer, Done pasti terpanggil saat function berakhir, apa pun yang terjadi di tengah jalan.

    Satu catatan. WaitGroup dikirim sebagai pointer, *sync.WaitGroup. Kamu sudah tahu alasannya dari bagian pointer. Kalau dikirim sebagai salinan, hitungan yang dipegang main tidak akan pernah berkurang, dan Wait() menunggu selamanya.

    Channel: Jalur Kirim Data Antar Goroutine

    Goroutine di atas hanya mencetak ke layar. Bagaimana kalau goroutine perlu mengirim hasil kerjanya kembali ke main? Jangan tergoda menulis ke variabel bersama. Cara yang aman di Go adalah channel.

    Channel itu jalur serah terima data antar goroutine. Satu pihak menyerahkan, pihak lain menerima. Membuatnya pakai make, mengirim pakai panah ke arah channel, menerima pakai panah dari channel.

    package main
    
    import "fmt"
    
    func main() {
    	ch := make(chan string)
    
    	go func() {
    		ch <- "pesan dari goroutine"
    	}()
    
    	pesan := <-ch
    	fmt.Println(pesan)
    }

    Ada satu sifat penting yang wajib kamu pahami: channel tanpa buffer bersifat blocking. Pengirim berhenti menunggu sampai ada yang menerima. Penerima juga berhenti menunggu sampai ada yang mengirim.

    Bayangkan serah terima barang secara langsung. Kurir dan penerima harus sama-sama hadir. Kalau kurir datang dan rumah kosong, kurir menunggu di depan pintu. Kalau kamu menunggu di rumah dan kurir belum datang, ya kamu yang menunggu. Barang baru berpindah saat keduanya bertemu.

    Sifat blocking ini pula yang membuat contoh di atas jalan tanpa WaitGroup. Baris pesan := <-ch menahan main sampai goroutine mengirim sesuatu.

    Pola Worker: Hasil Kerja Mengalir Lewat Channel

    Sekarang kita gabungkan. Satu goroutine bertugas memproses daftar pekerjaan, hasilnya dikirim satu per satu lewat channel. Main tinggal duduk manis menerima.

    package main
    
    import (
    	"fmt"
    	"strings"
    )
    
    func worker(tugas []string, hasil chan string) {
    	for _, t := range tugas {
    		hasil <- strings.ToUpper(t)
    	}
    	close(hasil)
    }
    
    func main() {
    	tugas := []string{"laporan", "invoice", "surat jalan"}
    	hasil := make(chan string)
    
    	go worker(tugas, hasil)
    
    	for h := range hasil {
    		fmt.Println("Selesai diproses:", h)
    	}
    }

    Ada dua hal baru di sini:

    • close(hasil) menandakan pengiriman sudah selesai. Menutup channel adalah tugas pengirim, bukan penerima.
    • for h := range hasil terus menerima dari channel sampai channel ditutup. Kalau lupa close, loop ini menunggu selamanya dan program berakhir deadlock.

    Buffered Channel, Sekilas Saja

    Channel biasa tidak punya ruang tunggu. Buffered channel punya. Kamu tentukan kapasitasnya saat membuat.

    package main
    
    import "fmt"
    
    func main() {
    	ch := make(chan string, 2)
    
    	ch <- "pesanan pertama"
    	ch <- "pesanan kedua"
    
    	fmt.Println(<-ch)
    	fmt.Println(<-ch)
    }

    Dengan kapasitas dua, pengirim bisa menaruh dua pesan tanpa menunggu penerima hadir. Pengirim baru terblokir saat buffernya penuh. Ini seperti loker paket di depan rumah. Kurir bisa titip barang lalu pergi, selama lokernya belum penuh.

    Buffered channel berguna saat pengirim dan penerima bekerja dengan tempo berbeda, misalnya satu goroutine memproduksi data cepat dan penerimanya mencerna lebih lambat. Untuk sekarang, pakai channel tanpa buffer dulu sampai kamu menemukan alasan yang jelas.

    select: Menunggu Beberapa Channel Sekaligus

    select mirip switch, tapi khusus untuk channel. Dia menunggu, lalu memilih cabang yang channelnya lebih dulu siap. Pemakaian paling umum untuk pemula adalah timeout: kita hanya mau menunggu sampai batas waktu tertentu.

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	ch := make(chan string)
    
    	go func() {
    		time.Sleep(3 * time.Second)
    		ch <- "data siap"
    	}()
    
    	select {
    	case pesan := <-ch:
    		fmt.Println(pesan)
    	case <-time.After(2 * time.Second):
    		fmt.Println("Kelamaan, kita batalkan saja")
    	}
    }

    Goroutine butuh tiga detik, batas sabar kita dua detik. Jadi cabang time.After yang menang. time.After sendiri mengembalikan channel yang mengirim sinyal setelah durasi tertentu, cocok dipasangkan dengan select. Pola select masih banyak variasinya. Detailnya kita simpan untuk bagian lanjutan seri ini.

    Peringatan: Jangan Tabur Goroutine di Semua Tempat

    Goroutine memang ringan. Satu program Go bisa menjalankan ribuan goroutine tanpa masalah. Tapi bukan berarti semua kode harus dibuat concurrent. Kode sekuensial lebih mudah dibaca, lebih mudah dicari salahnya. Pakai goroutine saat memang ada yang layak dikerjakan bersamaan, misalnya beberapa panggilan jaringan yang saling bebas.

    Bahaya paling umum di dunia concurrency namanya race condition. Ini terjadi saat dua goroutine atau lebih menulis ke variabel yang sama pada waktu beririsan. Hasilnya tidak bisa ditebak.

    package main
    
    import (
    	"fmt"
    	"sync"
    )
    
    func main() {
    	var wg sync.WaitGroup
    	counter := 0
    
    	for i := 0; i < 1000; i++ {
    		wg.Add(1)
    		go func() {
    			defer wg.Done()
    			counter++
    		}()
    	}
    
    	wg.Wait()
    	fmt.Println("Counter:", counter)
    }

    Logikanya counter harus 1000. Coba jalankan beberapa kali. Angkanya sering meleset. Penyebabnya, counter++ sebenarnya tiga langkah: baca nilai, tambah satu, tulis balik. Saat dua goroutine melakukannya bersamaan, langkah mereka bisa saling menimpa.

    Solusinya ada dua jalur: mengunci akses dengan mutex, atau mengubah desain supaya data mengalir lewat channel seperti contoh worker tadi. Keduanya akan kita bedah di kesempatan lain. Yang perlu kamu ingat sekarang, Go punya detektor bawaan. Jalankan programmu dengan go run -race main.go, dan Go akan melapor kalau menemukan race condition.

    Latihan: Cek Harga dari Tiga Supplier

    Saatnya latihan utuh. Skenarionya begini. Aplikasi gudangmu perlu membandingkan harga satu barang dari tiga supplier. Setiap supplier punya waktu respons berbeda, kita tiru dengan delay. Kalau dicek satu per satu, total waktunya adalah jumlah semua delay. Kita kerjakan secara concurrent, kumpulkan hasilnya lewat channel, lalu cari penawaran termurah.

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    type Penawaran struct {
    	Supplier string
    	Harga    int
    }
    
    func cekHarga(supplier string, harga int, delay time.Duration, hasil chan Penawaran) {
    	time.Sleep(delay)
    	hasil <- Penawaran{Supplier: supplier, Harga: harga}
    }
    
    func main() {
    	mulai := time.Now()
    	hasil := make(chan Penawaran)
    
    	go cekHarga("Supplier A", 152000, 2*time.Second, hasil)
    	go cekHarga("Supplier B", 148500, 1*time.Second, hasil)
    	go cekHarga("Supplier C", 150000, 3*time.Second, hasil)
    
    	var termurah Penawaran
    
    	for i := 0; i < 3; i++ {
    		p := <-hasil
    		fmt.Printf("%s menawarkan Rp%d\n", p.Supplier, p.Harga)
    
    		if termurah.Harga == 0 || p.Harga < termurah.Harga {
    			termurah = p
    		}
    	}
    
    	fmt.Printf("Termurah: %s dengan harga Rp%d\n", termurah.Supplier, termurah.Harga)
    	fmt.Println("Total waktu:", time.Since(mulai))
    }

    Kalau dicek sekuensial, total delaynya enam detik. Versi concurrent ini selesai sekitar tiga detik, mengikuti supplier yang paling lambat. Tiga kasir bekerja, toko tutup begitu pembeli terakhir selesai.

    Perhatikan juga urutan hasilnya. Supplier B muncul paling dulu karena delaynya paling pendek. Hasil dari channel datang sesuai siapa yang selesai duluan, bukan sesuai urutan pemanggilan. Kita menerima tepat tiga kali lewat loop, jadi tidak perlu close di sini karena jumlah datanya sudah pasti.

    Untuk latihan mandiri: tambahkan supplier keempat, lalu coba pasang timeout dengan select supaya supplier yang jawabnya lebih dari dua detik dianggap gugur.

    Rangkuman dan Lanjut ke Mana

    Hari ini kamu sudah memegang dasar concurrency di Go:

    • Kata go menjalankan function sebagai goroutine
    • Main yang selesai duluan mematikan semua goroutine, jadi kita menunggu dengan sync.WaitGroup
    • Channel adalah jalur serah terima data yang aman, sifat dasarnya blocking
    • close dan range dipakai saat jumlah kiriman tidak pasti
    • Buffered channel memberi ruang tunggu, select memilih channel yang siap duluan
    • Race condition nyata, dan go run -race membantu menangkapnya

    Materi ini akan langsung terpakai. Di bagian berikutnya, Belajar Golang dari Nol #11: Membuat REST API Pertamamu dengan net/http, kamu akan lihat bahwa setiap request yang masuk ke server Go dilayani oleh goroutine sendiri. Konsep hari ini adalah pondasinya.

    Kalau ada yang terlewat, ulangi dulu bagian 9 tentang interface sebelum lanjut. Dan kalau bisnismu butuh sistem aplikasi yang cepat dan andal seperti yang biasa dibangun dengan Go, tim Arrazy siap membantu.

  • Perbedaan Docker Image dan Container + Praktik Pertama

    Perbedaan Docker Image dan Container + Praktik Pertama

    Perbedaan Docker image dan container sebenarnya sederhana. Image adalah paket read-only berisi filesystem, aplikasi, dan konfigurasi yang dibutuhkan untuk menjalankan sebuah program. Container adalah proses yang berjalan dari image itu. Image sifatnya statis, seperti file yang diam di disk. Container sifatnya hidup, punya proses, punya memori, dan bisa dihentikan atau dihapus tanpa menyentuh image aslinya.

    Artikel ini bagian kedua dari seri Belajar Docker dari Nol. Setelah paham konsepnya, kita langsung praktik: menjalankan Nginx di container, melihat prosesnya, membukanya di browser, lalu membedah kenapa ada container yang langsung mati begitu dijalankan.

    Prasyarat: Docker Sudah Terpasang

    Kamu butuh Docker Engine yang sudah jalan di komputermu. Kalau belum, ikuti dulu bagian pertama seri ini: Apa Itu Docker dan Cara Install Docker di Ubuntu & Windows. Semua contoh di sini saya jalankan di Ubuntu 24.04 dengan Docker Engine 27, tapi perintahnya sama persis di Windows dengan Docker Desktop.

    Cek dulu instalasimu:

    docker --version

    Output yang diharapkan kurang lebih seperti ini (angka minor boleh beda):

    Docker version 27.5.1, build 9f9e405

    Analogi Paling Gampang: Image Itu Cetakan, Container Itu Hasil Cetakannya

    Bayangkan cetakan kue. Cetakan itu cuma satu, bentuknya tetap, dan tidak berubah sedikit pun setiap kali dipakai. Dari satu cetakan yang sama, kamu bisa membuat sepuluh kue. Tiap kue berdiri sendiri. Kue pertama boleh kamu makan, kue kedua boleh gosong, cetakannya tetap utuh.

    Docker persis seperti itu. Image adalah cetakannya. Container adalah kuenya. Dari satu image nginx, kamu bisa menjalankan lima container Nginx sekaligus, masing-masing dengan nama, port, dan isi memori sendiri. Menghapus salah satu container tidak berpengaruh apa pun ke image, dan tidak berpengaruh ke container lain.

    Kalau dirangkum dalam tabel:

    Aspek Image Container
    Sifat Read-only, statis Hidup, punya proses yang berjalan
    Analogi Cetakan kue Kue hasil cetakan
    Jumlah Satu image Bisa jadi banyak container
    Disimpan di Disk lokal, hasil pull dari registry Dibuat saat docker run, di atas image
    Kalau dihapus Container baru tidak bisa dibuat darinya Image tetap aman, container lain tetap jalan

    Satu detail teknis yang penting: saat container dibuat, Docker tidak menyalin seluruh isi image. Docker hanya menambahkan satu lapisan tulis (writable layer) tipis di atas image. Semua perubahan file di dalam container terjadi di lapisan ini. Itulah kenapa membuat container itu cepat sekali, hitungan detik, bukan menit seperti menyalakan virtual machine.

    Arsitektur Docker: Client, Daemon, dan Registry

    Sebelum praktik, kenali dulu tiga aktor utama supaya kamu paham apa yang sebenarnya terjadi saat mengetik perintah.

    • Docker client, yaitu perintah docker yang kamu ketik di terminal. Dia tidak menjalankan container sendiri, dia cuma mengirim permintaan.
    • Docker daemon (dockerd), proses yang jalan di background. Dialah yang benar-benar bekerja: membangun image, membuat container, mengatur network dan storage.
    • Registry, gudang image di internet atau server internal. Yang paling populer adalah Docker Hub. Dari sinilah image nginx, mysql, dan lainnya diunduh.

    Saat kamu mengetik docker run nginx, urutan kejadiannya seperti ini:

    1. Client mengirim permintaan ke daemon lewat socket.
    2. Daemon mengecek apakah image nginx sudah ada di disk lokal.
    3. Kalau belum ada, daemon melakukan pull dari registry (Docker Hub) dan menyimpannya di lokal.
    4. Daemon membuat container baru dari image itu, menambahkan writable layer di atasnya.
    5. Daemon menjalankan proses utama container, dalam kasus Nginx yaitu web servernya.

    Pemahaman alur ini kepakai terus sampai level produksi. Di tim Arrazy, alur yang sama persis kami pakai saat men-deploy backend Go dan Laravel milik klien ke server, hanya saja registry-nya kadang privat, bukan Docker Hub.

    Praktik: Menjalankan Nginx di Container Pertamamu

    Cukup teori. Jalankan perintah ini:

    docker run -d -p 8080:80 --name webku nginx:1.27

    Penjelasan singkat flag-nya. -d membuat container jalan di background. -p 8080:80 menyambungkan port 8080 di komputermu ke port 80 di dalam container. --name webku memberi nama supaya gampang dirujuk. Detail lengkap soal port mapping dan mode detach dibahas khusus di bagian 4 seri ini, sekarang cukup pakai dulu.

    Karena ini pertama kali, Docker akan pull image dulu. Outputnya kurang lebih:

    Unable to find image 'nginx:1.27' locally
    1.27: Pulling from library/nginx
    a480a496ba95: Pull complete
    f3ace1b8ce45: Pull complete
    11d6fdd0e8a7: Pull complete
    Digest: sha256:...
    Status: Downloaded newer image for nginx:1.27
    3f9c1d2e8b7a...

    Baris terakhir yang berupa deretan huruf dan angka itu adalah ID container barumu. Sekarang lihat container yang sedang jalan:

    docker ps

    Output yang diharapkan:

    CONTAINER ID   IMAGE        COMMAND                  CREATED          STATUS          PORTS                  NAMES
    3f9c1d2e8b7a   nginx:1.27   "/docker-entrypoint.…"   10 seconds ago   Up 9 seconds    0.0.0.0:8080->80/tcp   webku

    Perhatikan kolom STATUS bertuliskan Up. Artinya container hidup dan proses Nginx sedang berjalan di dalamnya. Sekarang buka browser dan akses http://localhost:8080. Kamu akan melihat halaman default dengan tulisan Welcome to nginx! di bagian atasnya. Kalau lebih suka lewat terminal:

    curl http://localhost:8080

    Responsnya HTML yang diawali <title>Welcome to nginx!</title>. Web server pertamamu di Docker sudah jalan.

    Membuktikan Satu Image Bisa Jadi Banyak Container

    Sekarang buktikan analogi cetakan tadi. Jalankan container kedua dari image yang sama, dengan nama dan port berbeda:

    docker run -d -p 8081:80 --name webku2 nginx:1.27

    Kali ini tidak ada proses pull karena image sudah ada di lokal, jadi container langsung jalan dalam waktu kurang dari satu detik. Cek dengan docker ps, sekarang ada dua container, webku dan webku2, dua-duanya dari image nginx:1.27 yang sama. Buka http://localhost:8081, hasilnya halaman Nginx juga. Satu cetakan, dua kue.

    Siklus Hidup Container: Created, Running, Exited

    Container punya beberapa status utama yang perlu kamu hafal:

    • Created: container sudah dibuat dari image tapi prosesnya belum dijalankan. Terjadi kalau kamu pakai docker create tanpa docker start.
    • Running: proses utama di dalam container sedang berjalan. Inilah yang muncul di docker ps.
    • Exited: proses utamanya sudah berhenti, entah karena selesai, error, atau dihentikan manual.

    Coba hentikan container kedua:

    docker stop webku2

    Lalu jalankan docker ps lagi. Container webku2 hilang dari daftar. Nah, di sinilah banyak pemula bingung: containernya ke mana?

    Jawabannya, container itu tidak hilang. Dia masih ada di disk dengan status Exited, hanya saja docker ps secara default cuma menampilkan container yang running. Untuk melihat semuanya, tambahkan flag -a:

    docker ps -a

    Output:

    CONTAINER ID   IMAGE        COMMAND                  CREATED         STATUS                     PORTS                  NAMES
    7b2e4a1c9d03   nginx:1.27   "/docker-entrypoint.…"   5 minutes ago   Exited (0) 30 seconds ago                         webku2
    3f9c1d2e8b7a   nginx:1.27   "/docker-entrypoint.…"   8 minutes ago   Up 8 minutes               0.0.0.0:8080->80/tcp   webku

    Container Exited bisa dihidupkan lagi dengan docker start webku2, lengkap dengan isi writable layer-nya yang masih utuh. Kalau memang sudah tidak dibutuhkan, hapus permanen dengan:

    docker stop webku2
    docker rm webku2

    Ingat, docker rm hanya menghapus container. Image nginx:1.27 tetap ada di disk dan bisa dipakai membuat container baru kapan saja. Menghapus image adalah perintah lain, yaitu docker rmi, dan hanya bisa dilakukan kalau tidak ada container yang masih memakainya.

    Kenapa Container Langsung Exit? Kasus docker run ubuntu

    Ini kebingungan paling klasik. Coba jalankan:

    docker run -d --name coba-ubuntu ubuntu:24.04

    Lalu cek docker ps. Kosong, tidak ada coba-ubuntu. Cek docker ps -a, ternyata statusnya Exited (0) padahal baru saja dijalankan. Kenapa Nginx bertahan hidup tapi Ubuntu langsung mati?

    Aturannya satu: container hanya hidup selama proses utamanya hidup. Image nginx proses utamanya adalah web server yang memang dirancang berjalan terus menerus di foreground, menunggu request. Image ubuntu proses utamanya adalah bash. Karena container jalan di background tanpa terminal interaktif, bash tidak punya input apa pun, jadi dia langsung selesai. Bash selesai, container ikut exit. Ini bukan error, ini perilaku yang benar.

    Kalau mau masuk ke dalam Ubuntu dan main-main di shell-nya, jalankan dengan mode interaktif:

    docker run -it --name ubuntu-interaktif ubuntu:24.04 bash

    Flag -it memberi container terminal interaktif, jadi bash tetap hidup dan kamu langsung berada di dalam container. Prompt-mu berubah jadi seperti root@a1b2c3d4e5f6:/#. Ketik exit untuk keluar, dan begitu bash berhenti, container pun berstatus Exited. Konsisten dengan aturannya.

    Troubleshooting: Error yang Sering Dialami Pemula

    Error: port is already allocated

    docker: Error response from daemon: ... bind: address already in use

    Penyebab: port 8080 di komputermu sudah dipakai, entah oleh container lain atau aplikasi lain. Solusi: pakai port host lain, misalnya -p 8082:80, atau cari siapa yang memakai port itu dengan docker ps lalu stop container yang bentrok.

    Error: the container name is already in use

    docker: Error response from daemon: Conflict. The container name "/webku" is already in use

    Penyebab: nama container harus unik, dan container lama bernama sama masih ada meskipun statusnya Exited. Solusi: hapus dulu container lama dengan docker rm webku, atau pakai nama lain.

    Error: Cannot connect to the Docker daemon

    Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

    Penyebab: dockerd tidak jalan, atau user-mu belum punya izin mengakses socket Docker. Solusi di Ubuntu: jalankan sudo systemctl start docker. Kalau masalahnya izin, pastikan user sudah masuk grup docker seperti dibahas di bagian 1, lalu logout dan login lagi. Di Windows, pastikan aplikasi Docker Desktop sedang berjalan.

    Container Nginx jalan tapi browser tidak bisa akses

    Penyebab paling umum: lupa flag -p saat docker run, jadi port container tidak tersambung ke host. Cek kolom PORTS di docker ps. Kalau kosong, hapus container itu dan jalankan ulang dengan -p 8080:80. Port mapping tidak bisa ditambahkan ke container yang sudah terlanjur dibuat.

    Rangkuman dan Lanjut ke Bagian 3

    Hari ini kamu sudah memegang konsep paling fundamental di Docker. Image itu cetakan yang read-only, container itu instance hidup yang dibuat dari cetakan tersebut, dan satu image bisa melahirkan banyak container yang saling independen. Kamu juga sudah tahu alur kerja client, daemon, dan registry, plus aturan emas bahwa container hanya hidup selama proses utamanya hidup.

    Pola kerja ini sama persis dengan yang dipakai di dunia kerja. Saat tim Arrazy membangun sistem aplikasi untuk klien, satu image backend yang sama bisa dijalankan sebagai container di laptop developer, di server staging, dan di server produksi tanpa ada cerita “di laptop saya jalan kok”.

    Bagian berikutnya, “Perintah Dasar Docker yang Wajib Dikuasai Pemula”, akan membedah perintah harian seperti docker ps, docker images, docker rm, dan kawan-kawannya secara sistematis. Artikelnya terbit menyusul, pantau daftarnya di halaman Belajar Docker dari Nol.

    Referensi

  • Contoh Auto-Reply WhatsApp Bisnis yang Tidak Kaku dan Siap Pakai

    Contoh Auto-Reply WhatsApp Bisnis yang Tidak Kaku dan Siap Pakai

    Kalau kamu pakai WhatsApp Business, ada tiga fitur auto-reply bawaan yang bisa langsung dipakai tanpa aplikasi tambahan: salam pembuka (greeting message) untuk orang yang baru pertama kali chat, pesan di luar jam kerja (away message), dan balasan cepat (quick reply) untuk pertanyaan yang itu-itu terus. Ketiganya gratis dan sudah ada di dalam aplikasi. Masalahnya, kebanyakan contoh auto-reply yang beredar terasa kaku seperti mesin penjawab kantor. Pelanggan baca satu kalimat, langsung malas melanjutkan.

    Padahal resep auto-reply yang enak dibaca itu sederhana, cuma tiga hal: jujur bilang ini pesan otomatis, kasih ekspektasi kapan dibalas manusia, dan sisipkan info yang bisa langsung dipakai seperti jam buka, link katalog, atau alamat. Di artikel ini kita bahas cara setting ketiga fitur tadi secara singkat, lalu masuk ke bagian utamanya: contoh siap pakai untuk berbagai situasi yang tinggal kamu sesuaikan dengan bisnismu.

    Tiga Fitur Auto-Reply Bawaan WhatsApp Business

    Sebelum ke contoh, kenalan dulu sama tiga fiturnya biar tahu kapan masing-masing dipakai. Semuanya ada di menu yang sama: buka WhatsApp Business, ketuk ikon titik tiga di kanan atas, pilih Perangkat bisnis (Business tools). Di situ kamu akan menemukan tiga menu ini.

    • Salam pembuka (greeting message). Terkirim otomatis ke orang yang chat pertama kali, atau yang sudah tidak chat selama 14 hari. Fungsinya seperti sapaan pertama waktu pelanggan masuk toko. Aktifkan, tulis pesannya, pilih penerima, selesai.
    • Pesan di luar jam kerja (away message). Terkirim otomatis saat kamu tidak bisa balas, misalnya malam hari atau hari libur. Kamu bisa atur jadwalnya: selalu aktif, jadwal khusus, atau di luar jam bisnis yang sudah kamu isi di profil.
    • Balasan cepat (quick reply). Ini bukan pesan otomatis, tapi template yang kamu panggil manual. Kamu simpan jawaban untuk pertanyaan yang sering muncul, lalu saat ada yang tanya, ketik garis miring (/) di kolom chat dan pilih templatenya. Hemat waktu tanpa terasa seperti robot, karena kamu tetap yang menekan tombol kirim.

    Catatan penting: greeting dan away message hanya terkirim kalau HP tersambung internet. Jadi kalau HP mati total, pesan otomatisnya juga tidak jalan.

    Prinsip Auto-Reply yang Tidak Kaku

    Auto-reply terasa menyebalkan biasanya bukan karena otomatis, tapi karena berpura-pura tidak otomatis, atau karena tidak memberi informasi apa-apa. Tiga prinsip ini yang membedakan auto-reply yang membantu dan yang bikin ilfeel.

    • Jujur bilang ini pesan otomatis. Pelanggan tidak masalah dibalas robot, yang bikin kesal itu merasa dibohongi. Kalimat sederhana seperti “ini balasan otomatis ya” justru bikin pesan terasa jujur dan sopan.
    • Kasih ekspektasi kapan dibalas manusia. “Kami akan segera membalas” itu kalimat kosong. Ganti dengan yang konkret: “admin balas dalam 1-2 jam di jam kerja” atau “kami buka lagi besok jam 8 pagi”. Pelanggan jadi tahu harus menunggu berapa lama, dan tidak spam chat berkali-kali.
    • Kasih info yang bisa langsung diakses. Selagi menunggu, pelanggan bisa lihat katalog, cek jam buka, atau buka alamat di maps. Auto-reply yang cuma bilang “terima kasih sudah menghubungi kami” itu buang-buang kesempatan.

    Satu tambahan soal gaya bahasa: tulis seperti kamu ngobrol dengan pelanggan di toko. Kalau sehari-hari kamu bilang “kak”, ya pakai “kak”. Kalau pelangganmu perusahaan, pakai bahasa yang lebih rapi. Yang penting konsisten dengan cara kamu balas chat manual, biar tidak terasa ada dua kepribadian berbeda di satu nomor.

    Contoh Salam Pembuka (Greeting Message)

    Untuk toko online

    “Halo, makasih sudah chat Toko Berkah. Ini balasan otomatis ya, admin biasanya balas dalam 1-2 jam di jam kerja (09.00-17.00 WIB). Sambil nunggu, katalog lengkap bisa dilihat di bit.ly/katalogberkah. Kalau mau tanya stok atau harga, langsung tulis saja nama produknya, jadi begitu admin online bisa langsung dijawab tanpa tanya-tanya lagi.”

    Perhatikan strukturnya: sapaan, jujur otomatis, ekspektasi waktu, link katalog, lalu instruksi kecil biar pelanggan menulis pertanyaan lengkap. Instruksi terakhir ini sering dilupakan padahal paling menghemat waktu, karena kamu tidak perlu bolak-balik tanya “produk yang mana kak?”.

    Untuk usaha jasa

    “Halo, terima kasih sudah menghubungi Arunika Studio. Pesan ini otomatis, tapi tenang, chat kamu sudah masuk dan akan dibalas satu per satu di jam kerja (Senin-Sabtu, 08.00-16.00). Biar prosesnya cepat, boleh langsung tulis kebutuhanmu: jenis layanan, perkiraan tanggal, dan lokasi. Nanti kami balas dengan info harga dan ketersediaan jadwal yang sesuai.”

    Untuk usaha jasa, pertanyaan pembuka pelanggan biasanya menggantung, semacam “halo, mau tanya”. Auto-reply yang meminta detail sejak awal memotong satu ronde tanya jawab dan bikin balasan pertamamu langsung berisi penawaran.

    Contoh Pesan di Luar Jam Kerja (Away Message)

    Malam hari

    “Halo, makasih sudah chat. Toko sudah tutup ya, kami buka lagi besok jam 08.00. Pesanmu sudah tercatat dan masuk antrean pertama besok pagi. Sambil nunggu, bisa lihat-lihat katalog dulu di bit.ly/katalogberkah. Selamat istirahat.”

    Hari libur

    “Halo, terima kasih sudah menghubungi kami. Kami sedang libur Lebaran sampai 5 April, jadi balasan akan lebih lama dari biasanya. Chat kamu tetap tersimpan dan kami balas berurutan mulai 6 April. Untuk cek harga dan produk, sementara bisa lihat katalog di bit.ly/katalogberkah. Mohon maaf lahir dan batin, sampai ketemu setelah libur ya.”

    Yang bikin away message hari libur terasa manusiawi adalah tanggal yang jelas. Bandingkan dengan “kami sedang libur, mohon menunggu” yang tidak memberi tahu apa-apa. Dan ingat, begitu masuk kerja lagi, matikan atau update pesannya. Away message Lebaran yang masih aktif bulan Juni itu nyata terjadi di banyak toko.

    Sedang ramai, balasan lebih lama

    “Halo, makasih sudah menghubungi kami. Hari ini chat sedang ramai sekali (efek promo gajian, hehe), jadi estimasi balas sekitar 2-3 jam. Pesanmu tidak hilang kok, semua dibalas sesuai urutan. Kalau pertanyaannya soal harga atau stok, coba cek dulu bit.ly/katalogberkah, siapa tahu jawabannya sudah ada di sana.”

    Away message tidak harus soal jam tutup. Saat promo besar atau menjelang hari raya, mengaktifkan pesan seperti ini beberapa jam saja bisa mengurangi chat susulan bernada “kok belum dibalas”.

    Contoh Balasan Cepat (Quick Reply)

    Quick reply kamu simpan dengan shortcut, misalnya /harga, /ongkir, /alamat. Saat ada yang tanya, ketik shortcutnya dan pesan lengkap langsung muncul. Karena dikirim manual, gayanya boleh lebih personal daripada greeting dan away message.

    Shortcut /harga untuk pertanyaan harga dan katalog

    “Ini daftar harga terbaru kami ya kak: bit.ly/katalogberkah. Harga di katalog sudah harga bersih, belum termasuk ongkir. Kalau ada produk yang mau ditanyakan lebih detail, sebut saja namanya, nanti aku bantu cek stok dan variannya.”

    Shortcut /ongkir untuk pertanyaan ongkos kirim

    “Kami kirim dari Purwokerto via JNE, J&T, dan SiCepat kak. Boleh info alamat lengkapnya sampai kecamatan? Nanti aku hitungkan ongkirnya sekalian estimasi berapa hari sampai, terus kakak tinggal pilih kurir yang paling pas.”

    Shortcut /alamat untuk pertanyaan lokasi

    “Toko kami di Jl. Melati No. 12, Purwokerto, sebelah apotek K24 ya kak. Ini link Google Maps biar gampang: maps.app.goo.gl/tokoberkah. Buka Senin-Sabtu jam 09.00-17.00, Minggu libur. Ditunggu kedatangannya.”

    Tipsnya satu: setelah quick reply muncul di kolom chat, biasakan baca sekilas sebelum kirim. Kadang perlu tambah satu kalimat kecil yang menyambung ke pertanyaan pelanggan, dan sentuhan ini yang bikin template terasa seperti balasan manusia.

    Kesalahan yang Bikin Auto-Reply Jadi Menyebalkan

    • Sok akrab berlebihan. Sapaan seperti “haiii kakak sayang, bestie belanja online” dari toko yang belum pernah kamu hubungi itu bukan ramah, tapi aneh. Ramah itu cukup hangat dan jelas. Kedekatan biarkan tumbuh dari obrolan, bukan dipaksakan di pesan pertama.
    • Janji balas cepat tapi tidak ditepati. Menulis “dibalas dalam 5 menit” padahal kamu baru sempat pegang HP dua jam sekali cuma bikin pelanggan kecewa dua kali. Lebih baik janjikan waktu yang realistis lalu balas lebih cepat dari janji, daripada sebaliknya.
    • Tidak pernah diupdate. Jam buka sudah berubah tapi auto-reply masih menyebut jam lama, link promo yang sudah berakhir, ucapan hari raya yang lewat berbulan-bulan. Jadwalkan cek auto-reply sebulan sekali, isinya cuma tiga pesan pendek, lima menit selesai.

    Batas Fitur Bawaan: Kapan Kamu Butuh Chatbot

    Perlu jujur juga soal batasnya. Greeting dan away message itu pesan satu arah: isinya sama untuk semua orang, apa pun pertanyaannya. Dia tidak bisa membedakan orang yang tanya harga dengan orang yang mau komplain, tidak bisa cek stok, dan tidak bisa menjawab pertanyaan spesifik seperti “ready warna hitam ukuran L?”. Quick reply pun tetap butuh kamu online untuk memilih dan mengirimnya.

    Untuk kebanyakan usaha kecil, itu sudah cukup. Tapi kalau volume chat sudah ratusan per hari dan mayoritas pertanyaannya berulang, di situ bedanya dengan chatbot custom mulai terasa: chatbot bisa memahami isi pertanyaan, menjawab sesuai konteks, bahkan terhubung ke data stok atau pesanan. Kalau bisnismu sudah di tahap itu, bisa lihat gambarannya di layanan chatbot WhatsApp AI, dan kalau mau tahu seperti apa alur dialognya, ada contoh percakapan chatbot toko online yang bisa jadi referensi.

    Mulai dari Satu Pesan Dulu

    Tidak perlu langsung menyiapkan semuanya. Mulai dari yang paling terasa dampaknya: away message untuk malam hari, karena chat yang masuk saat kamu tidur itu yang paling sering hilang tanpa jawaban. Ambil salah satu contoh di atas, ganti nama toko, jam buka, dan linknya, lalu aktifkan. Besok pagi kamu bisa cek sendiri bedanya: pelanggan yang semalam chat tidak lagi bertanya “halo, ada orang?” tapi sudah langsung menyebut produk yang mereka mau.

  • Laporan Keuangan Sederhana untuk Usaha Kecil Tanpa Akuntansi

    Laporan Keuangan Sederhana untuk Usaha Kecil Tanpa Akuntansi

    Anda tidak perlu paham akuntansi untuk tahu kondisi keuangan usaha. Cukup tiga catatan: kas harian, untung rugi bulanan, dan utang piutang. Tiga itu saja sudah menjawab pertanyaan paling penting: uang saya ke mana, usaha ini untung atau tidak, dan siapa yang belum bayar.

    Modalnya juga murah. Buku tulis atau spreadsheet gratis di HP sudah cukup, dan Anda bisa mulai malam ini. Supaya gampang dibayangkan, sepanjang artikel ini kita pakai satu contoh yang sama: katering rumahan Bu Rina, yang melayani nasi kotak dan pesanan harian dari rumahnya sendiri.

    Kenapa saldo rekening bukan laporan keuangan

    Banyak pemilik usaha kecil menilai kesehatan usahanya dari satu angka: saldo rekening. Kalau saldonya naik, berarti aman. Sayangnya angka itu sering menipu, karena tiga alasan.

    Pertama, uang pribadi dan uang usaha campur. Saldo rekening Bu Rina berisi hasil jualan nasi kotak, tapi juga transferan arisan, uang dari suami, dan sisa THR. Angka Rp 12 juta di rekening tidak menjawab berapa yang benar-benar milik usaha.

    Kedua, utang tidak kelihatan di saldo. Bu Rina masih punya tagihan Rp 900 ribu di toko sembako langganan yang belum dibayar. Saldo rekeningnya tidak berkurang, tapi kewajibannya nyata. Kalau semua utang dilunasi hari ini, saldonya langsung menyusut.

    Ketiga, untung semu. Minggu ini ada kantor yang bayar DP pesanan 200 kotak untuk acara bulan depan. Saldo melonjak, rasanya untung besar. Padahal uang itu belum jadi milik Bu Rina, karena bahannya belum dibelanjakan dan pesanannya belum dikerjakan. Saldo naik bukan berarti untung. Saldo turun juga bukan berarti rugi.

    Karena itu usaha sekecil apa pun butuh catatan. Bukan laporan ala kantor akuntan, cukup tiga catatan berikut.

    Catatan pertama: kas harian, semua uang masuk dan keluar

    Ini catatan paling dasar. Setiap uang yang masuk dan keluar karena urusan usaha, dicatat hari itu juga. Bentuknya bebas: tunai, transfer, QRIS, semuanya masuk satu catatan.

    Formatnya cukup empat kolom: tanggal, keterangan, masuk, keluar. Contoh catatan Bu Rina di satu hari:

    • Pembayaran 50 nasi kotak PT Maju: masuk Rp 1.250.000
    • Belanja ayam dan sayur di pasar: keluar Rp 600.000
    • Isi gas 2 tabung: keluar Rp 44.000
    • Bensin antar pesanan: keluar Rp 25.000
    • Pesanan harian 10 porsi, bayar tunai: masuk Rp 250.000

    Selesai. Tidak ada istilah debit kredit, tidak ada jurnal. Yang penting jujur dan lengkap, termasuk pengeluaran kecil seperti parkir atau plastik kemasan. Pengeluaran receh inilah yang paling sering bocor tanpa ketahuan.

    Satu kebiasaan kecil yang sangat membantu: saat mencatat pengeluaran, beri tanda mana yang belanja bahan dan mana yang biaya rutin. Cukup tulis huruf B untuk bahan dan R untuk rutin di ujung baris. Kebiasaan sepele ini membuat rekap akhir bulan selesai dalam hitungan menit, bukan berjam-jam memilah struk.

    Catatan kas harian gunanya dua. Anda tahu ke mana uang mengalir setiap hari, dan di akhir bulan Anda punya bahan mentah untuk catatan kedua.

    Catatan kedua: untung rugi bulanan yang bisa dihitung sendiri

    Sebulan sekali, catatan kas harian direkap jadi satu hitungan sederhana: omzet dikurangi modal bahan, dikurangi biaya rutin, ketemu untung bersih.

    Begini rekap Bu Rina di bulan lalu:

    • Omzet (semua penjualan): Rp 15.000.000
    • Belanja bahan (ayam, beras, sayur, bumbu, kemasan): Rp 8.000.000
    • Biaya rutin (gas, listrik, bensin antar, kuota, upah asisten masak): Rp 2.500.000
    • Untung kotor: Rp 4.500.000

    Angka Rp 4,5 juta ini yang sering dikira untung bersih. Nanti kita koreksi di bagian berikutnya, karena masih ada satu biaya besar yang belum dihitung.

    Rekap bulanan seperti ini membuka mata dengan cepat. Bu Rina baru sadar belanja bahannya lebih dari separuh omzet. Dari situ dia bisa cek, apakah harga jualnya masih masuk akal, atau selama ini dia pasang harga cuma ikut harga tetangga. Kalau Anda juga ragu harga jual sudah menutup semua biaya nyata, cara menghitungnya kami bahas di artikel menentukan harga tanpa ikut perang harga.

    Satu aturan penting: rekap ini pakai angka dari catatan kas, bukan dari ingatan. Ingatan selalu lebih optimis daripada kenyataan.

    Catatan ketiga: utang piutang, siapa belum bayar siapa

    Catatan ketiga menjawab dua pertanyaan: siapa yang belum bayar ke kita, dan kita belum bayar ke siapa.

    Di buku Bu Rina, isinya seperti ini:

    • Piutang: PT Maju belum bayar pesanan minggu lalu Rp 1.800.000, jatuh tempo tanggal 5
    • Utang: toko sembako Rp 900.000, janji bayar akhir bulan

    Tanpa catatan ini, piutang gampang menguap. Pelanggan kantor biasa bayar mundur, dan kalau tidak ditagih karena lupa, uang itu hilang begitu saja. Bu Rina pernah kelupaan menagih pesanan arisan Rp 400 ribu sampai tiga bulan. Itu setara untung bersih beberapa hari kerja.

    DP juga sebaiknya masuk catatan ini. DP 200 kotak dari kantor tadi bisa ditulis sebagai kewajiban: uang sudah diterima, tapi pesanannya belum dikerjakan. Dengan begitu Bu Rina tidak tergoda memakai uang DP untuk keperluan lain, lalu bingung cari modal bahan saat hari pengerjaan tiba.

    Sebaliknya, catatan utang membuat Anda tidak kaget. Saldo rekening yang kelihatan gemuk jadi terbaca apa adanya: sebagian bukan milik Anda, tapi milik toko sembako yang menunggu dibayar.

    Cukup satu halaman khusus di buku yang sama, atau satu sheet terpisah di spreadsheet. Setiap ada yang lunas, coret. Sederhana, tapi inilah catatan yang paling sering menyelamatkan arus kas usaha kecil.

    Kesalahan paling umum: gaji sendiri tidak dihitung

    Sekarang kita koreksi angka Bu Rina tadi. Untung kotornya Rp 4,5 juta. Tapi siapa yang belanja ke pasar jam 4 pagi, masak sampai siang, dan antar pesanan sore hari? Bu Rina sendiri. Selama ini kerja itu dianggap gratis.

    Padahal kalau posisi itu digantikan orang lain, Bu Rina harus menggaji minimal Rp 2 juta sebulan. Jadi hitungan yang jujur seperti ini:

    • Untung kotor: Rp 4.500.000
    • Gaji pemilik (upah kerja Bu Rina sendiri): Rp 2.000.000
    • Untung bersih usaha: Rp 2.500.000

    Kenapa ini penting? Karena tanpa gaji sendiri, usaha kelihatan untung padahal buntung. Bayangkan kalau untung kotornya cuma Rp 1,5 juta. Kelihatannya masih positif. Kenyataannya Bu Rina kerja penuh waktu dengan bayaran di bawah upah asistennya sendiri. Lebih baik dia tahu itu dari angka, bukan dari badan yang capek tanpa hasil.

    Gaji sendiri juga membuat keputusan lebih jernih. Kalau setelah gaji pemilik usaha masih untung, berarti usahanya sehat dan layak dibesarkan. Kalau untungnya habis untuk gaji pemilik saja, berarti ada yang harus dibenahi: harga jual, porsi belanja bahan, atau volume penjualan.

    Pisahkan rekening pribadi dan usaha, mulai malam ini

    Dari semua langkah di artikel ini, memisahkan rekening adalah yang paling murah dengan dampak paling besar. Buka satu rekening atau e-wallet khusus usaha. Semua pembayaran pelanggan masuk ke situ, semua belanja usaha keluar dari situ.

    Efeknya langsung terasa. Catatan kas jadi jauh lebih gampang, karena mutasi rekening usaha otomatis jadi cadangan catatan Anda. Uang pribadi tidak lagi diam-diam menambal usaha, dan uang usaha tidak diam-diam kepakai untuk jajan keluarga. Bu Rina cukup transfer gaji Rp 2 juta ke rekening pribadinya setiap awal bulan. Sisanya tetap di rekening usaha sebagai modal kerja.

    Soal ritme, tidak perlu ambisius. Yang realistis begini:

    • Harian: 5 menit tiap malam, catat semua uang masuk keluar hari itu
    • Bulanan: 30 menit tiap awal bulan, rekap untung rugi dan periksa daftar utang piutang

    Lima menit semalam itu setara satu scroll pendek media sosial. Kuncinya bukan rajin, tapi rutin. Catatan yang bolong dua minggu hampir pasti ditinggalkan. Catatan yang diisi tiap malam, walau cuma tiga baris, akan bertahan bertahun-tahun.

    Kalau suatu malam kelewat, jangan langsung menyerah. Besoknya isi dari ingatan dan mutasi rekening usaha, lalu lanjut seperti biasa. Catatan yang 90 persen lengkap jauh lebih berguna daripada niat catatan sempurna yang berhenti di minggu kedua.

    Lalu sampai kapan cara manual ini cukup? Untuk katering rumahan seperti punya Bu Rina, buku tulis atau spreadsheet bisa dipakai bertahun-tahun. Batasnya baru terasa ketika transaksi mencapai ratusan per hari, kasir lebih dari satu orang, atau cabang mulai lebih dari satu tempat. Di titik itu, mencatat manual mulai memakan waktu dan angkanya rawan selisih antar orang. Kalau usaha Anda sudah sampai di fase itu, biasanya sudah waktunya pindah ke sistem aplikasi yang dibangun sesuai alur usaha Anda, supaya pencatatan berjalan otomatis dari transaksi.

    Tapi itu urusan nanti. Malam ini, cukup siapkan satu buku tulis atau satu spreadsheet baru. Buat empat kolom, catat transaksi hari ini, lalu tulis daftar siapa yang belum bayar ke Anda. Dalam sebulan, Anda akan tahu kondisi usaha Anda lebih jelas daripada bertahun-tahun hanya menatap saldo rekening.

  • Belajar Golang dari Nol #9: Interface, Kontrak yang Bikin Kode Fleksibel

    Belajar Golang dari Nol #9: Interface, Kontrak yang Bikin Kode Fleksibel

    Di Belajar Golang dari Nol #8 kita sudah merapikan kode ke dalam package dan module. Struktur project kamu sekarang sudah bersih. Tapi ada satu masalah yang belum kita sentuh: bagaimana kalau satu tugas bisa dikerjakan dengan banyak cara berbeda? Di sinilah interface masuk. Ini salah satu fitur paling penting di Go, dan kabar baiknya, konsepnya sederhana.

    Masalah: satu tugas, banyak cara

    Bayangkan kamu membangun aplikasi toko online. Setiap kali ada pesanan baru, sistem harus mengirim notifikasi ke pembeli. Ada yang mau lewat WhatsApp, ada yang lewat email, ada yang lewat SMS.

    Dengan bekal sampai bagian 8, kamu mungkin akan menulis begini:

    func KirimNotifikasi(via string, pesan string) error {
    	if via == "wa" {
    		// logika kirim WhatsApp
    	} else if via == "email" {
    		// logika kirim email
    	} else if via == "sms" {
    		// logika kirim SMS
    	}
    	return nil
    }

    Kode ini jalan. Tapi coba pikirkan nasibnya tiga bulan lagi. Perusahaan mau tambah notifikasi lewat Telegram. Kamu harus buka function ini lagi, tambah satu cabang else if lagi. Tambah push notification, tambah cabang lagi. Function-nya makin gemuk, dan setiap perubahan berisiko merusak cabang yang sudah jalan.

    Masalah sebenarnya bukan di if else. Masalahnya, function ini harus tahu detail semua cara pengiriman. Padahal yang dia butuhkan cuma satu hal: sesuatu yang bisa mengirim pesan. Titik.

    Interface itu kontrak

    Interface di Go adalah cara kita menulis kebutuhan itu secara eksplisit. Bentuknya begini:

    type Notifier interface {
    	Kirim(pesan string) error
    }

    Baca deklarasi ini seperti kontrak kerja. Perusahaan pasang lowongan: “kami butuh kurir, syaratnya bisa mengantar paket”. Perusahaan tidak peduli kurirnya naik motor, mobil, atau sepeda. Selama bisa mengantar paket, kontrak terpenuhi, dia bisa dipekerjakan.

    Notifier adalah kontrak versi kode. Isinya satu syarat: siapa pun yang mau disebut Notifier harus punya method Kirim yang menerima string dan mengembalikan error. Interface tidak berisi implementasi sama sekali. Dia cuma daftar syarat.

    Implementasi implisit, ciri khas Go

    Di bahasa lain seperti Java atau PHP, kamu harus menulis implements Notifier secara eksplisit di deklarasi class. Go tidak begitu. Di Go, sebuah type otomatis memenuhi interface kalau dia punya semua method yang disyaratkan. Tidak ada kata kunci implements. Tidak ada pendaftaran apa pun.

    Kembali ke analogi kontrak kerja: kamu tidak perlu sertifikat bertuliskan “saya kurir”. Kalau kamu terbukti bisa mengantar paket, kamu memenuhi syarat. Kemampuanmu yang bicara, bukan labelmu.

    Kenapa ini enak? Dua alasan:

    • Kamu bisa membuat interface untuk type yang bukan milikmu. Misalnya type dari library orang lain sudah punya method yang cocok, dia otomatis memenuhi interface buatanmu tanpa mengubah kode library itu.
    • Kode implementasi tidak perlu tahu interface apa saja yang dia penuhi. Struct WhatsAppSender cukup fokus mengirim WhatsApp. Urusan dia dipakai sebagai Notifier atau bukan, itu urusan pemakainya.

    Contoh utuh: sistem notifikasi

    Sekarang kita rakit semuanya. Dua struct, satu interface, satu function yang menerima interface:

    package main
    
    import (
    	"errors"
    	"fmt"
    )
    
    type Notifier interface {
    	Kirim(pesan string) error
    }
    
    type WhatsAppSender struct {
    	Nomor string
    }
    
    func (w WhatsAppSender) Kirim(pesan string) error {
    	if w.Nomor == "" {
    		return errors.New("nomor WhatsApp kosong")
    	}
    	fmt.Println("[WA ke", w.Nomor+"]", pesan)
    	return nil
    }
    
    type EmailSender struct {
    	Alamat string
    }
    
    func (e EmailSender) Kirim(pesan string) error {
    	if e.Alamat == "" {
    		return errors.New("alamat email kosong")
    	}
    	fmt.Println("[Email ke", e.Alamat+"]", pesan)
    	return nil
    }
    
    func ProsesNotifikasi(n Notifier, pesan string) {
    	err := n.Kirim(pesan)
    	if err != nil {
    		fmt.Println("Gagal kirim:", err)
    	}
    }
    
    func main() {
    	wa := WhatsAppSender{Nomor: "0812xxxx1234"}
    	email := EmailSender{Alamat: "budi@tokokopi.id"}
    
    	ProsesNotifikasi(wa, "Pesanan kamu sudah dikirim")
    	ProsesNotifikasi(email, "Pesanan kamu sudah dikirim")
    }

    Perhatikan ProsesNotifikasi. Parameternya bertipe Notifier, bukan WhatsAppSender atau EmailSender. Function ini tidak tahu dan tidak peduli pesan dikirim lewat apa. Dia cuma pegang kontrak: apa pun yang masuk, pasti bisa dipanggil method Kirim-nya.

    Output program di atas:

    [WA ke 0812xxxx1234] Pesanan kamu sudah dikirim
    [Email ke budi@tokokopi.id] Pesanan kamu sudah dikirim

    Karena Notifier adalah type biasa, kamu juga bisa membuat slice berisi campuran implementasi. Ini pola broadcast yang sering dipakai:

    	daftar := []Notifier{
    		WhatsAppSender{Nomor: "0812xxxx1234"},
    		EmailSender{Alamat: "budi@tokokopi.id"},
    		EmailSender{},
    	}
    
    	for _, n := range daftar {
    		ProsesNotifikasi(n, "Promo akhir bulan dimulai")
    	}

    Elemen ketiga sengaja dibuat kosong supaya kelihatan jalur error-nya:

    [WA ke 0812xxxx1234] Promo akhir bulan dimulai
    [Email ke budi@tokokopi.id] Promo akhir bulan dimulai
    Gagal kirim: alamat email kosong

    Sekarang bandingkan dengan versi if else di awal. Mau tambah Telegram? Buat struct TelegramSender dengan method Kirim, selesai. ProsesNotifikasi tidak disentuh sama sekali. Kode lama tetap aman, fitur baru tinggal ditambahkan. Ini yang dimaksud kode fleksibel.

    Interface kecil itu idiomatik

    Perhatikan Notifier cuma punya satu method. Ini bukan kebetulan. Di Go, interface yang bagus justru yang kecil. Makin sedikit syarat di kontrak, makin banyak type yang bisa memenuhinya, makin fleksibel kodemu.

    Standard library Go penuh contoh interface satu method. Dua yang paling sering kamu temui:

    error ternyata interface

    Masih ingat bagian 3 waktu kita belajar error handling? Waktu itu saya bilang error akan kita bahas lebih dalam nanti. Ini dia bayarannya. error yang selama ini kamu pakai sebenarnya interface bawaan:

    type error interface {
    	Error() string
    }

    Artinya, type apa pun yang punya method Error() string bisa dipakai sebagai error. Itulah kenapa kamu bisa mengembalikan hasil errors.New, hasil fmt.Errorf, atau error buatan library mana pun dari function yang sama. Semuanya cuma memenuhi satu kontrak kecil.

    fmt.Stringer

    Interface kecil lain yang berguna adalah fmt.Stringer, kontraknya satu method String() string. Kalau struct kamu memenuhinya, fmt.Println otomatis memakai method itu saat mencetak:

    package main
    
    import "fmt"
    
    type Produk struct {
    	Nama  string
    	Harga int
    }
    
    func (p Produk) String() string {
    	return fmt.Sprintf("%s (Rp%d)", p.Nama, p.Harga)
    }
    
    func main() {
    	p := Produk{Nama: "Kopi Gayo 250g", Harga: 85000}
    	fmt.Println(p)
    }

    Tanpa method String, outputnya format default: {Kopi Gayo 250g 85000}. Dengan method itu, outputnya jadi Kopi Gayo 250g (Rp85000). Kamu tidak pernah mendaftarkan apa pun ke package fmt. Dia cuma mengecek: type ini memenuhi kontrak Stringer atau tidak. Implementasi implisit bekerja diam-diam di sini.

    any, interface kosong, dan type assertion

    Ada satu interface ekstrem: interface tanpa syarat sama sekali, ditulis interface{} atau alias modernnya, any. Karena kontraknya kosong, semua type otomatis memenuhinya. Variabel bertipe any bisa diisi apa saja.

    Kedengarannya praktis, tapi hati-hati. Begitu data masuk ke any, compiler tidak bisa lagi menjaga kamu. Kamu kehilangan informasi type, dan itu mahal. Untuk mengambil kembali type aslinya, kamu butuh type assertion:

    var data any = "halo"
    
    s, ok := data.(string)
    if ok {
    	fmt.Println("panjang teks:", len(s))
    } else {
    	fmt.Println("data bukan string")
    }

    Bentuk data.(string) artinya “saya yakin isi data adalah string, tolong keluarkan”. Variabel ok bernilai true kalau tebakanmu benar. Selalu pakai bentuk dua nilai ini. Bentuk satu nilai tanpa ok akan membuat program panic kalau tebakannya salah.

    Aturan praktisnya: pakai any hanya kalau memang tidak ada pilihan, misalnya saat menangani JSON yang strukturnya tidak tentu. Untuk kode sehari-hari, interface dengan kontrak jelas seperti Notifier hampir selalu lebih baik.

    Kapan tidak perlu interface

    Setelah paham interface, ada godaan untuk membuatnya di mana-mana. Tahan dulu. Interface itu abstraksi, dan abstraksi ada biayanya: kode jadi lebih sulit dilacak karena pembaca harus mencari implementasi aslinya.

    Patokan sederhananya:

    • Kalau cuma ada satu implementasi dan belum ada rencana nyata menambah yang lain, pakai struct langsung. Jangan bikin UserServiceInterface hanya karena terlihat rapi.
    • Buat interface saat kebutuhannya muncul: ada implementasi kedua, atau kamu perlu memisahkan kode dari dependensi eksternal.
    • Definisikan interface di sisi pemakai, bukan di sisi implementasi. Ini kebiasaan idiomatik di Go, beda dengan kebiasaan di Java.

    Ingat prinsipnya: interface menjawab masalah “banyak cara untuk satu tugas”. Kalau caranya memang cuma satu, tidak ada masalah yang perlu dijawab.

    Latihan: sistem pembayaran mini

    Sekarang giliran kamu. Kita buat sistem checkout yang menerima beberapa metode pembayaran. Kontraknya satu: bisa membayar sejumlah uang. Aturan validasinya beda per metode. Transfer bank punya minimal nominal, e-wallet dibatasi saldo.

    package main
    
    import (
    	"errors"
    	"fmt"
    )
    
    type MetodePembayaran interface {
    	Bayar(jumlah float64) error
    }
    
    type TransferBank struct {
    	NamaBank   string
    	NoRekening string
    }
    
    func (t TransferBank) Bayar(jumlah float64) error {
    	if t.NoRekening == "" {
    		return errors.New("nomor rekening belum diisi")
    	}
    	if jumlah < 10000 {
    		return errors.New("minimal transfer bank Rp10.000")
    	}
    	fmt.Printf("Transfer Rp%.0f via %s berhasil\n", jumlah, t.NamaBank)
    	return nil
    }
    
    type EWallet struct {
    	Provider string
    	Saldo    float64
    }
    
    func (e EWallet) Bayar(jumlah float64) error {
    	if jumlah > e.Saldo {
    		return errors.New("saldo " + e.Provider + " tidak cukup")
    	}
    	fmt.Printf("Bayar Rp%.0f pakai %s berhasil\n", jumlah, e.Provider)
    	return nil
    }
    
    func Checkout(m MetodePembayaran, total float64) {
    	fmt.Printf("Memproses pembayaran Rp%.0f...\n", total)
    	err := m.Bayar(total)
    	if err != nil {
    		fmt.Println("Pembayaran gagal:", err)
    		return
    	}
    	fmt.Println("Pesanan dikonfirmasi")
    }
    
    func main() {
    	bca := TransferBank{NamaBank: "BCA", NoRekening: "1234567890"}
    	dana := EWallet{Provider: "Dana", Saldo: 50000}
    
    	Checkout(bca, 150000)
    	Checkout(dana, 35000)
    	Checkout(dana, 100000)
    	Checkout(TransferBank{NamaBank: "BRI"}, 150000)
    }

    Jalankan dengan go run main.go. Outputnya:

    Memproses pembayaran Rp150000...
    Transfer Rp150000 via BCA berhasil
    Pesanan dikonfirmasi
    Memproses pembayaran Rp35000...
    Bayar Rp35000 pakai Dana berhasil
    Pesanan dikonfirmasi
    Memproses pembayaran Rp100000...
    Pembayaran gagal: saldo Dana tidak cukup
    Memproses pembayaran Rp150000...
    Pembayaran gagal: nomor rekening belum diisi

    Amati dua hal. Pertama, Checkout memperlakukan semua metode sama rata lewat kontrak MetodePembayaran, padahal aturan validasi di baliknya berbeda. Kedua, dua kasus gagal ditangani rapi tanpa satu pun if else soal jenis metode di dalam Checkout.

    Untuk latihan mandiri, coba tambah KartuKredit dengan validasi limit, lalu masukkan ke pemanggilan Checkout. Kalau kamu tidak perlu mengubah function Checkout sama sekali, berarti kamu sudah paham inti bab ini.

    Rangkuman dan bagian selanjutnya

    Hari ini kamu belajar bahwa interface adalah kontrak: daftar method yang harus dipenuhi, tanpa implementasi. Go memakai implementasi implisit, jadi type cukup punya method yang cocok. Interface kecil seperti error dan fmt.Stringer adalah gaya idiomatik Go. any ada untuk kasus khusus, dan type assertion adalah pintu keluarnya. Terakhir, jangan bikin interface untuk satu implementasi tanpa alasan.

    Di bagian 10 kita masuk ke materi yang membuat Go terkenal: “Goroutine dan Channel: Kenalan dengan Concurrency”. Sampai ketemu di sana.

    Kalau bisnismu butuh aplikasi yang dibangun dengan fondasi rapi seperti ini, tim Arrazy menyediakan jasa pengembangan sistem aplikasi dari perancangan sampai perawatan.

  • Belajar Linux dari Nol #2: Cara Menggunakan Terminal Linux

    Belajar Linux dari Nol #2: Cara Menggunakan Terminal Linux

    Cara menggunakan terminal Linux sebenarnya sederhana: buka aplikasi Terminal (di Ubuntu 24.04 tekan Ctrl+Alt+T), ketik perintah, lalu tekan Enter. Komputer menjalankan perintah itu dan menampilkan hasilnya sebagai teks. Itu saja intinya. Yang membuat pemula sering mundur bukan karena terminalnya susah, tapi karena layar hitam berisi teks terasa asing dibanding klik-klik di tampilan grafis.

    Artikel ini bagian kedua dari seri Belajar Linux dari Nol. Di sini kita bongkar dulu konsepnya: apa itu terminal, shell, dan bash, lalu langsung praktik perintah pertama seperti whoami, date, dan echo. Di akhir artikel kamu juga belajar membaca manual perintah dan mengenali error yang paling sering bikin pemula panik.

    Prasyarat: Ubuntu Sudah Terpasang

    Semua praktik di seri ini memakai Ubuntu 24.04 LTS. Kalau kamu belum menginstalnya, ikuti dulu panduan Belajar Linux dari Nol #1: Cara Install Ubuntu 24.04. Boleh instal langsung di laptop, dual boot, atau lewat virtual machine. Yang penting kamu punya akses ke desktop Ubuntu yang berjalan normal.

    Apa Itu Terminal, Shell, dan Bash

    Tiga istilah ini sering dipakai bergantian, padahal artinya berbeda. Memahami bedanya akan memudahkan kamu membaca dokumentasi dan tutorial lain.

    • Terminal adalah aplikasi jendelanya. Tempat kamu mengetik dan tempat hasil perintah ditampilkan. Di Ubuntu 24.04 aplikasi bawaannya bernama GNOME Terminal (di edisi terbaru namanya GNOME Console di beberapa varian). Istilah lengkapnya terminal emulator, karena dia meniru terminal fisik zaman dulu.
    • Shell adalah program yang membaca ketikan kamu, menerjemahkannya, lalu meminta sistem operasi menjalankannya. Terminal cuma wadah, shell yang benar-benar bekerja.
    • Bash adalah salah satu jenis shell, kependekan dari Bourne Again Shell. Bash adalah shell default di Ubuntu 24.04. Ada shell lain seperti zsh atau fish, tapi sepanjang seri ini kita pakai bash.

    Analogi sederhananya: terminal itu ruang obrolan, shell itu penerjemahnya, dan bash itu bahasa yang dipakai si penerjemah.

    Beda GUI vs CLI dan Kapan CLI Menang

    GUI (Graphical User Interface) adalah antarmuka grafis yang kamu pakai sehari-hari: ikon, jendela, tombol, klik mouse. CLI (Command Line Interface) adalah antarmuka berbasis teks lewat terminal.

    GUI unggul untuk tugas visual seperti mengedit foto atau menonton video. Tapi ada banyak situasi di mana CLI jelas menang:

    • Tugas berulang. Mengganti nama 500 file lewat GUI butuh 500 kali klik. Lewat CLI cukup satu baris perintah.
    • Server tanpa layar. Hampir semua server di dunia berjalan tanpa GUI untuk menghemat resource. Satu-satunya cara mengelolanya ya lewat terminal, biasanya melalui SSH.
    • Otomatisasi. Perintah CLI bisa dirangkai jadi script yang berjalan sendiri, misalnya backup otomatis tiap malam.
    • Presisi. Perintah teks bisa dicatat, diulang persis, dan dibagikan ke orang lain tanpa perlu screenshot langkah demi langkah.

    Di pekerjaan nyata ini bukan teori. Tim Arrazy mengelola server klien untuk berbagai sistem aplikasi yang kami bangun, dan hampir semuanya dikerjakan lewat terminal: deploy aplikasi, cek log, sampai restart service. Skill terminal adalah fondasi yang dipakai setiap hari.

    Anatomi Prompt Terminal Linux

    Buka terminal dengan Ctrl+Alt+T. Kamu akan melihat satu baris teks seperti ini:

    budi@laptop-kerja:~$

    Baris ini disebut prompt. Artinya shell siap menerima perintah. Mari kita bedah bagian-bagiannya:

    Bagian Contoh Arti
    Username budi Nama user yang sedang login
    Pemisah @ Dibaca “di” (budi di laptop-kerja)
    Hostname laptop-kerja Nama komputer
    Direktori aktif ~ Lokasi kamu sekarang. Tanda ~ artinya home directory
    Penanda akhir $ User biasa. Kalau # artinya kamu login sebagai root

    Perhatikan tanda $ dan #. Ini penting saat membaca tutorial di internet. Perintah yang diawali # biasanya dimaksudkan untuk dijalankan sebagai root (administrator), sedangkan $ untuk user biasa. Tanda itu sendiri tidak ikut diketik.

    Anatomi Perintah: Command, Option, Argument

    Hampir semua perintah Linux mengikuti pola yang sama:

    command [option] [argument]

    Contoh nyata:

    ls -l /home
    • Command: ls, program yang dijalankan. Tugasnya menampilkan isi direktori.
    • Option: -l, pengubah perilaku command. Di sini artinya tampilkan dalam format panjang (detail). Option pendek diawali satu strip, option panjang diawali dua strip seperti --help.
    • Argument: /home, target yang dikenai perintah. Di sini direktori yang ingin dilihat isinya.

    Sekali kamu paham pola ini, perintah apa pun jadi mudah dibaca. apt install nginx? Command apt, argumen install dan nginx. Polanya selalu mirip.

    Praktik: Perintah Pertama di Terminal

    Sekarang giliran tangan yang bekerja. Ketik tiap perintah berikut lalu tekan Enter. Jangan cuma dibaca, otot jari juga perlu hafal.

    whoami: Cek User yang Sedang Login

    whoami

    Output yang diharapkan (sesuai username kamu):

    budi

    Terlihat sepele sekarang, tapi perintah ini penting saat nanti kamu berpindah-pindah user atau masuk ke server orang lain dan lupa sedang login sebagai siapa.

    hostname: Cek Nama Komputer

    hostname

    Output:

    laptop-kerja

    Berguna saat kamu membuka banyak terminal ke banyak server sekaligus dan perlu memastikan sedang berada di mesin yang mana.

    date: Tampilkan Tanggal dan Waktu

    date

    Output kira-kira seperti ini:

    Mon Jul 27 09:15:42 AM WIB 2026

    echo: Cetak Teks ke Layar

    echo "Halo, saya sedang belajar Linux"

    Output:

    Halo, saya sedang belajar Linux

    echo kelihatan remeh, tapi nanti jadi andalan saat menulis shell script dan mengecek isi variabel.

    clear: Bersihkan Layar

    clear

    Layar terminal jadi bersih lagi. Riwayat perintah tidak hilang, cuma tampilannya yang digulung ke atas. Alternatifnya tekan Ctrl+L, hasilnya sama.

    Tab Completion: Biarkan Terminal Melengkapi Ketikanmu

    Ini fitur yang wajib jadi kebiasaan sejak hari pertama. Ketik sebagian nama perintah atau nama file, lalu tekan tombol Tab. Shell akan melengkapinya otomatis.

    Coba: ketik who lalu tekan Tab dua kali. Terminal menampilkan semua perintah yang diawali “who”:

    whoami  who  whoopsie-id

    Ketik whoa lalu tekan Tab sekali, langsung jadi whoami. Selain menghemat ketikan, tab completion juga mencegah typo. Kalau kamu menekan Tab dan tidak terjadi apa-apa, kemungkinan besar ejaan awalnya sudah salah. Itu sinyal untuk berhenti dan mengecek.

    Panah Atas: Panggil Ulang Perintah Sebelumnya

    Tekan tombol panah atas, perintah terakhir muncul kembali. Tekan lagi untuk perintah sebelumnya, dan seterusnya. Panah bawah untuk maju lagi. Kamu juga bisa melihat seluruh riwayat dengan perintah history. Kombinasi panah atas plus Enter jauh lebih cepat daripada mengetik ulang perintah panjang.

    Cara Membaca Manual Perintah Linux

    Tidak ada orang yang hafal semua perintah beserta option-nya. Yang membedakan pemula dan orang berpengalaman adalah kebiasaan membuka manual.

    man: Manual Lengkap

    man ls

    Perintah ini membuka halaman manual untuk ls. Isinya lengkap: deskripsi, semua option, sampai contoh. Navigasinya:

    • Panah atas/bawah atau PgUp/PgDn untuk menggulung
    • /kata lalu Enter untuk mencari kata tertentu, n untuk hasil berikutnya
    • q untuk keluar

    Tombol q ini serius perlu diingat. Banyak pemula mengira terminalnya rusak karena tidak bisa mengetik perintah lagi, padahal mereka masih berada di dalam halaman manual.

    –help: Ringkasan Cepat

    Kalau butuh ringkasan singkat tanpa membuka halaman penuh, hampir semua perintah punya option --help:

    ls --help

    Outputnya daftar option beserta penjelasan satu barisnya, langsung tercetak di terminal. Cocok untuk sekadar mengingatkan “option untuk sort by time itu apa ya”. Untuk pemahaman mendalam, tetap buka man.

    Troubleshooting: Error yang Sering Dialami Pemula

    bash: whoyami: command not found

    Ini error paling umum. Contoh:

    budi@laptop-kerja:~$ whoyami
    whoyami: command not found

    Penyebab: hampir selalu typo. Shell mencari program bernama persis seperti yang kamu ketik, dan Linux membedakan huruf besar kecil. Whoami dengan W besar juga dianggap perintah berbeda dan akan gagal.

    Solusi: cek ejaan, lalu biasakan pakai tab completion supaya typo tidak terjadi. Kalau ejaan sudah benar tapi tetap error, kemungkinan programnya memang belum terinstal. Cara instal aplikasi kita bahas di bagian #11 seri ini.

    Terminal Terlihat Hang, Tidak Merespons Apa pun

    Penyebab: sering kali terminal tidak hang, tapi sedang menunggu input dari kamu. Contoh klasik: kamu mengetik echo "halo dengan tanda kutip yang tidak ditutup, lalu Enter. Prompt berubah jadi > dan terminal seolah diam. Sebenarnya bash menunggu kamu menutup tanda kutip itu.

    Solusi: ketik tanda kutip penutup lalu Enter, atau batalkan sekalian dengan Ctrl+C. Kasus lain: kamu tidak sengaja menjalankan program interaktif yang memang menunggu ketikan. Perhatikan bentuk prompt, kalau bukan nama@host:~$ berarti kamu sedang berada di dalam program lain.

    Perintah Berjalan Terus dan Tidak Berhenti

    Penyebab: beberapa perintah memang berjalan terus sampai dihentikan manual, misalnya ping google.com yang mengirim paket tanpa henti.

    Solusi: tekan Ctrl+C untuk mengirim sinyal berhenti. Ini kombinasi penyelamat nomor satu di terminal. Perintah apa pun yang terasa kebablasan, Ctrl+C dulu. Catatan penting: Ctrl+C di terminal bukan untuk copy. Copy paste di GNOME Terminal memakai Ctrl+Shift+C dan Ctrl+Shift+V.

    Terkunci di Halaman man, Tidak Bisa Mengetik Perintah

    Penyebab: kamu masih di dalam pager (tampilan halaman manual). Semua ketikan diperlakukan sebagai navigasi, bukan perintah shell.

    Solusi: tekan q. Kamu langsung kembali ke prompt dan bisa mengetik perintah lagi.

    Rangkuman dan Lanjut ke Bagian Berikutnya

    Hari ini kamu sudah paham beda terminal, shell, dan bash, bisa membaca anatomi prompt dan struktur perintah, menjalankan whoami, hostname, date, echo, dan clear, memanfaatkan tab completion dan history, membuka manual dengan man dan --help, serta tahu cara keluar dari situasi macet dengan Ctrl+C dan q.

    Latihan kecil sebelum lanjut: buka terminal, jalankan kelima perintah tadi tanpa melihat artikel, lalu buka man date dan cari cara menampilkan tanggal dalam format tertentu. Sepuluh menit latihan mandiri lebih melekat daripada membaca ulang.

    Bagian berikutnya, “Belajar Linux dari Nol #3: Perintah Dasar Linux Navigasi”, membahas cara berpindah direktori dengan cd, melihat isi folder dengan ls, dan mengetahui posisi kamu dengan pwd. Artikelnya terbit menyusul, pantau daftar lengkapnya di halaman hub Belajar Linux.

    Referensi