diff --git a/en/content-en/1-berkenalan-dengan-golang.md b/en/content-en/1-berkenalan-dengan-golang.md new file mode 100644 index 000000000..d69e8f6d7 --- /dev/null +++ b/en/content-en/1-berkenalan-dengan-golang.md @@ -0,0 +1,30 @@ +# Belajar Golang (Gratis!) + +**[Golang](https://golang.org/)** (atau biasa disebut dengan **Go**) adalah bahasa pemrograman yang dikembangkan di **Google** oleh **[Robert Griesemer](https://github.com/griesemer)**, **[Rob Pike](https://en.wikipedia.org/wiki/Rob_Pike)**, dan **[Ken Thompson](https://en.wikipedia.org/wiki/Ken_Thompson)** pada tahun 2007 dan mulai diperkenalkan ke publik tahun 2009. + +Penciptaan bahasa Go didasari bahasa **C** dan **C++**, oleh karena itu gaya sintaksnya mirip. + +#### Kelebihan Go + +Go memiliki kelebihan dibanding bahasa lainnya, beberapa di antaranya: + +* Mendukung konkurensi di level bahasa dengan pengaplikasian cukup mudah +* Mendukung pemrosesan data dengan banyak prosesor dalam waktu yang bersamaan *(pararel processing)* +* Memiliki *garbage collector* +* Proses kompilasi sangat cepat +* Bukan bahasa pemrograman yang hirarkial dan bukan *strict* OOP, memberikan kebebasan ke developer perihal bagaimana cara penulisan kode. +* Dependensi dan *tooling* yang disediakan terbilang lengkap. +* Dukungan komunitas sangat bagus. Banyak tools yang tersedia secara gratis dan *open source* yang bisa langsung dimanfaatkan. + +Sudah banyak industri dan perusahaan yg menggunakan Go sampai level production, termasuk di antaranya adalah Google sendiri, dan juga tempat di mana penulis bekerja 😁 + +--- + +Pada buku ini (terutama semua serial chapter A) kita akan belajar tentang dasar pemrograman Go, mulai dari 0, dan gratis. + +![The Go Logo](images/A_introduction_1_logo.png) + +--- + + + diff --git a/en/content-en/2-instalasi-golang.md b/en/content-en/2-instalasi-golang.md new file mode 100644 index 000000000..93cdcfbfa --- /dev/null +++ b/en/content-en/2-instalasi-golang.md @@ -0,0 +1,106 @@ +# A.2. Instalasi Golang + +Hal pertama yang perlu dilakukan sebelum bisa menggunakan Go adalah meng-*install*-nya terlebih dahulu. Panduan instalasi sebenarnya sudah disediakan di situs resmi Go [http://golang.org/doc/install#install](http://golang.org/doc/install#install). + +Di sini penulis mencoba meringkas petunjuk instalasi pada *link* di atas, agar lebih mudah untuk diikuti terutama untuk pembaca yang baru belajar. + +> Go yang digunakan adalah versi **1.22**, direkomendasikan menggunakan versi tersebut. + +URL untuk mengunduh *installer* Go: https://golang.org/dl/. Silakan langsung unduh dari *link* tersebut lalu lakukan proses instalasi, atau bisa mengikuti petunjuk pada chapter ini. + +## A.2.1. Instalasi Go *Stable* + +#### ◉ Instalasi Go di Windows + + 1. Download terlebih dahulu *installer*-nya di [https://golang.org/dl/](https://golang.org/dl/). Pilih *installer* untuk sistem operasi Windows sesuai jenis bit yang digunakan. + + 2. Setelah ter-*download*, jalankan *installer*, klik *next* hingga proses instalasi selesai. *By default* jika anda tidak merubah path pada saat instalasi, Go akan ter-*install* di `C:\go`. *Path* tersebut secara otomatis akan didaftarkan dalam `PATH` *environment variable*. + + 3. Buka *Command Prompt* / *CMD*, eksekusi perintah berikut untuk mengecek versi Go. + + ```bash + go version + ``` + + 4. Jika output adalah sama dengan versi Go yang ter-*install*, menandakan proses instalasi berhasil. + +> Sering terjadi, command `go version` tidak bisa dijalankan meskipun instalasi sukses. Solusinya bisa dengan restart CMD (tutup CMD, kemudian buka lagi). Setelah itu coba jalankan ulang command di atas. + +#### ◉ Instalasi Go di MacOS + +Cara termudah instalasi Go di MacOS adalah menggunakan [Homebrew](http://brew.sh/). + + 1. *Install* terlebih dahulu Homebrew (jika belum ada), caranya jalankan perintah berikut di **terminal**. + + ```bash + $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + ``` + + 2. *Install* Go menggunakan command `brew`. + + ```bash + $ brew install go + ``` + + 3. Tambahkan path binary Go ke `PATH` *environment variable*. + + ```bash + $ echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bash_profile + $ source ~/.bash_profile + ``` + + 4. Jalankan perintah berikut mengecek versi Go. + + ```bash + go version + ``` + + 5. Jika output adalah sama dengan versi Go yang ter-*install*, menandakan proses instalasi berhasil. + +#### ◉ Instalasi Go di Linux + + 1. Unduh arsip *installer* dari [https://golang.org/dl/](https://golang.org/dl/), pilih installer untuk Linux yang sesuai dengan jenis bit komputer anda. Proses download bisa dilakukan lewat CLI, menggunakan `wget` atau `curl`. + + ```bash + $ wget https://storage.googleapis.com/golang/go1... + ``` + + 2. Buka terminal, *extract* arsip tersebut ke `/usr/local`. + + ```bash + $ tar -C /usr/local -xzf go1... + ``` + + 3. Tambahkan path binary Go ke `PATH` *environment variable*. + + ```bash + $ echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bashrc + $ source ~/.bashrc + ``` + + 4. Selanjutnya, eksekusi perintah berikut untuk mengetes apakah Go sudah terinstal dengan benar. + + ```bash + go version + ``` + + 5. Jika output adalah sama dengan versi Go yang ter-*install*, menandakan proses instalasi berhasil. + +## A.2.2. Variabel `GOROOT` + +*By default*, setelah proses instalasi Go selesai, secara otomatis akan muncul *environment variable* `GOROOT`. Isi dari variabel ini adalah lokasi di mana Go ter-*install*. + +Sebagai contoh di Windows, ketika Go di-*install* di `C:\go`, maka path tersebut akan menjadi isi dari `GOROOT`. + +Silakan gunakan command `go env` untuk melihat informasi konfigurasi *environment* yang ada. + +## A.2.3. Instalasi Go *Unstable*/*Development* + +Jika pembaca tertarik untuk mencoba versi development Go, ingin mencoba fitur yang belum dirilis secara official, ada beberapa cara: + +- Instalasi dengan *build from source* https://go.dev/doc/install/source +- Gunakan command `go install`, contohnya seperti `go install golang.org/dl/go1.18beta1@latest`. Untuk melihat versi unstable yang bisa di-install silakan merujuk ke https://go.dev/dl/#unstable + +--- + + diff --git a/en/content-en/A-array.md b/en/content-en/A-array.md new file mode 100644 index 000000000..cfb047bb9 --- /dev/null +++ b/en/content-en/A-array.md @@ -0,0 +1,208 @@ +# A.15. Array + +Array adalah kumpulan data bertipe sama, yang disimpan dalam sebuah variabel. Array memiliki kapasitas yang nilainya ditentukan pada saat pembuatan, menjadikan elemen/data yang disimpan di array tersebut jumlahnya tidak boleh melebihi yang sudah dialokasikan. + +Default nilai tiap elemen array pada awalnya tergantung dari tipe datanya. Jika `int` maka tiap element zero value-nya adalah `0`, jika `bool` maka `false`, dan seterusnya. Setiap elemen array memiliki indeks berupa angka yang merepresentasikan posisi urutan elemen tersebut. Indeks array dimulai dari 0. + +Contoh penerapan array: + +```go +var names [4]string +names[0] = "trafalgar" +names[1] = "d" +names[2] = "water" +names[3] = "law" + +fmt.Println(names[0], names[1], names[2], names[3]) +``` + +Variabel `names` dideklarasikan sebagai `array string` dengan alokasi kapasitas elemen adalah `4` slot. Cara mengisi slot elemen array bisa dilihat di kode di atas, yaitu dengan langsung mengakses elemen menggunakan indeks, lalu mengisinya. + +![Menampilkan elemen array](images/A_array_0_array.png) + +## A.15.1. Pengisian Elemen Array yang Melebihi Alokasi Awal + +Pengisian elemen array pada indeks yang tidak sesuai dengan jumlah alokasi menghasilkan error. Contoh: jika array memiliki 4 slot, maka pengisian nilai slot 5 seterusnya adalah tidak valid. + +```go +var names [4]string +names[0] = "trafalgar" +names[1] = "d" +names[2] = "water" +names[3] = "law" +names[4] = "ez" // baris kode ini menghasilkan error +``` + +Solusi dari masalah di atas adalah dengan menggunakan keyword `append`, yang pembahasannya ada pada chapter selanjutnya, ([A.16. Slice](/A-slice.html)). + +## A.15.2. Inisialisasi Nilai Awal Array + +Pengisian elemen array bisa dilakukan pada saat deklarasi variabel. Caranya dengan menuliskan data elemen dalam kurung kurawal setelah tipe data, dengan pembatas antar elemen adalah tanda koma (`,`). + +```go +var fruits = [4]string{"apple", "grape", "banana", "melon"} + +fmt.Println("Jumlah element \t\t", len(fruits)) +fmt.Println("Isi semua element \t", fruits) +``` + +Penggunaan fungsi `fmt.Println()` pada data array tanpa mengakses indeks tertentu, menghasilkan output dalam bentuk string dari semua array yang ada. Teknik ini umum digunakan untuk keperluan *debugging* data array. + +![Menghitung jumlah elemen dan menampilkan isi array](images/A_array_1_array_initialization_and_len.png) + +Fungsi `len()` berfungsi untuk menghitung jumlah elemen sebuah array. + +## A.15.3. Inisialisasi Nilai Array Dengan Gaya Vertikal + +Elemen array bisa dituliskan dalam bentuk horizontal (seperti yang sudah dicontohkan di atas) ataupun dalam bentuk vertikal. + +```go +var fruits [4]string + +// cara horizontal +fruits = [4]string{"apple", "grape", "banana", "melon"} + +// cara vertikal +fruits = [4]string{ + "apple", + "grape", + "banana", + "melon", +} +``` + +Khusus untuk deklarasi array dengan cara vertikal, tanda koma wajib dituliskan setelah setiap elemen (termasuk elemen terakhir), agar tidak memunculkan syntax error. + +## A.15.4. Inisialisasi Nilai Awal Array Tanpa Jumlah Elemen + +Deklarasi array yang nilainya diset di awal, boleh tidak dituliskan jumlah lebar array-nya, cukup ganti dengan tanda 3 titik (`...`). Metode penulisan ini membuat kapasitas array otomatis dihitung dari jumlah elemen array yang ditulis. + +```go +var numbers = [...]int{2, 3, 2, 4, 3} + +fmt.Println("data array \t:", numbers) +fmt.Println("jumlah elemen \t:", len(numbers)) +``` + +Variabel `numbers` secara otomatis kapasitas elemennya adalah `5`. + +![Deklarasi array menggunakan tanda 3 titik](images/A_array_1_1_array_dots.png) + +## A.15.5. Array Multidimensi + +Array multidimensi adalah array yang tiap elemennya juga berupa array. + +> Level kedalaman array multidimensi adalah tidak terbatas, bisa saja suatu array berisi elemen array yang setiap elemennya juga adalah nilai array, dst. + +Cara deklarasi array multidimensi secara umum sama dengan array biasa, bedanya adalah pada array biasa, setiap elemen berisi satu nilai, sedangkan pada array multidimensi setiap elemen berisi array. + +Khusus penulisan array yang merupakan subdimensi/elemen, boleh tidak dituliskan jumlah datanya. Contohnya bisa dilihat pada deklarasi variabel `numbers2` di kode berikut. + +```go +var numbers1 = [2][3]int{[3]int{3, 2, 3}, [3]int{3, 4, 5}} +var numbers2 = [2][3]int{{3, 2, 3}, {3, 4, 5}} + +fmt.Println("numbers1", numbers1) +fmt.Println("numbers2", numbers2) +``` + +Kedua array di atas memiliki jumlah dan isi elemen yang sama. + +![Array multidimensi](images/A_array_2_array_multidimension.png) + +## A.15.6. Perulangan Elemen Array Menggunakan Keyword `for` + +Keyword `for` dan array memiliki hubungan yang sangat erat. Dengan memanfaatkan perulangan/looping menggunakan keyword ini, elemen-elemen dalam array bisa didapat. + +Ada beberapa cara yang bisa digunakan untuk me-looping data array, yg pertama adalah dengan memanfaatkan variabel iterasi perulangan untuk mengakses elemen berdasarkan indeks-nya. Contoh: + +```go +var fruits = [4]string{"apple", "grape", "banana", "melon"} + +for i := 0; i < len(fruits); i++ { + fmt.Printf("elemen %d : %s\n", i, fruits[i]) +} +``` + +Perulangan di atas dijalankan sebanyak jumlah elemen array `fruits` (bisa diketahui dari kondisi `i < len(fruits`). Di tiap perulangan, elemen array diakses lewat variabel iterasi `i`. + +![Iterasi elemen array](images/A_array_3_for_range.png) + +## A.15.7. Perulangan Elemen Array Menggunakan Keyword `for` - `range` + +Ada cara lain yang lebih sederhana untuk operasi perulangan array, yaitu menggunakan kombinasi keyword `for` - `range`. Contoh pengaplikasiannya bisa dilihat di kode berikut. + +```go +var fruits = [4]string{"apple", "grape", "banana", "melon"} + +for i, fruit := range fruits { + fmt.Printf("elemen %d : %s\n", i, fruit) +} +``` + +Array `fruits` diambil elemen-nya secara berurutan. Nilai tiap elemen ditampung variabel oleh `fruit` (tanpa huruf s), sedangkan indeks nya ditampung variabel `i`. + +Output program di atas, sama persis dengan output program sebelumnya, hanya saja cara yang diterapkan berbeda. + +## A.15.8. Penggunaan Variabel Underscore `_` Dalam `for` - `range` + +Terkadang, dalam penerapan *looping* menggunakan `for` - `range`, ada kebutuhan di mana yang dibutuhkan dari perulangan adlah adalah elemen-nya saja, sedangkan indeks-nya tidak, contoh: + +```go +var fruits = [4]string{"apple", "grape", "banana", "melon"} + +for i, fruit := range fruits { + fmt.Printf("nama buah : %s\n", fruit) +} +``` + +Hasil dari kode program di atas adalah error, karena Go tidak memperbolehkan adanya variabel yang menganggur atau tidak dipakai. + +![Error karena ada variabel yang tidak digunakan](images/A_array_4_for_range_error.png) + +Di sinilah salah satu kegunaan dari variabel pengangguran, atau underscore (`_`). Tampung saja nilai yang tidak ingin digunakan ke underscore. + +```go +var fruits = [4]string{"apple", "grape", "banana", "melon"} + +for _, fruit := range fruits { + fmt.Printf("nama buah : %s\n", fruit) +} +``` + +Pada kode di atas, yang sebelumnya adalah variabel `i` diganti dengan `_`, karena kebetulan variabel `i` tidak digunakan. + +![For range tanpa indeks](images/A_array_5_for_range_underscore.png) + +Bagaiamana jika sebaliknya? Misal, yang dibutuhkan hanya indeks-nya saja, nilainya tidak penting. Maka cukup tulis satu variabel saja setelah keyword `for`, yaitu variabel penampung nilai indeks. + +```go +for i, _ := range fruits { } +// atau +for i := range fruits { } +``` + +## A.15.9. Alokasi Elemen Array Menggunakan Keyword `make` + +Deklarasi sekaligus alokasi kapasitas array juga bisa dilakukan lewat keyword `make`. + +```go +var fruits = make([]string, 2) +fruits[0] = "apple" +fruits[1] = "manggo" + +fmt.Println(fruits) // [apple manggo] +``` + +Parameter pertama keyword `make` diisi dengan tipe data elemen array yang diinginkan, parameter kedua adalah jumlah elemennya. Pada kode di atas, variabel `fruits` tercetak sebagai array string dengan kapasitas alokasi 2 slot. + +--- + +
+ +--- + + diff --git a/en/content-en/A-buffered-channel.md b/en/content-en/A-buffered-channel.md new file mode 100644 index 000000000..11f3f026c --- /dev/null +++ b/en/content-en/A-buffered-channel.md @@ -0,0 +1,77 @@ +# A.32. Buffered Channel + +Proses transfer data pada channel secara default dilakukan dengan metode **un-buffered** atau tidak di-buffer di memori. Ketika terjadi proses kirim data via channel dari sebuah goroutine, maka harus ada goroutine lain yang menerima data dari channel yang sama, dengan proses serah-terima yang bersifat blocking. Maksudnya, baris kode setelah kode pengiriman dan juga penerimaan data tidak akan diproses sebelum proses serah-terima itu sendiri selesai. + +Buffered channel sedikit berbeda. Pada channel jenis ini, ditentukan angka jumlah buffer-nya. Angka tersebut menjadi penentu jumlah data yang bisa dikirimkan bersamaan. Selama jumlah data yang dikirim tidak melebihi jumlah buffer, maka pengiriman akan berjalan **asynchronous** (tidak blocking). + +Ketika jumlah data yang dikirim sudah melewati batas buffer, maka pengiriman data hanya bisa dilakukan ketika salah satu data yang sudah terkirim adalah sudah diambil dari channel di goroutine penerima, sehingga ada slot channel yang kosong. + +Proses pengiriman data pada buffered channel adalah *asynchronous* ketika jumlah data yang dikirim tidak melebihi batas buffer. Namun pada bagian channel penerimaan data selalu bersifat *synchronous* atau blocking. + +![Analogi buffered channel](images/A_buffered_channel_1_anatomy.png) + +## A.32.1. Penerapan Buffered Channel + +Penerapan buffered channel pada dasarnya mirip seperti channel biasa. Perbedaannya hanya pada penulisan deklarasinya, perlu ditambahkan angka buffer sebagai argumen `make()`. + +Berikut adalah contoh penerapan buffered channel. Program di bawah ini merupakan pembuktian bahwa pengiriman data lewat buffered channel adalah asynchronous selama jumlah data yang sedang di-buffer oleh channel tidak melebihi kapasitas buffer. + +```go +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + runtime.GOMAXPROCS(2) + + messages := make(chan int, 3) + + go func() { + for { + i := <-messages + fmt.Println("receive data", i) + } + }() + + for i := 0; i < 5; i++ { + fmt.Println("send data", i) + messages <- i + } + + time.Sleep(1 * time.Second) +} + +``` + +Pada kode di atas, parameter kedua fungsi `make()` adalah representasi jumlah buffer. Perlu diperhatikan bahwa nilai buffered channel dimulai dari `0`. Ketika nilainya adalah **3** berarti jumlah buffer maksimal ada **4**. + +Terdapat juga IIFE goroutine yang isinya proses penerimaan data dari channel `messages`, untuk kemudian datanya ditampilkan. Setelah goroutine tersebut dieksekusi, perulangan dijalankan dengan di-masing-masing perulangan dilakukan pengiriman data. Total ada 5 data dikirim lewat channel `messages` secara sekuensial. + +![Implementasi buffered channel](images/A_buffered_channel_2_buffered_channel.png) + +Terlihat di output, proses pengiriman data indeks ke-4 adalah diikuti dengan proses penerimaan data yang proses transfernya sendiri dilakukan *syncrhonous* atau blocking. + +Pengiriman data indeks ke 0, 1, 2 dan 3 akan berjalan secara asynchronous, hal ini karena channel ditentukan nilai buffer-nya sebanyak 3 (ingat, jika nilai buffer adalah 3, maka 4 data yang akan di-buffer). Pengiriman selanjutnya (indeks 5) hanya akan terjadi jika ada salah satu data dari ke-empat data yang sebelumnya telah dikirimkan sudah diterima (dengan serah terima data yang bersifat blocking). Setelahnya, pengiriman data kembali dilakukan secara asynchronous (karena sudah ada slot buffer ada yang kosong). + +Karena pengiriman dan penerimaan data via buffered channel terjadi tidak selalu synchronous (tergantung jumlah buffer-nya), maka ada kemungkinan dimana eksekusi program selesai namun tidak semua data diterima via channel `messages`. Karena alasan ini pada bagian akhir ditambahkan statement `time.Sleep(1 * time.Second)` agar ada jeda 1 detik sebelum program selesai. + +#### ◉ Fungsi `time.Sleep()` + +Fungsi ini digunakan untuk menambahkan delay sebelum statement berikutnya dieksekusi. Durasi delay ditentukan oleh parameter, misal `1 * time.Second` maka durasi delay adalah 1 detik. + +Lebih detailnya mengenai fungsi `time.Sleep()` dan `time.Second` dibahas pada chapter terpisah, yaitu [Time Duration](/A-time-duration.html). + +--- + + + + +--- + + diff --git a/en/content-en/A-channel-range-close.md b/en/content-en/A-channel-range-close.md new file mode 100644 index 000000000..7b4163e72 --- /dev/null +++ b/en/content-en/A-channel-range-close.md @@ -0,0 +1,82 @@ +# A.34. Channel - Range dan Close + +Proses penerimaan/*retrieving* data dari banyak channel bisa lebih mudah dilakukan dengan memanfaatkan kombinasi keyword `for` - `range`. Penerapannnya cukup mudah, yaitu dengan menuliskan keyword `for` - `range` pada variabel channel. + +Cara kerjanya: + +- Transaksi data via channel men-trigger perulangan `for` - `range`. Perulangan akan berlangsung seiring terjadinya pengiriman data ke channel yang di-iterasi. +- Perulangan tersebut hanya akan berhenti jika channel di-**close** atau di non-aktifkan via fungsi `close()`. Channel yang sudah di-close tidak bisa digunakan lagi baik untuk menerima data ataupun untuk mengirim data. + +## A.34.1. Penerapan `for` - `range` - `close` + +Berikut adalah contoh program pengaplikasian `for`, `range`, dan `close` untuk penerimaan data dari channel. + +Pertama siapkan fungsi `sendMessage()` yang tugasnya mengirim data via channel. Di dalam fungsi ini dijalankan perulangan sebanyak 20 kali, ditiap perulangannya data dikirim ke channel. Channel di-close setelah semua data selesai dikirim. + +```go +func sendMessage(ch chan<- string) { + for i := 0; i < 20; i++ { + ch <- fmt.Sprintf("data %d", i) + } + close(ch) +} +``` + +Siapkan juga fungsi `printMessage()` untuk handle penerimaan data. Di dalam fungsi tersebut, channel di-looping menggunakan `for` - `range`. Di setiap iterasi, data yang diterima dari channel ditampilkan. + +```go +func printMessage(ch <-chan string) { + for message := range ch { + fmt.Println(message) + } +} +``` + +Selanjutnya, buat channel baru dalam fungsi `main()`, jalankan `sendMessage()` sebagai goroutine. Dengan ini 20 data yang berada dalam fungsi tersebut dikirimkan via goroutine baru. Tak lupa jalankan juga fungsi `printMessage()`. + +```go +func main() { + runtime.GOMAXPROCS(2) + + var messages = make(chan string) + go sendMessage(messages) + printMessage(messages) +} +``` + +Setelah 20 data yang dikirim sukses diterima, channel `ch` di-non-aktifkan dengan adanya statement `close(ch)`. Statement tersebut menghentikan perulangan channel dalam `printMessage()`. + +![Penerapan for-range-close pada channel](images/A_channel_range_close_1_for_range_close.png) + +## A.34.2. Penjelasan tambahan + +Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan: + +#### ◉ Channel Direction + +Go mendesain API channel untuk mendukung level akses channel, apakah hanya sebagai penerima, pengirim, atau penerima sekaligus pengirim. Konsep ini disebut dengan **channel direction**. + +Cara pemberian level akses adalah dengan menambahkan tanda `<-` sebelum atau setelah keyword `chan`. Untuk lebih jelasnya bisa dilihat di list berikut. + +| Sintaks | Penjelasan | +|:--------------------- |:------------------------------------------------------- | +| `ch chan string` | Parameter `ch` untuk **mengirim** dan **menerima** data | +| `ch chan<- string` | Parameter `ch` hanya untuk **mengirim** data | +| `ch <-chan string` | Parameter `ch` hanya untuk **menerima** data | + +Pada kode di atas bisa dilihat bahwa secara default channel akan memiliki kemampuan untuk mengirim dan menerima data. Untuk mengubah channel tersebut agar hanya bisa mengirim atau menerima saja, dengan memanfaatkan simbol `<-`. + +Sebagai contoh fungsi `sendMessage(ch chan<- string)` yang parameter `ch` dideklarasikan dengan level akses untuk pengiriman data saja. Channel tersebut hanya bisa digunakan untuk mengirim, contohnya: `ch <- fmt.Sprintf("data %d", i)`. + +Dan sebaliknya pada fungsi `printMessage(ch <-chan string)`, channel `ch` hanya bisa digunakan untuk menerima data saja. + +--- + + + + +--- + + diff --git a/en/content-en/A-channel-select.md b/en/content-en/A-channel-select.md new file mode 100644 index 000000000..bd779f787 --- /dev/null +++ b/en/content-en/A-channel-select.md @@ -0,0 +1,90 @@ +# A.33. Channel - Select + +Channel membuat manajemen goroutine menjadi sangat mudah di Go. Namun perlu di-ingat, fungsi utama channel adalah bukan untuk kontrol eksekusi goroutine, melainkan untuk sharing data atau komunikasi goroutine. + +> Pada chapter [A.59. sync.WaitGroup](/A-waitgroup.html) akan dibahas secara komprehensif tentang cara yang lebih optimal untuk kontrol eksekusi goroutine. + +Tergantung jenis kasusnya, ada kalanya kita butuh lebih dari satu channel untuk komunikasi data antar goroutine. Penerimaan data pada banyak goroutine penerapannya masih sama, yaitu dengan menambahkan karakter `<-` pada statement. Selain itu, ada juga cara lain yaitu menggunakan keyword `select`. Keyword ini mempermudah kontrol penerimaan data via satu atau lebih dari satu channel. + +Cara penggunaan `select` untuk kontrol channel sama seperti penggunaan `switch` untuk seleksi kondisi. + +## A.33.1. Penerapan Keyword `select` + +Program berikut merupakan contoh sederhana penerapan keyword `select`. Di sini disiapkan 2 buah goroutine, satu untuk menghitung rata-rata dari data array numerik, dan satu lagi untuk pencarian nilai tertinggi. Hasil operasi di masing-masing goroutine dikirimkan ke fungsi `main()` via channel (ada dua channel). Di fungsi `main()` sendiri, data tersebut diterima dengan memanfaatkan keyword `select`. + +Ok, langsung saja kita praktek. Pertama, siapkan 2 fungsi yang sudah dibahas di atas. Fungsi pertama digunakan untuk mencari rata-rata, dan fungsi kedua untuk penentuan nilai tertinggi dari sebuah slice. + +```go +package main + +import "fmt" +import "runtime" + +func getAverage(numbers []int, ch chan float64) { + var sum = 0 + for _, e := range numbers { + sum += e + } + ch <- float64(sum) / float64(len(numbers)) +} + +func getMax(numbers []int, ch chan int) { + var max = numbers[0] + for _, e := range numbers { + if max < e { + max = e + } + } + ch <- max +} +``` + +Kedua fungsi tersebut dijalankan sebagai goroutine. Di akhir blok masing-masing fungsi, hasil kalkulasi dikirimkan via channel yang sudah dipersiapkan, yaitu `ch1` untuk menampung data rata-rata, `ch2` untuk data nilai tertinggi. + +Ok lanjut, buat implementasinya pada fungsi `main()`. + +```go +func main() { + runtime.GOMAXPROCS(2) + + var numbers = []int{3, 4, 3, 5, 6, 3, 2, 2, 6, 3, 4, 6, 3} + fmt.Println("numbers :", numbers) + + var ch1 = make(chan float64) + go getAverage(numbers, ch1) + + var ch2 = make(chan int) + go getMax(numbers, ch2) + + for i := 0; i < 2; i++ { + select { + case avg := <-ch1: + fmt.Printf("Avg \t: %.2f \n", avg) + case max := <-ch2: + fmt.Printf("Max \t: %d \n", max) + } + } +} +``` + +Pada kode di atas, pengiriman data pada channel `ch1` dan `ch2` dikontrol menggunakan `select`. Terdapat 2 buah `case` kondisi penerimaan data dari kedua channel tersebut. + + - Kondisi `case avg := <-ch1` akan terpenuhi ketika ada penerimaan data dari channel `ch1`, yang kemudian akan ditampung oleh variabel `avg`. + - Kondisi `case max := <-ch2` akan terpenuhi ketika ada penerimaan data dari channel `ch2`, yang kemudian akan ditampung oleh variabel `max`. + +Karena ada 2 buah channel, maka perlu disiapkan perulangan 2 kali sebelum penggunaan keyword `select`. + +![Contoh penerapan channel select](images/A_channel_select_1_channel_select.png) + +Cukup mudah bukan? + +--- + + + + +--- + + diff --git a/en/content-en/A-channel-timeout.md b/en/content-en/A-channel-timeout.md new file mode 100644 index 000000000..4212955fa --- /dev/null +++ b/en/content-en/A-channel-timeout.md @@ -0,0 +1,77 @@ +# A.35. Channel - Timeout + +Teknik channel timeout digunakan untuk kontrol waktu penerimaan data pada channel, berapa lama channel tersebut harus menunggu hingga akhirnya suatu penerimaan data dianggap timeout. + +Durasi penerimaan kita tentukan sendiri. Ketika tidak ada aktivitas penerimaan data dalam durasi tersebut, blok timeout dijalankan. + +## A.35.1. Penerapan Channel Timeout + +Berikut adalah program sederhana contoh pengaplikasian timeout pada channel. Sebuah goroutine dijalankan dengan tugas adalah mengirimkan data secara berulang dalam interval tertentu, dengan durasi interval-nya sendiri adalah acak/random. + +```go +package main + +import "fmt" +import "math/rand" +import "runtime" +import "time" + +func sendData(ch chan<- int) { + randomizer := rand.New(rand.NewSource(time.Now().Unix())) + + for i := 0; true; i++ { + ch <- i + time.Sleep(time.Duration(randomizer.Int()%10+1) * time.Second) + } +} +``` + +Selanjutnya, disiapkan perulangan tanpa henti, yang di setiap perulangan ada seleksi kondisi channel menggunakan `select`. + +```go +func retreiveData(ch <-chan int) { + loop: + for { + select { + case data := <-ch: + fmt.Print(`receive data "`, data, `"`, "\n") + case <-time.After(time.Second * 5): + fmt.Println("timeout. no activities under 5 seconds") + break loop + } + } +} +``` + +Ada 2 blok kondisi pada `select` tersebut. + +- Kondisi `case data := <-messages:`, akan terpenuhi ketika ada serah terima data pada channel `messages`. +- Kondisi `case <-time.After(time.Second * 5):`, akan terpenuhi ketika tidak ada aktivitas penerimaan data dari channel dalam durasi 5 detik. Blok inilah yang kita sebut sebagai blok timeout. + +Terakhir, kedua fungsi tersebut dipanggil di `main()`. + +```go +func main() { + runtime.GOMAXPROCS(2) + + var messages = make(chan int) + + go sendData(messages) + retreiveData(messages) +} +``` + +Muncul output setiap kali ada penerimaan data dengan delay waktu acak. Ketika dalam durasi 5 detik tidak ada aktivitas penerimaan sama sekali, maka dianggap timeout dan perulangan pengecekkan channel dihentikan. + +![Channel timeout](images/A_channel_timeout_1_channel_delay.png) + +--- + + + + +--- + + diff --git a/en/content-en/A-channel.md b/en/content-en/A-channel.md new file mode 100644 index 000000000..c7cedee60 --- /dev/null +++ b/en/content-en/A-channel.md @@ -0,0 +1,173 @@ +# A.31. Channel + +**Channel** digunakan untuk menghubungkan goroutine satu dengan goroutine lain dengan mekanisme serah terima data, jadi harus ada data yang dikirim dari goroutine A untuk kemudian diterima di goroutine B. + +Peran channel adalah sebagai media perantara bagi pengirim data dan juga penerima data. Jadi channel adalah *thread safe*, aman digunakan di banyak goroutine. + +Pengiriman dan penerimaan data pada channel bersifat **blocking** atau **synchronous**. Artinya, statement di-bawah syntax pengiriman dan penerimaan data via channel hanya akan dieksekusi setelah proses serah terima berlangsung dan selesai. + +![Analogi channel](images/A_channel_1_analogy.png) + +## A.31.1. Penerapan Channel + +Channel berbentuk variabel, dibuat dengan menggunakan kombinasi keyword `make` dan `chan`. + +Program berikut adalah contoh implementasi channel. 3 buah goroutine dieksekusi, di masing-masing goroutine terdapat proses pengiriman data lewat channel. Kesemua data tersebut nantinya diterima oleh di goroutine utama yaitu proses yang dijalankan di dalam blok fungsi `main()`. + +```go +package main + +import "fmt" +import "runtime" + +func main() { + runtime.GOMAXPROCS(2) + + var messages = make(chan string) + + var sayHelloTo = func(who string) { + var data = fmt.Sprintf("hello %s", who) + messages <- data + } + + go sayHelloTo("john wick") + go sayHelloTo("ethan hunt") + go sayHelloTo("jason bourne") + + var message1 = <-messages + fmt.Println(message1) + + var message2 = <-messages + fmt.Println(message2) + + var message3 = <-messages + fmt.Println(message3) +} +``` + +Pada kode di atas, variabel `messages` dideklarasikan bertipe channel `string`. Contoh cara pembuatan channel bisa dilihat di situ, yaitu dengan memanggil fungsi `make()` dengan isi adalah keyword `chan` diikuti dengan tipe data channel yang diinginkan. + +```go +var messages = make(chan string) +``` + +Selain itu disiapkan juga closure `sayHelloTo` yang tugasnya membuat sebuah pesan string yang kemudian dikirim via channel. Pesan string tersebut dikirim lewat channel `messages`. Tanda `<-` jika dituliskan di sebelah kiri nama variabel, berarti sedang berlangsung proses pengiriman data dari variabel yang berada di kanan lewat channel yang berada di kiri (pada konteks ini, variabel `data` dikirim lewat channel `messages`). + +```go +var sayHelloTo = func(who string) { + var data = fmt.Sprintf("hello %s", who) + messages <- data +} +``` + +Fungsi `sayHelloTo` dieksekusi tiga kali sebagai goroutine berbeda. Menjadikan tiga proses ini berjalan secara **asynchronous** atau tidak saling tunggu. + +> Sekali lagi perlu diingat bahwa eksekusi goroutine adalah *asynchronous*, sedangkan serah-terima data antar channel adalah *synchronous*. + +```go +go sayHelloTo("john wick") +go sayHelloTo("ethan hunt") +go sayHelloTo("jason bourne") +``` + +Dari ketiga fungsi tersebut, goroutine yang selesai paling awal akan mengirim data lebih dulu, datanya kemudian diterima variabel `message1`. Tanda `<-` jika dituliskan di sebelah kiri channel, menandakan proses penerimaan data dari channel yang di kanan, untuk disimpan ke variabel yang di kiri. + +```go +var message1 = <-messages +fmt.Println(message1) +``` + +Penerimaan channel bersifat blocking. Artinya: + +- Statement `var message1 = <-messages` hingga setelahnya tidak akan dieksekusi sebelum ada data yang dikirim lewat channel. +- Berlaku juga dengan statement `messages <- data`. Statement dibawahnya tidak akan dieksekusi hingga data yang dikirim ke channel `messages` benar-benar diterima oleh penerima, yaitu variabel `message1`. + +Ke semua data yang dikirim dari tiga goroutine berbeda tersebut nantinya diterima oleh `message1`, `message2`, `message3`; untuk kemudian ditampilkan. + +![Implementasi channel](images/A_channel_2_channel.png) + +Dari screenshot output di atas bisa dilihat bahwa text yang dikembalikan oleh `sayHelloTo` tidak selalu berurutan, meskipun penerimaan datanya adalah berurutan. Hal ini dikarenakan, pengiriman data adalah dari 3 goroutine yang berbeda, yang kita tidak tau mana yang prosesnya selesai lebih dulu. Goroutine yang dieksekusi lebih awal belum tentu selesai lebih awal, yang jelas proses yang selesai lebih awal datanya akan diterima lebih awal. + +Karena pengiriman dan penerimaan data lewat channel bersifat **blocking**, tidak perlu memanfaatkan sifat blocking dari fungsi seperti `fmt.Scanln()` (atau lainnya) untuk mengantisipasi goroutine utama (yaitu `main`) selesai sebelum ketiga goroutine di atas selesai. + +## A.31.2. Channel Sebagai Tipe Data Parameter + +Variabel channel bisa di-pass ke fungsi lain via parameter. Cukup tambahkan keyword `chan` pada deklarasi parameter agar operasi pass channel variabel bisa dilakukan. + +Siapkan fungsi `printMessage()` dengan parameter adalah channel. Lalu ambil data yang dikirimkan lewat channel tersebut untuk ditampilkan. + +```go +func printMessage(what chan string) { + fmt.Println(<-what) +} +``` + +Setelah itu ubah implementasi di fungsi `main()`. + +```go +func main() { + runtime.GOMAXPROCS(2) + + var messages = make(chan string) + + for _, each := range []string{"wick", "hunt", "bourne"} { + go func(who string) { + var data = fmt.Sprintf("hello %s", who) + messages <- data + }(each) + } + + for i := 0; i < 3; i++ { + printMessage(messages) + } +} +``` + +Output program di atas sama dengan program sebelumnya. + +Parameter `what` pada fungsi `printMessage()` bertipe channel `string`, bisa dilihat dari kode `chan string` pada cara deklarasinya. Operasi serah-terima data akan bisa dilakukan pada variabel tersebut, dan akan berdampak juga pada variabel `messages` di fungsi `main()`. + +Passing data bertipe channel lewat parameter sifatnya **pass by reference**, yang di transferkan adalah pointer datanya, bukan nilai datanya. + +![Parameter channel](images/A_channel_3_channel_param.png) + +## A.32.3. Penjelasan tambahan + +Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan: + +#### ◉ Iterasi Data Slice/Array Langsung Pada Saat Inisialisasi + +Data slice yang baru di inisialisasi bisa langsung di-iterasi, caranya mudah dengan menuliskannya langsung setelah keyword `range`. + +```go +for _, each := range []string{"wick", "hunt", "bourne"} { + // ... +} +``` + +#### ◉ Eksekusi Goroutine Pada IIFE + +Eksekusi goroutine tidak harus pada fungsi atau closure yang sudah terdefinisi. Sebuah IIFE juga bisa dijalankan sebagai goroutine baru. Caranya dengan langsung menambahkan keyword `go` pada waktu deklarasi-eksekusi IIFE-nya. + +```go +var messages = make(chan string) + +go func(who string) { + var data = fmt.Sprintf("hello %s", who) + messages <- data +}("wick") + +var message = <-messages +fmt.Println(message) +``` + +--- + + + + +--- + + diff --git a/en/content-en/A-client-http-request-simple.md b/en/content-en/A-client-http-request-simple.md new file mode 100644 index 000000000..5e8332edf --- /dev/null +++ b/en/content-en/A-client-http-request-simple.md @@ -0,0 +1,182 @@ +# A.55. Simple Client HTTP Request + +Pada chapter sebelumnya telah dibahas bagaimana cara membuat Web Service API yang response data-nya berbentuk JSON. Pada chapter ini kita akan belajar mengenai cara untuk mengkonsumsi data tersebut. + +Pastikan anda sudah mempraktekkan apa-apa yang ada pada chapter sebelumnya ([A.54. Web Service API Server](/A-web-service-api.html)), karena web service yang telah dibuat di situ juga dipergunakan pada chapter ini. + +![Jalankan web server](images/A_web_service_1_server.png) + +## A.55.1. Penggunaan HTTP Request + +Package `net/http`, selain berisikan tools untuk keperluan pembuatan web, juga berisikan fungsi-fungsi untuk melakukan http request. Salah satunya adalah `http.NewRequest()` yang akan kita bahas di sini. Untuk menggunakannya pastikan import package-nya terlebih dahulu. + +Kemudian siapkan struct `student` yang nantinya akan dipakai sebagai tipe data reponse dari web API. Struk tersebut skema nya sama dengan yang ada pada chapter ([A.54. Web Service API Server](/A-web-service-api.html)). + +```go +package main + +import "fmt" +import "net/http" +import "encoding/json" + +var baseURL = "http://localhost:8080" + +type student struct { + ID string + Name string + Grade int +} +``` + +Setelah itu buat fungsi `fetchUsers()`. Fungsi ini bertugas melakukan request ke [http://localhost:8080/users](http://localhost:8080/users), menerima response dari request tersebut, lalu menampilkannya. + +```go +func fetchUsers() ([]student, error) { + var err error + var client = &http.Client{} + var data []student + + request, err := http.NewRequest("GET", baseURL+"/users", nil) + if err != nil { + return nil, err + } + + response, err := client.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + err = json.NewDecoder(response.Body).Decode(&data) + if err != nil { + return nil, err + } + + return data,nil +} +``` + +Statement `&http.Client{}` menghasilkan instance `http.Client`. Objek ini nantinya diperlukan untuk eksekusi request. + +Fungsi `http.NewRequest()` digunakan untuk membuat request baru. Fungsi tersebut memiliki 3 parameter yang wajib diisi. + +1. Parameter pertama, berisikan tipe request **POST** atau **GET** atau lainnya +2. Parameter kedua, adalah URL tujuan request +3. Parameter ketiga, form data request (jika ada) + +Fungsi tersebut menghasilkan instance bertipe `http.Request` yang nantinya digunakan saat eksekusi request. + +Cara eksekusi request sendiri adalah dengan memanggil method `Do()` pada variabel `client` yang sudah dibuat. Fungsi `Do()` dipanggil dengan disisipkan argument fungsi yaitu object `request`. Penulisannya: `client.Do(request)`. + +Method tersebut mengembalikan instance bertipe `http.Response` yang di contoh ditampung oleh variabel `response`. Dari data response tersebut kita bisa mengakses informasi yang berhubungan dengan HTTP response, termasuk response body. + +Data response body tersedia via property `Body` dalam tipe `[]byte`. Gunakan JSON Decoder untuk mengkonversinya menjadi bentuk JSON. Contohnya bisa dilihat di kode di atas, `json.NewDecoder(response.Body).Decode(&data)`. + +Perlu diketahui, data response perlu di-**close** setelah tidak dipakai. Caranya dengan memanggil method `Close()` milik property `Body` yang dalam penerapannya umumnya di-defer. Contohnya: `defer response.Body.Close()`. + +Selanjutnya, eksekusi fungsi `fetchUsers()` dalam fungsi `main()`. + +```go +func main() { + var users, err = fetchUsers() + if err != nil { + fmt.Println("Error!", err.Error()) + return + } + + for _, each := range users { + fmt.Printf("ID: %s\t Name: %s\t Grade: %d\n", each.ID, each.Name, each.Grade) + } +} +``` + +Ok, terakhir sebelum memulai testing, pastikan telah run aplikasi pada chapter sebelumya ([A.54. Web Service API Server](/A-web-service-api.html)). Setelah itu start prompt cmd/terminal baru dan jalankan program yang telah dibuat di chapter ini. + +![HTTP Request](images/A_http_request_1_http_request.png) + +## A.55.2. HTTP Request Dengan Form Data + +Untuk menyisipkan data pada sebuah request, ada beberapa hal yang perlu ditambahkan. Pertama, import package `bytes` dan `net/url`. + +```go +import "bytes" +import "net/url" +``` + +Kemudian buat fungsi baru, isinya request ke [http://localhost:8080/user](http://localhost:8080/user) dengan data yang disisipkan adalah `ID`. + +```go +func fetchUser(ID string) (student, error) { + var err error + var client = &http.Client{} + var data student + + var param = url.Values{} + param.Set("id", ID) + var payload = bytes.NewBufferString(param.Encode()) + + request, err := http.NewRequest("POST", baseURL+"/user", payload) + if err != nil { + return data, err + } + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + response, err := client.Do(request) + if err != nil { + return data, err + } + defer response.Body.Close() + + err = json.NewDecoder(response.Body).Decode(&data) + if err != nil { + return data, err + } + + return data, nil +} +``` + +Isi fungsi `fetchUser()` memiliki beberapa kemiripan dengan fungsi `fetchUsers()` sebelumnya. + +Statement `url.Values{}` akan menghasilkan objek yang nantinya digunakan sebagai form data request. Pada objek tersebut perlu di set data apa saja yang ingin dikirimkan menggunakan fungsi `Set()` seperti pada `param.Set("id", ID)`. + +Statement `bytes.NewBufferString(param.Encode())` melakukan proses encoding pada data param untuk kemudian diubah menjadi bentuk `bytes.Buffer`. Nantinya data buffer tersebut disisipkan pada parameter ketiga pemanggilan fungsi `http.NewRequest()`. + +Karena data yang akan dikirim adalah *encoded*, maka pada header perlu dituliskan juga tipe encoding-nya. Kode `request.Header.Set("Content-Type", "application/x-www-form-urlencoded")` menandai bahwa HTTP request berisi body yang ter-encode sesuai spesifikasi `application/x-www-form-urlencoded`. + +> Pada konteks HTML, HTTP Request yang di trigger dari tag `` secara default tipe konten-nya sudah di set `application/x-www-form-urlencoded`. Lebih detailnya bisa merujuk ke spesifikasi HTML form [http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1](http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1) + +Response dari endpoint `/user` bukanlah slice, tetapi berupa objek. Maka pada saat decode perlu pastikan tipe variabel penampung hasil decode data response adalah `student` (bukan `[]student`). + +Lanjut ke perkodingan, terakhir, implementasikan `fetchUser()` pada fungsi `main()`. + +```go +func main() { + var user1, err = fetchUser("E001") + if err != nil { + fmt.Println("Error!", err.Error()) + return + } + + fmt.Printf("ID: %s\t Name: %s\t Grade: %d\n", user1.ID, user1.Name, user1.Grade) +} +``` + +Untuk keperluan testing, kita hardcode `ID` nilainya `"E001"`. Jalankan program untuk test apakah data yang dikembalikan sesuai. + +![HTTP request Form Data](images/A_http_request_2_http_request_form_data.png) + +## A.55.3. Secure & Insecure HTTP Request + +Sampai sini kita telah belajar bagaimana cara membuat http request sederhana untuk kirim data dan juga ambil data. Nantinya pada chapter [C.27. Secure & Insecure Client HTTP Request](/C-secure-insecure-client-http-request.html) pembelajaran topik HTTP request dilanjutkan kembali, kita akan bahas tentang aspek keamanan/security suatu HTTP request. + +--- + + + + +--- + + diff --git a/en/content-en/A-command-line-args-flag.md b/en/content-en/A-command-line-args-flag.md new file mode 100644 index 000000000..f6a39e407 --- /dev/null +++ b/en/content-en/A-command-line-args-flag.md @@ -0,0 +1,139 @@ +# A.48. Arguments & Flag + +**Arguments** adalah data argument opsional yang disisipkan ketika eksekusi program. Sedangkan **flag** merupakan ekstensi dari argument. Dengan flag, penulisan argument menjadi lebih rapi dan terstruktur. + +Pada chapter ini kita akan belajar tentang penerapan arguments dan flag. + +## A.48.1. Penggunaan Arguments + +Data arguments bisa didapat lewat variabel `os.Args` (package `os` perlu di-import terlebih dahulu). Data tersebut tersimpan dalam bentuk array. Setiap data argument yang disisipkan saat pemanggilan program, datanya dipecah menggunakan karakter spasi lalu di-map ke bentuk array. Contoh penerapan: + +```go +package main + +import "fmt" +import "os" + +func main() { + var argsRaw = os.Args + fmt.Printf("-> %#v\n", argsRaw) + // -> []string{".../bab45", "banana", "potato", "ice cream"} + + var args = argsRaw[1:] + fmt.Printf("-> %#v\n", args) + // -> []string{"banana", "potatao", "ice cream"} +} +``` + +Argument disisipkan saat eksekusi program. Sebagai contoh, kita ingin menyisipkan 3 buah argumen berikut: `banana`, `potato`, dan `ice cream`. Maka penulisan saat pemanggilan program-nya seperti ini: + + - Menggunakan `go run` + + ``` + go run bab45.go banana potato "ice cream" + ``` + + - Menggunakan `go build` + + ``` + go build bab45.go + $ ./bab45 banana potato "ice cream" + ``` + +Output program: + +![Pemanfaatan arguments](images/A_cli_flag_arg_1_argument.png) + +Bisa dilihat pada kode di atas, bahwa untuk data argumen yang ada karakter spasi-nya ( +
) harus dituliskan dengan diapit tanda petik (`"`) agar tidak dideteksi sebagai 2 argumen.
+
+Variabel `os.Args` mengembalikan tak hanya arguments saja, tapi juga path file executable (jika eksekusi-nya menggunakan `go run` maka path akan merujuk ke folder temporary). Maka disini penting untuk hanya mengambil element index ke 1 hingga seterusnya saja via statement `os.Args[1:]`.
+
+## A.48.2. Penggunaan Flag
+
+Flag memiliki kegunaan yang sama seperti arguments, yaitu untuk *parameterize* eksekusi program, dengan penulisan dalam bentuk key-value. Berikut merupakan contoh penerapannya.
+
+```go
+package main
+
+import "flag"
+import "fmt"
+
+func main() {
+ var name = flag.String("name", "anonymous", "type your name")
+ var age = flag.Int64("age", 25, "type your age")
+
+ flag.Parse()
+ fmt.Printf("name\t: %s\n", *name)
+ fmt.Printf("age\t: %d\n", *age)
+}
+```
+
+Cara penulisan arguments menggunakan flag:
+
+```
+go run bab45.go -name="john wick" -age=28
+```
+
+Tiap argument harus ditentukan key, tipe data, dan nilai default-nya. Contohnya seperti pada `flag.String()` di atas. Agar lebih mudah dipahami, mari kita bahas kode berikut.
+
+```go
+var dataName = flag.String("name", "anonymous", "type your name")
+fmt.Println(*dataName)
+```
+
+Kode tersebut maksudnya adalah, disiapkan flag bertipe `string`, dengan key adalah `name`, dengan nilai default `"anonymous"`, dan keterangan `"type your name"`. Nilai flag nya sendiri akan disimpan ke dalam variabel `dataName`.
+
+Nilai balik fungsi `flag.String()` adalah string pointer, jadi perlu di-*dereference* terlebih dahulu untuk mengakses nilai aslinya (`*dataName`).
+
+![Contoh penggunaan flag](images/A_cli_flag_arg_2_flag.png)
+
+Flag yang nilainya tidak di set, secara otomatis akan mengembalikan nilai default.
+
+Tabel berikut merupakan macam-macam fungsi flag yang tersedia untuk tiap jenis tipe data.
+
+| Nama Fungsi | Return Value |
+|:------------------------------------------ |:---------------- |
+| `flag.Bool(name, defaultValue, usage)` | `*bool` |
+| `flag.Duration(name, defaultValue, usage)` | `*time.Duration` |
+| `flag.Float64(name, defaultValue, usage)` | `*float64` |
+| `flag.Int(name, defaultValue, usage)` | `*int` |
+| `flag.Int64(name, defaultValue, usage)` | `*int64` |
+| `flag.String(name, defaultValue, usage)` | `*string` |
+| `flag.Uint(name, defaultValue, usage)` | `*uint` |
+| `flag.Uint64(name, defaultValue, usage)` | `*uint64` |
+
+## A.48.3. Deklarasi Flag Dengan Cara Passing Reference Variabel Penampung Data
+
+Sebenarnya ada 2 cara deklarasi flag yang bisa digunakan, dan cara di atas merupakan cara pertama.
+
+Cara kedua mirip dengan cara pertama, perbedannya adalah kalau di cara pertama nilai pointer flag dikembalikan lalu ditampung variabel. Sedangkan pada cara kedua, nilainya diambil lewat parameter pointer.
+
+Agar lebih jelas perhatikan contoh berikut:
+
+```go
+// cara ke-1
+var data1 = flag.String("name", "anonymous", "type your name")
+fmt.Println(*data1)
+
+// cara ke-2
+var data2 string
+flag.StringVar(&data2, "gender", "male", "type your gender")
+fmt.Println(data2)
+```
+
+Tinggal tambahkan akhiran `Var` pada pemanggilan nama fungsi flag yang digunakan (contoh `flag.IntVar()`, `flag.BoolVar()`, dll), lalu disisipkan referensi variabel penampung flag sebagai parameter pertama.
+
+Kegunaan dari parameter terakhir method-method flag adalah untuk memunculkan hints atau petunjuk arguments apa saja yang bisa dipakai, ketika argument `--help` ditambahkan saat eksekusi program.
+
+![Contoh penggunaan flag](images/A_cli_flag_arg_3_flag_info.png)
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-concurrency-pipeline.md b/en/content-en/A-concurrency-pipeline.md
new file mode 100644
index 000000000..d64fd662c
--- /dev/null
+++ b/en/content-en/A-concurrency-pipeline.md
@@ -0,0 +1,548 @@
+# A.62. Concurrency Pattern: Pipeline
+
+Kita sudah membahas beberapa kali tentang topik *concurrency* atau konkurensi di Go programming. Pada chapter ini kita akan belajar salah satu best practice konkurensi dalam Go, yaitu teknik *pipeline*, yang merupakan satu di antara banyak *concurrency pattern* yang ada di Go.
+
+Go memiliki beberapa API untuk keperluan konkurensi, dua diantaranya adalah *goroutine* dan *channel*. Dengan memanfaatkan APIs yang ada kita bisa membentuk sebuah *streaming data pipeline* yang benefitnya adalah efisiensi penggunaan I/O dan efisiensi penggunaan banyak CPU.
+
+## A.62.1. Konsep *Pipeline*
+
+Definisi *pipeline* yang paling mudah versi penulis adalah **beberapa/banyak proses yang berjalan secara konkuren yang masing-masing proses merupakan bagian dari serangkaian tahapan proses yang berhubungan satu sama lain**.
+
+Analoginya seperti ini: bayangkan sebuah flow proses untuk auto backup database secara rutin, yang mana database server yang perlu di-backup ada banyak. Untuk backup-nya sendiri kita menggunakan program Go, bukan *shell script*. Mungkin secara garis besar serangkaian tahapan proses yang akan dijalankan adalah berikut:
+
+1. Kita perlu data *list* dari semua database yang harus di-backup, beserta alamat akses dan kredensial-nya.
+2. Kita jalankan proses backup, bisa secara sekuensial (setelah `db1` selesai, lanjut `db2`, lanjut `db3`, dst), atau secara paralel (proses backup `db1`, `db2`, `db3`, dan lainnya dijalankan secara bersamaan).
+3. Di masing-masing proses backup database sendiri ada beberapa proses yang dijalankan:
+
+ * A. Lakukan operasi *dump* terhadap database, outputnya berupa banyak file disimpan ke sebuah folder.
+ * B. File-file hasil dump kemudian di-*archive* ke bentuk `.zip` atau `.tar.gz` (misalnya).
+ * C. File archive di-kirim ke server backup, sebagai contoh AWS S3.
+
+Kalau diperhatikan pada kasus di atas, mungkin akan lebih bagus dari segi performansi kalau proses backup banyak database tersebut dilakukan secara parallel.
+
+Dan akan lebih bagus lagi, jika di masing-masing proses backup database tersebut, proses A, B, dan C dijalankan secara konkuren. Dengan menjadikan ketiga proses tersebut (A, B, C) sebagai proses konkuren, maka I/O akan lebih efisien. Nantinya antara proses A, B, dan C eksekusinya akan tetap berurutan (karena memang harus berjalan secara urut. Tidak boleh kalau misal B lebih dulu dieksekusi kemudian A); akan tetapi, ketika goroutine yang bertanggung jawab untuk eksekusi proses A selesai, kita bisa lanjut dengan eksekusi proses B (yang memang *next stage*-nya proses A) plus eksekusi proses A lainnya (database lain) secara paralel. Jadi goroutine yang handle A ini ga sampai menganggur.
+
+Silakan perhatikan visualisasi berikut. Kolom merupakan representasi dari goroutine yang berjalan secara bersamaan. Tapi karena ketiga goroutine tersebut merupakan serangkaian proses, sehingga eksekusinya harus secara berurut. Sedangkan baris/row representasi dari *sequence* atau urutan.
+
+| sequence | pipeline A | pipeline B | pipeline C |
+|:--------:|:----------:|:----------:|:----------:|
+| 1 | db1 | - | - |
+| 2 | db2 | db1 | - |
+| 3 | db3 | db1 | - |
+| 4 | db4 | db2 | db1 |
+| 5 | db5 | db3 | db2 |
+| 6 | db5 | db4 | db3 |
+| 7 | db6 | db5 | db4 |
+| ... | ... | ... | ... |
+
+Di Go, umumnya proses yang berupa goroutine yang eksekusinya adalah concurrent tapi secara flow adalah harus berurutan, itu disebut dengan **pipeline**. Jadi untuk sementara anggap saja pipeline A sebuah goroutine untuk proses A, pipeline B adalah goroutine proses B, dst.
+
+Untuk mempermudah memahami tabel di atas silakan ikuti penjelasan beruntun berikut:
+
+1. Sequence 1: pipeline A akan melakukan proses dump dari dari `db1`. Pada waktu yang sama, pipeline B dan C menganggur.
+2. Sequence 2: proses dump `db1` telah selesai, maka lanjut ke *next stage* yaitu proses archive data dump `db1` yang dilakukan oleh pipeline B. Dan pada waktu yang sama juga, pipeline A menjalankan proses dump `db2`. Pipeline C masih menganggur.
+3. Sequence 3: pipeline A menjalankan proses dump `db3`. Pada waktu yang sama pipeline B belum menjalankan proses archiving `db2` yang sudah di-dump karena archiving `db1` masih belum selesai. Pipeline C masih menganggur.
+4. Sequence 4: proses archiving `db1` sudah selesai, maka lanjut ke *next stage* yaitu kirim archive ke server backup yang prosesnya di-handle oleh pipeline C. Pada saat yang sama, pipeline B mulai menjalankan archiving data dump `db2` dan pipeline A dumping `db4`.
+5. ... dan seterusnya.
+
+Pada contoh ini kita asumsikan pipeline A adalah hanya satu goroutine, pipeline B juga satu goroutine, demikian juga pipeline C. Tapi sebenarnya dalam implementasi *real world* bisa saja ada banyak goroutine untuk masing-masing pipeline (banyak goroutine untuk pipeline A, banyak goroutine untuk pipeline B, banyak goroutine untuk pipeline C).
+
+Semoga cukup jelas ya. Tapi jika masih bingung, juga tidak apa. Kita sambil praktek juga, dan bisa saja pembaca mulai benar-benar pahamnya saat praktek.
+
+> Penulis sarankan untuk benar-benar memahami setiap bagian praktek ini, karena topik ini merupakan pembahasan yang cukup berat untuk pemula, tapi masih dalam klasifikasi fundamental kalau di Go programming. Bingung tidak apa, nanti bisa di-ulang-ulang, yang penting tidak sekadar *copy-paste*.
+
+## A.62.2. Skenario Praktek
+
+Ok, penjabaran teori sepanjang sungai `nil` tidak akan banyak membawa penjelasan yang real kalau tidak diiringi dengan praktek. So, mari kita mulai praktek.
+
+Untuk skenario praktek kita tidak menggunakan analogi backup database di atas ya, karena untuk setup environment-nya butuh banyak *effort*. Skenario praktek yang kita pakai adalah mencari [md5 sum](https://en.wikipedia.org/wiki/Md5sum) dari banyak file, kemudian menggunakan hash dari content-nya sebagai nama file. Jadi file yang lama akan di-rename dengan nama baru yaitu hash dari konten file tersebut.
+
+Agar skenario ini bisa kita eksekusi, kita perlu siapkan dulu sebuah program untuk *generate dummy files*.
+
+## A.62.3. Program 1: Generate Dummy File
+
+Buat project baru dengan nama bebas
+ loss gak reweellll beserta satu buah file bernama `1-dummy-file-generator.go`.
+
+Dalam file tersebut import dan definisikan beberapa hal, diantaranya:
+
+1. Konstanta `totalFile` yang isinya jumlah file yang ingin di-generate.
+1. Variabel `contentLength` yang isinya panjang karakter random yang merupakan isi dari masing-masing *generated* file.
+1. Variabel `tempPath` yang mengarah ke [temporary folder](https://en.wikipedia.org/wiki/Temporary_folder).
+
+```go
+package main
+
+import (
+ "fmt"
+ "log"
+ "math/rand"
+ "os"
+ "path/filepath"
+ "time"
+)
+
+const totalFile = 3000
+const contentLength = 5000
+
+var tempPath = filepath.Join(os.Getenv("TEMP"), "chapter-A.59-pipeline-temp")
+```
+
+Kemudian siapkan fungsi `main()` yang isinya statement pemanggilan fungsi `generate()`, dan beberapa hal lainnya untuk keperluan *benchmark* performa dari sisi *execution time*.
+
+```go
+func main() {
+ log.Println("start")
+ start := time.Now()
+
+ generateFiles()
+
+ duration := time.Since(start)
+ log.Println("done in", duration.Seconds(), "seconds")
+}
+```
+
+Sekarang siapkan fungsi `randomString()`-nya:
+
+```go
+func randomString(length int) string {
+ randomizer := rand.New(rand.NewSource(time.Now().Unix()))
+ letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+ b := make([]rune, length)
+ for i := range b {
+ b[i] = letters[randomizer.Intn(len(letters))]
+ }
+
+ return string(b)
+}
+```
+
+Siapkan fungsi `generateFiles()`-nya, isinya kurang lebih adalah generate banyak file sejumlah `totalFile`. Lalu di tiap-tiap file di-isi dengan *random string* dengan lebar sepanjang `contentLength`. Untuk nama file-nya sendiri, formatnya adalah `file-'
).
+
+Juga berlaku sebaliknya, data numerik jika di-casting ke bentuk string dideteksi sebagai kode ASCII dari karakter yang akan dihasilkan.
+
+```go
+var c int64 = int64('h')
+fmt.Println(c) // 104
+
+var d string = string(104)
+fmt.Println(d) // h
+```
+
+## A.43.4. Type Assertions Pada Tipe `any` atau Interface Kosong (`interface{}`)
+
+**Type assertions** merupakan teknik untuk mengambil tipe data konkret dari data yang terbungkus dalam `interface{}` atau `any`. Lebih jelasnya silakan cek contoh berikut.
+
+Variabel `data` disiapkan bertipe `map[string]interface{}`, map tersebut berisikan beberapa item dengan tipe data value-nya berbeda satu sama lain, sementara tipe data untuk key-nya sama yaitu `string`.
+
+```go
+var data = map[string]interface{}{
+ "nama": "john wick",
+ "grade": 2,
+ "height": 156.5,
+ "isMale": true,
+ "hobbies": []string{"eating", "sleeping"},
+}
+
+fmt.Println(data["nama"].(string))
+fmt.Println(data["grade"].(int))
+fmt.Println(data["height"].(float64))
+fmt.Println(data["isMale"].(bool))
+fmt.Println(data["hobbies"].([]string))
+```
+
+Statement `data["nama"].(string)` maksudnya adalah, nilai `data["nama"]` yang bertipe `interface{}` diambil nilai konkretnya dalam bentuk string `string`.
+
+Pada kode di atas, tidak akan terjadi panic error, karena semua operasi type assertion adalah dilakukan menggunakan tipe data yang sudah sesuai dengan tipe data nilai aslinya. Seperti `data["nama"]` yang merupakan `string` pasti bisa di-asertasi ke tipe `string`.
+
+Coba lakukan asertasi ke tipe yang tidak sesuai dengan tipe nilai aslinya, seperti `data["nama"].(int)`, hasilnya adalah panic error.
+
+Nah, dari penjelasan di atas, terlihat bahwa kita harus tau terlebih dahulu apa tipe data asli dari data yang tersimpan dalam interface. Jika misal tidak tau, maka bisa gunakan teknik di bawah ini untuk pengecekan sukses tidaknya proses asertasi.
+
+Tipe asli data pada variabel `interface{}` bisa diketahui dengan cara meng-casting ke tipe `type`, namun casting ini hanya bisa dilakukan pada `switch`.
+
+```go
+for _, val := range data {
+ switch val.(type) {
+ case string:
+ fmt.Println(val.(string))
+ case int:
+ fmt.Println(val.(int))
+ case float64:
+ fmt.Println(val.(float64))
+ case bool:
+ fmt.Println(val.(bool))
+ case []string:
+ fmt.Println(val.([]string))
+ default:
+ fmt.Println(val.(int))
+ }
+}
+```
+
+Kombinasi `switch` - `case` bisa dimanfaatkan untuk deteksi tipe konkret data yang bertipe `interface{}`, contoh penerapannya seperti pada kode di atas.
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-defer-exit.md b/en/content-en/A-defer-exit.md
new file mode 100644
index 000000000..7cedb64e7
--- /dev/null
+++ b/en/content-en/A-defer-exit.md
@@ -0,0 +1,140 @@
+# A.36. Defer & Exit
+
+**Defer** digunakan untuk mengakhirkan eksekusi sebuah statement tepat sebelum blok fungsi selesai. Sedangkan **Exit** digunakan untuk menghentikan program secara paksa (ingat, menghentikan program, tidak seperti `return` yang hanya menghentikan blok kode).
+
+## A.36.1. Penerapan keyword `defer`
+
+Seperti yang sudah dijelaskan singkat di atas, bahwa defer digunakan untuk mengakhirkan eksekusi baris kode **dalam skope blok fungsi**. Ketika eksekusi blok sudah hampir selesai, statement yang di-defer dijalankan.
+
+Defer bisa ditempatkan di mana saja, awal maupun akhir blok. Tetapi tidak mempengaruhi kapan waktu dieksekusinya, akan selalu dieksekusi di akhir.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ defer fmt.Println("halo")
+ fmt.Println("selamat datang")
+}
+```
+
+Output program:
+
+![Penerapan
+ defer
](images/A_defer_exit_1_defer.png)
+
+Keyword `defer` di atas akan mengakhirkan ekseusi `fmt.Println("halo")`, efeknya pesan `"halo"` akan muncul setelah `"selamat datang"`.
+
+Statement yang di-defer akan tetap muncul meskipun blok kode diberhentikan ditengah jalan menggunakan `return`. Contohnya seperti pada kode berikut.
+
+```go
+func main() {
+ orderSomeFood("pizza")
+ orderSomeFood("burger")
+}
+
+func orderSomeFood(menu string) {
+ defer fmt.Println("Terimakasih, silakan tunggu")
+ if menu == "pizza" {
+ fmt.Print("Pilihan tepat!", " ")
+ fmt.Print("Pizza ditempat kami paling enak!", "\n")
+ return
+ }
+
+ fmt.Println("Pesanan anda:", menu)
+}
+```
+
+Output program:
+
+![Penerapan defer
dengan return
](images/A_defer_exit_2_defer_return.png)
+
+Info tambahan, ketika ada banyak statement yang di-defer, maka seluruhnya akan dieksekusi di akhir secara berurutan.
+
+## A.36.2. Kombinasi `defer` dan IIFE
+
+Penulis ingatkan lagi bahwa eksekusi defer adalah di akhir blok fungsi, bukan blok lainnya seperti blok seleksi kondisi.
+
+```go
+func main() {
+ number := 3
+
+ if number == 3 {
+ fmt.Println("halo 1")
+ defer fmt.Println("halo 3")
+ }
+
+ fmt.Println("halo 2")
+}
+```
+
+Output program:
+
+```
+halo 1
+halo 2
+halo 3
+```
+
+Pada contoh di atas `halo 3` akan tetap di print setelah `halo 2` meskipun statement defer dipergunakan dalam blok seleksi kondisi `if`. Hal ini karena defer eksekusinya terjadi pada akhir blok fungsi (dalam contoh di atas `main()`), bukan pada akhir blok `if`.
+
+Agar `halo 3` bisa dimunculkan di akhir blok `if`, maka harus dibungkus dengan IIFE. Contoh:
+
+```go
+func main() {
+ number := 3
+
+ if number == 3 {
+ fmt.Println("halo 1")
+ func() {
+ defer fmt.Println("halo 3")
+ }()
+ }
+
+ fmt.Println("halo 2")
+}
+```
+
+Output program:
+
+```
+halo 1
+halo 3
+halo 2
+```
+
+Bisa dilihat `halo 3` muncul sebelum `halo 2`, karena dalam blok seleksi kondisi `if` eksekusi defer terjadi dalam blok fungsi anonymous (IIFE).
+
+## A.36.3. Penerapan Fungsi `os.Exit()`
+
+Exit digunakan untuk menghentikan program secara paksa pada saat itu juga. Semua statement setelah exit tidak akan dieksekusi, termasuk juga defer.
+
+Fungsi `os.Exit()` berada dalam package `os`. Fungsi ini memiliki sebuah parameter bertipe numerik yang wajib diisi. Angka yang dimasukkan akan muncul sebagai **exit status** ketika program berhenti.
+
+```go
+package main
+
+import "fmt"
+import "os"
+
+func main() {
+ defer fmt.Println("halo")
+ os.Exit(1)
+ fmt.Println("selamat datang")
+}
+```
+
+Meskipun `defer fmt.Println("halo")` ditempatkan sebelum `os.Exit()`, statement tersebut tidak akan dieksekusi, karena di-tengah fungsi program dihentikan secara paksa.
+
+![Penerapan exit
](images/A_defer_exit_3_exit.png)
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-encoding-base64.md b/en/content-en/A-encoding-base64.md
new file mode 100644
index 000000000..b0f1255ee
--- /dev/null
+++ b/en/content-en/A-encoding-base64.md
@@ -0,0 +1,92 @@
+# A.46. Encode - Decode Base64
+
+Go menyediakan package `encoding/base64`, berisikan fungsi-fungsi untuk kebutuhan **encode** dan **decode** data ke bentuk base64 dan sebaliknya. Data yang akan di-encode harus bertipe `[]byte`, maka perlu dilakukan casting untuk data-data yang tipenya belum `[]byte`.
+
+Proses encoding dan decoding bisa dilakukan via beberapa cara yang pada chapter ini kita akan pelajari.
+
+## A.46.1. Penerapan Fungsi `EncodeToString()` & `DecodeString()`
+
+Fungsi `EncodeToString()` digunakan untuk encode data dari bentuk string ke base64. Fungsi `DecodeString()` melakukan kebalikan dari `EncodeToString()`. Berikut adalah contoh penerapannya.
+
+```go
+package main
+
+import "encoding/base64"
+import "fmt"
+
+func main() {
+ var data = "john wick"
+
+ var encodedString = base64.StdEncoding.EncodeToString([]byte(data))
+ fmt.Println("encoded:", encodedString)
+
+ var decodedByte, _ = base64.StdEncoding.DecodeString(encodedString)
+ var decodedString = string(decodedByte)
+ fmt.Println("decoded:", decodedString)
+}
+```
+
+Variabel `data` yang bertipe `string`, harus di-casting terlebih dahulu ke dalam bentuk `[]byte` sebelum di-encode menggunakan fungsi `base64.StdEncoding.EncodeToString()`. Hasil encode adalah data base64 bertipe `string`.
+
+Sedangkan pada fungsi decode `base64.StdEncoding.DecodeString()`, data base64 bertipe `string` di-decode kembali ke string aslinya, tapi bertipe `[]byte`. Ekspresi `string(decodedByte)` menjadikan data `[]byte` tersebut berubah menjadi string.
+
+![Encode & decode data string](images/A_encoding_base64_1_encode_decode.png)
+
+## A.46.2. Penerapan Fungsi `Encode()` & `Decode()`
+
+Kedua fungsi ini kegunaannya sama dengan fungsi yang sebelumnya kita bahas, salah satu pembedanya adalah data yang akan dikonversi dan hasilnya bertipe `[]byte`. Penggunaan cara ini cukup panjang karena variabel penyimpan hasil encode maupun decode harus disiapkan terlebih dahulu, dan harus memiliki lebar data sesuai dengan hasil yang akan ditampung (yang nilainya bisa dicari menggunakan fungsi `EncodedLen()` dan `DecodedLen()`).
+
+Lebih jelasnya silakan perhatikan contoh berikut.
+
+```go
+var data = "john wick"
+
+var encoded = make([]byte, base64.StdEncoding.EncodedLen(len(data)))
+base64.StdEncoding.Encode(encoded, []byte(data))
+var encodedString = string(encoded)
+fmt.Println(encodedString)
+
+var decoded = make([]byte, base64.StdEncoding.DecodedLen(len(encoded)))
+var _, err = base64.StdEncoding.Decode(decoded, encoded)
+if err != nil {
+ fmt.Println(err.Error())
+}
+var decodedString = string(decoded)
+fmt.Println(decodedString)
+```
+
+Fungsi `base64.StdEncoding.EncodedLen(len(data))` menghasilkan informasi lebar variable `data` ketika sudah di-encode. Nilai tersebut kemudian ditentukan sebagai lebar alokasi tipe `[]byte` pada variabel `encoded` yang nantinya digunakan untuk menampung hasil encoding.
+
+Fungsi `base64.StdEncoding.DecodedLen()` memiliki kegunaan sama dengan `EncodedLen()`, hanya saja digunakan untuk keperluan decoding.
+
+Dibanding 2 fungsi sebelumnya, fungsi `Encode()` dan `Decode()` ini memiliki beberapa perbedaan. Selain lebar data penampung encode/decode harus dicari terlebih dahulu, terdapat perbedaan lainnya, yaitu pada fungsi ini hasil encode/decode tidak didapat dari nilai kembalian, melainkan dari parameter. Variabel yang digunakan untuk menampung hasil, disisipkan pada parameter fungsi tersebut.
+
+Pada pemanggilan fungsi encode/decode, variabel `encoded` dan `decoded` tidak disisipkan nilai pointer-nya, cukup di-pass dengan cara biasa, tipe datanya sudah dalam bentuk `[]byte`.
+
+## A.46.3. Encode & Decode Data URL
+
+Khusus encode data string yang isinya merupakan URL, lebih efektif menggunakan `URLEncoding` dibandingkan `StdEncoding`.
+
+Cara penerapannya kurang lebih sama, bisa menggunakan metode pertama maupun metode kedua yang sudah dibahas di atas. Cukup ganti `StdEncoding` menjadi `URLEncoding`.
+
+```go
+var data = "https://kalipare.com/"
+
+var encodedString = base64.URLEncoding.EncodeToString([]byte(data))
+fmt.Println(encodedString)
+
+var decodedByte, _ = base64.URLEncoding.DecodeString(encodedString)
+var decodedString = string(decodedByte)
+fmt.Println(decodedString)
+```
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-error-panic-recover.md b/en/content-en/A-error-panic-recover.md
new file mode 100644
index 000000000..7f2d86e9f
--- /dev/null
+++ b/en/content-en/A-error-panic-recover.md
@@ -0,0 +1,229 @@
+# A.37. Error, Panic, dan Recover
+
+Error merupakan topik yang sangat penting dalam pemrograman Go, salah satu alasannya karena Go tidak mengadopsi konsep exception.
+
+Pada chapter ini kita akan belajar tentang pemanfaatan error dan cara membuat custom error. Selain itu, kita juga akan belajar tentang penggunaan **panic** untuk memunculkan panic error, dan **recover** untuk mengatasinya.
+
+## A.37.1. Pemanfaatan Error
+
+Di go, `error` merupakan sebuah tipe data. Error memiliki beberapa property yang salah satunya adalah method `Error()`. Method ini mengembalikan detail pesan error dalam string. Error termasuk tipe yang isinya bisa `nil`.
+
+Pada praktik pemrograman Go, pembaca akan menemui banyak sekali fungsi yang mengembalikan nilai balik lebih dari satu, yang biasanya salah satunya adalah bertipe `error`.
+
+Contohnya seperti pada fungsi `strconv.Atoi()`. Fungsi tersebut digunakan untuk konversi data string menjadi numerik. Fungsi ini mengembalikan 2 nilai balik. Nilai balik pertama adalah hasil konversi, dan nilai balik kedua adalah `error`. Ketika konversi berjalan mulus, nilai balik kedua akan bernilai `nil`. Sedangkan ketika konversi gagal, penyebabnya bisa langsung diketahui dari error yang dikembalikan.
+
+Di bawah ini merupakan contoh program sederhana untuk deteksi inputan dari user, apakah numerik atau bukan. Pada kode tersebut kita akan belajar mengenai pemanfaatan error.
+
+```go
+package main
+
+import (
+ "fmt"
+ "strconv"
+)
+
+func main() {
+ var input string
+ fmt.Print("Type some number: ")
+ fmt.Scanln(&input)
+
+ var number int
+ var err error
+ number, err = strconv.Atoi(input)
+
+ if err == nil {
+ fmt.Println(number, "is number")
+ } else {
+ fmt.Println(input, "is not number")
+ fmt.Println(err.Error())
+ }
+}
+```
+
+Jalankan program, maka muncul tulisan `"Type some number: "`. Ketik angka bebas, jika sudah maka enter.
+
+Statement `fmt.Scanln(&input)` dipergunakan untuk men-capture inputan yang diketik oleh user sebelum dia menekan enter, lalu menyimpannya sebagai string ke variabel `input`.
+
+Selanjutnya variabel tersebut dikonversi ke tipe numerik menggunakan `strconv.Atoi()`. Fungsi tersebut mengembalikan 2 data, ditampung oleh `number` dan `err`.
+
+Data pertama (`number`) berisi hasil konversi. Dan data kedua `err`, berisi informasi errornya (jika memang terjadi error ketika proses konversi).
+
+Setelah itu dilakukan pengecekkan, ketika tidak ada error, `number` ditampilkan. Dan jika ada error, `input` ditampilkan beserta pesan errornya.
+
+Pesan error bisa didapat dari method `Error()` milik tipe `error`.
+
+![Penerapan error](images/A_error_panic_recover_1_error.png)
+
+## A.37.2. Membuat Custom Error
+
+Selain memanfaatkan error hasil kembalian suatu fungsi internal yang tersedia, kita juga bisa membuat objek error sendiri dengan menggunakan fungsi `errors.New()` (harus import package `errors` terlebih dahulu).
+
+Pada contoh berikut ditunjukkan bagaimana cara membuat custom error. Pertama siapkan fungsi dengan nama `validate()`, yang nantinya digunakan untuk pengecekan input, apakah inputan kosong atau tidak. Ketika kosong, maka error baru akan dibuat.
+
+```go
+package main
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+)
+
+func validate(input string) (bool, error) {
+ if strings.TrimSpace(input) == "" {
+ return false, errors.New("cannot be empty")
+ }
+ return true, nil
+}
+```
+
+Selanjutnya di fungsi main, buat proses sederhana untuk capture inputan user. Manfaatkan fungsi `validate()` untuk mengecek inputannya.
+
+```go
+func main() {
+ var name string
+ fmt.Print("Type your name: ")
+ fmt.Scanln(&name)
+
+ if valid, err := validate(name); valid {
+ fmt.Println("halo", name)
+ } else {
+ fmt.Println(err.Error())
+ }
+}
+```
+
+Fungsi `validate()` mengembalikan 2 data. Data pertama adalah nilai `bool` yang menandakan inputan apakah valid atau tidak. Data ke-2 adalah pesan error-nya (jika inputan tidak valid).
+
+Fungsi `strings.TrimSpace()` digunakan untuk menghilangkan karakter spasi sebelum dan sesudah string. Ini dibutuhkan karena user bisa saja menginputkan spasi lalu enter.
+
+Ketika inputan tidak valid, maka error baru dibuat dengan memanfaatkan fungsi `errors.New()`.
+
+![Custom error](images/A_error_panic_recover_2_custom_error.png)
+
+Selain menggunakan `errors.New()`, objek error bisa dibuat via fungsi `fmt.Errorf()`. Pengaplikasiannya mirip, perbedaannya fungsi `fmt.Errorf()` mendukung format string.
+
+## A.37.3. Penggunaan `panic`
+
+Panic digunakan untuk menampilkan *stack trace* error sekaligus menghentikan flow goroutine. Setelah ada panic, proses akan terhenti, apapun setelah tidak di-eksekusi kecuali proses yang sudah di-defer sebelumnya (akan muncul sebelum panic error).
+
+> Perlu diingat bahwa `main()` juga merupakan goroutine, maka behaviour yang sama adalah berlaku.
+
+Panic menampilkan pesan error di console, sama seperti `fmt.Println()`. Informasi error yang ditampilkan adalah stack trace error, isinya sangat detail dan heboh.
+
+Kembali ke praktek, pada program yang telah kita buat tadi, ubah `fmt.Println()` yang berada di dalam blok kondisi `else` pada fungsi main menjadi `panic()`, lalu tambahkan `fmt.Println()` setelahnya.
+
+```go
+func main() {
+ var name string
+ fmt.Print("Type your name: ")
+ fmt.Scanln(&name)
+
+ if valid, err := validate(name); valid {
+ fmt.Println("halo", name)
+ } else {
+ panic(err.Error())
+ fmt.Println("end")
+ }
+}
+```
+
+Jalankan program lalu langsung tekan enter, maka panic error muncul dan baris kode setelahnya tidak dijalankan.
+
+![Menampilkan error menggunakan panic](images/A_error_panic_recover_3_panic.png)
+
+## A.37.4. Penggunaan `recover`
+
+Recover berguna untuk meng-handle panic error. Pada saat panic error muncul, recover men-take-over goroutine yang sedang panic dan efek sampingnya pesan panic tidak muncul dan eksekusi program adalah tidak error.
+
+Ok, mari kita modifikasi sedikit fungsi di-atas untuk mempraktekkan bagaimana cara penggunaan recover. Tambahkan fungsi `catch()`, dalam fungsi ini terdapat statement `recover()` yang dia akan mengembalikan pesan panic error yang seharusnya muncul.
+
+Untuk menggunakan recover, fungsi/closure/IIFE di mana `recover()` berada harus dieksekusi dengan cara di-defer.
+
+```go
+func catch() {
+ if r := recover(); r != nil {
+ fmt.Println("Error occured", r)
+ } else {
+ fmt.Println("Application running perfectly")
+ }
+}
+
+func main() {
+ defer catch()
+
+ var name string
+ fmt.Print("Type your name: ")
+ fmt.Scanln(&name)
+
+ if valid, err := validate(name); valid {
+ fmt.Println("halo", name)
+ } else {
+ panic(err.Error())
+ fmt.Println("end")
+ }
+}
+```
+
+Output program:
+
+![Handle panic menggunakan recover](images/A_error_panic_recover_4_recover.png)
+
+## A.37.5. Pemanfaatan `recover` pada IIFE
+
+Contoh penerapan recover pada IIFE:
+
+```go
+func main() {
+ defer func() {
+ if r := recover(); r != nil {
+ fmt.Println("Panic occured", r)
+ } else {
+ fmt.Println("Application running perfectly")
+ }
+ }()
+
+ panic("some error happen")
+}
+```
+
+Dalam real-world development, ada kalanya recover dibutuhkan tidak dalam blok fungsi terluar, tetapi dalam blok fungsi yg lebih spesifik.
+
+Silakan perhatikan contoh kode recover perulangan berikut. Umumnya, jika terjadi panic error, maka proses proses dalam scope blok fungsi akan terhenti, mengakibatkan perulangan juga akan terhenti secara paksa. Pada contoh berikut kita coba terapkan cara handle panic error tanpa menghentikan perulangan itu sendiri.
+
+```go
+func main() {
+ data := []string{"superman", "aquaman", "wonder woman"}
+
+ for _, each := range data {
+
+ func() {
+
+ // recover untuk IIFE dalam perulangan
+ defer func() {
+ if r := recover(); r != nil {
+ fmt.Println("Panic occured on looping", each, "| message:", r)
+ } else {
+ fmt.Println("Application running perfectly")
+ }
+ }()
+
+ panic("some error happen")
+ }()
+
+ }
+}
+```
+
+Bisa dilihat di dalam perulangan terdapat sebuah IIFE untuk recover panic dan juga ada kode untuk men-trigger panic error secara paksa. Ketika panic error terjadi, maka idealnya perulangan terhenti, tetapi pada contoh di atas tidak, dikarenakan operasi dalam perulangan sudah di bungkus dalam IIFE dan seperti yang kita tau sifat panic error adalah menghentikan proses secara paksa dalam scope blok fungsi.
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-exec.md b/en/content-en/A-exec.md
new file mode 100644
index 000000000..e6943b853
--- /dev/null
+++ b/en/content-en/A-exec.md
@@ -0,0 +1,64 @@
+# A.49. Exec
+
+**Exec** digunakan untuk eksekusi perintah command line lewat kode program. Command yang bisa dieksekusi adalah semua command yang bisa dieksekusi di command line sesuai sistem operasinya (Linux-distros, Windows, MacOS, dan lainnya).
+
+## A.49.1. Penggunaan Exec
+
+Go menyediakan package `exec` isinya banyak sekali API atau fungsi untuk keperluan eksekusi perintah command line.
+
+Cara eksekusi command adalah menggunakan fungsi `exec.Command()` dengan argument pemanggilan fungsi diisi command CLI yang diinginkan. Contoh:
+
+```go
+package main
+
+import "fmt"
+import "os/exec"
+
+func main() {
+ var output1, _ = exec.Command("ls").Output()
+ fmt.Printf(" -> ls\n%s\n", string(output1))
+
+ var output2, _ = exec.Command("pwd").Output()
+ fmt.Printf(" -> pwd\n%s\n", string(output2))
+
+ var output3, _ = exec.Command("git", "config", "user.name").Output()
+ fmt.Printf(" -> git config user.name\n%s\n", string(output3))
+}
+```
+
+Fungsi `exec.Command()` menjalankan command yang dituliskan pada argument pemanggilan fungsi.
+
+Untuk mendapatkan outputnya, chain saja langsung dengan method `Output()`. Output yang dihasilkan berbentuk `[]byte`, maka pastikan cast ke string terlebih dahulu untuk membaca isi outputnya.
+
+![Ekeskusi command menggunakan exec](images/A_exec_1_exec.png)
+
+## A.49.2. Rekomendasi Penggunaan Exec
+
+Ada kalanya saat eksekusi command yang sudah jelas-jelas ada (seperti `ls`, `dir`, atau lainnya), error muncul menginformasikan bahwa command tidak ditemukan (command not found). Hal ini biasanya terjadi karena executable dari command-command tersebut tidak ada. Seperti di windows tidak ada `cmd` atau `cmd.exe`, di Linux tidak ditentukan apakah memakai `bash` atau `shell`, dan lainnya
+
+Untuk mengatasi masalah ini, tambahkan `bash -c` pada sistem operasi berbasi Linux, MacOS, Unix, atau `cmd /C` untuk OS Windows.
+
+```go
+if runtime.GOOS == "windows" {
+ output, err = exec.Command("cmd", "/C", "git config user.name").Output()
+} else {
+ output, err = exec.Command("bash", "-c", "git config user.name").Output()
+}
+```
+
+Statement `runtime.GOOS` penggunaannya mengembalikan informasi sistem operasi dalam bentuk string. Manfaatkan seleksi kondisi untuk memastikan command yang ingin dieksekusi sudah sesuai dengan OS atau belum.
+
+## A.49.3. Method Exec Lainnya
+
+Selain `.Output()` ada sangat banyak sekali API untuk keperluan komunikasi dengan OS/CLI yang bisa dipergunakan. Lebih detailnya silakan langsung melihat dokumentasi package tersebut di [https://golang.org/pkg/os/exec/](https://golang.org/pkg/os/exec/)
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-file.md b/en/content-en/A-file.md
new file mode 100644
index 000000000..28aa84253
--- /dev/null
+++ b/en/content-en/A-file.md
@@ -0,0 +1,163 @@
+# A.50. File
+
+Pada chapter ini kita akan belajar beberapa teknik operasi file yang paling dasar.
+
+## A.50.1. Membuat File Baru
+
+Pembuatan file di Go sangat mudah, dilakukan dengan memanfaatkan fungsi `os.Create()` disertai path file sebagai argument pemanggilan fungsi. Jika ternyata file yang akan dibuat sudah ada duluan, maka operasi `os.Create()` akan menimpa file yang sudah ada dengan file baru. Untuk menghindari penimpaan file, gunakan fungsi `os.IsNotExist()` untuk mendeteksi apakah file yang ingin dibuat sudah ada atau belum.
+
+Contoh program operasi pembuatan file:
+
+```go
+package main
+
+import "fmt"
+import "os"
+
+var path = "/Users/novalagung/Documents/temp/test.txt"
+
+func isError(err error) bool {
+ if err != nil {
+ fmt.Println(err.Error())
+ }
+
+ return (err != nil)
+}
+
+func createFile() {
+ // deteksi apakah file sudah ada
+ var _, err = os.Stat(path)
+
+ // buat file baru jika belum ada
+ if os.IsNotExist(err) {
+ var file, err = os.Create(path)
+ if isError(err) { return }
+ defer file.Close()
+ }
+
+ fmt.Println("==> file berhasil dibuat", path)
+}
+
+func main() {
+ createFile()
+}
+```
+
+Fungsi `os.Stat()` mengembalikan 2 data, yaitu informasi tetang path yang dicari, dan error (jika ada). Masukkan error kembalian fungsi tersebut sebagai argument pemanggilan fungsi `os.IsNotExist()`, untuk mengetahui apakah file yang akan dibuat sudah ada. Jika rupanya file belum ada ada, maka fungsi tersebut akan mengembalikan nilai `true`.
+
+Fungsi `os.Create()` ini mengembalikan objek bertipe `*os.File`. File yang baru dibuat, statusnya adalah otomatis **open**. Setelah operasi file selesai, file harus di-**close** menggunakan method `file.Close()`.
+
+Membiarkan file terbuka ketika sudah tak lagi digunakan adalah tidak baik, karena ada efek ke memory dan akses ke file itu sendiri, file menjadi terkunci/locked, membuatnya tidak bisa diakses oleh proses lain selama status file statusnya masih **open** dan belum di-close.
+
+![Membuat file baru](images/A_file_1_create.png)
+
+## A.50.2. Mengedit Isi File
+
+Untuk mengedit file, yang pertama perlu dilakukan adalah membuka file dengan level akses **write**. Setelah mendapatkan objek file-nya, gunakan method `WriteString()` untuk penulisan data. Di akhir, panggil method `Sync()` untuk menyimpan perubahan.
+
+```go
+func writeFile() {
+ // buka file dengan level akses READ & WRITE
+ var file, err = os.OpenFile(path, os.O_RDWR, 0644)
+ if isError(err) { return }
+ defer file.Close()
+
+ // tulis data ke file
+ _, err = file.WriteString("halo\n")
+ if isError(err) { return }
+ _, err = file.WriteString("mari belajar golang\n")
+ if isError(err) { return }
+
+ // simpan perubahan
+ err = file.Sync()
+ if isError(err) { return }
+
+ fmt.Println("==> file berhasil di isi")
+}
+
+func main() {
+ writeFile()
+}
+```
+
+Pada program di atas, file dibuka dengan level akses **read** dan **write** dengan kode permission **0664**. Setelah itu, beberapa string diisikan ke dalam file tersebut menggunakan `WriteString()`. Di akhir, semua perubahan terhadap file menjadi tersimpan dengan adanya pemanggilan method `Sync()`.
+
+![Mengedit file](images/A_file_2_write.png)
+
+## A.50.3. Membaca Isi File
+
+File yang ingin dibaca harus dibuka terlebih dahulu menggunakan fungsi `os.OpenFile()` dengan level akses minimal adalah **read**. Dari object file kembalian fungsi tersebut, gunakan method `Read()` dengan disertai argument berupa variabel yang akan menampung data hasil operasi baca.
+
+```go
+// tambahkan di bagian import package io
+import "io"
+
+func readFile() {
+ // buka file
+ var file, err = os.OpenFile(path, os.O_RDONLY, 0644)
+ if isError(err) { return }
+ defer file.Close()
+
+ // baca file
+ var text = make([]byte, 1024)
+ for {
+ n, err := file.Read(text)
+ if err != io.EOF {
+ if isError(err) { break }
+ }
+ if n == 0 {
+ break
+ }
+ }
+ if isError(err) { return }
+
+ fmt.Println("==> file berhasil dibaca")
+ fmt.Println(string(text))
+}
+
+func main() {
+ readFile()
+}
+```
+
+Fungsi `os.OpenFile()` dalam pemanggilannya memerlukan beberapa argument parameter untuk di-isi:
+
+ 1. Parameter pertama adalah path file yang akan dibuka.
+ 2. Parameter kedua adalah level akses. `os.O_RDONLY` maksudnya adalah **read only**.
+ 3. Parameter ketiga adalah permission file-nya.
+
+Variabel `text` disiapkan bertipe slice `[]byte` dengan alokasi elemen `1024`. Variabel tersebut bertugas menampung data hasil statement `file.Read()`. Proses pembacaan file dilakukan terus menerus, berurutan dari baris pertama hingga akhir.
+
+Error yang muncul ketika eksekusi `file.Read()` akan di-filter, ketika error adalah selain `io.EOF` maka proses baca file akan berlanjut. Error `io.EOF` sendiri menandakan bahwa file yang sedang dibaca adalah baris terakhir isi atau **end of file**.
+
+![Membaca isi file](images/A_file_3_read.png)
+
+## A.50.4. Menghapus File
+
+Operasi menghapus file dilakukan via fungsi `os.Remove()`. Panggil fungsi tersebut, kemudian isi path dari file yang ingin dihapus sebagai argument fungsi.
+
+```go
+func deleteFile() {
+ var err = os.Remove(path)
+ if isError(err) { return }
+
+ fmt.Println("==> file berhasil di delete")
+}
+
+func main() {
+ deleteFile()
+}
+```
+
+![Menghapus file](images/A_file_4_delete.png)
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-fungsi-closure.md b/en/content-en/A-fungsi-closure.md
new file mode 100644
index 000000000..5d28f3964
--- /dev/null
+++ b/en/content-en/A-fungsi-closure.md
@@ -0,0 +1,183 @@
+# A.21. Fungsi Closure
+
+Definisi **Closure** adalah suatu *anonymous function* (atau fungsi tanpa nama) yang disimpan dalam variabel. Dengan adanya closure, kita bisa mendesain beberapa hal diantaranya seperti: membuat fungsi di dalam fungsi, atau bahkan membuat fungsi yang mengembalikan fungsi. Closure biasa dimanfaatkan untuk membungkus suatu proses yang hanya dijalankan sekali saja atau hanya dipakai pada blok tertentu saja.
+
+## A.21.1. Closure Disimpan Sebagai Variabel
+
+Sebuah fungsi tanpa nama bisa disimpan dalam variabel. Variabel closure memiliki sifat seperti fungsi yang disimpannya.
+
+Di bawah ini adalah contoh program sederhana yang menerapkan closure untuk pencarian nilai terendah dan tertinggi dari data array. Logika pencarian dibungkus dalam closure yang ditampung oleh variabel `getMinMax`.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ var getMinMax = func(n []int) (int, int) {
+ var min, max int
+ for i, e := range n {
+ switch {
+ case i == 0:
+ max, min = e, e
+ case e > max:
+ max = e
+ case e < min:
+ min = e
+ }
+ }
+ return min, max
+ }
+
+ var numbers = []int{2, 3, 4, 3, 4, 2, 3}
+ var min, max = getMinMax(numbers)
+ fmt.Printf("data : %v\nmin : %v\nmax : %v\n", numbers, min, max)
+}
+```
+
+Bisa dilihat pada kode di atas bagaimana cara deklarasi closure dan cara pemanggilannya. Sedikit berbeda memang dibanding pembuatan fungsi biasa, pada closure fungsi ditulis tanpa memiliki nama lalu ditampung ke variabel.
+
+```go
+var getMinMax = func(n []int) (int, int) {
+ // ...
+}
+```
+
+Cara pemanggilan closure adalah dengan memperlakukan variabel closure seperti fungsi, dituliskan seperti pemanggilan fungsi.
+
+```go
+var min, max = getMinMax(numbers)
+```
+
+Output program:
+
+![Penerapan closure](images/A_fungsi_closure_1_closure.png)
+
+## A.21.2. Penjelasan tambahan
+
+Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan:
+
+#### ◉ Penggunaan Template String `%v`
+
+Template `%v` digunakan untuk menampilkan data tanpa melihat tipe datanya. Jadi bisa digunakan untuk menampilkan data array, int, float, bool, dan lainnya. Bisa dilihat di contoh statement, data bertipe array dan numerik ditampilkan menggunakan `%v`.
+
+```go
+fmt.Printf("data : %v\nmin : %v\nmax : %v\n", numbers, min, max)
+```
+
+Template `%v` ini biasa dimanfaatkan untuk menampilkan sebuah data yang tipe nya bisa dinamis atau belum diketahui. Biasa digunakan untuk keperluan debugging, misalnya untuk menampilkan data bertipe `any` atau `interface{}`.
+
+> Pembahasan mengenai tipe data `any` atau `interface{}` ada di chapter [A.27. Interface](/A-interface.html)
+
+#### ◉ Immediately-Invoked Function Expression (IIFE)
+
+Closure jenis IIFE ini eksekusinya adalah langsung saat deklarasi. Teknik ini biasa diterapkan untuk membungkus proses yang hanya dilakukan sekali. IIFE bisa memiliki nilai balik atau bisa juga tidak.
+
+Di bawah ini merupakan contoh sederhana penerapan metode IIFE untuk filtering data array.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ var numbers = []int{2, 3, 0, 4, 3, 2, 0, 4, 2, 0, 3}
+
+ var newNumbers = func(min int) []int {
+ var r []int
+ for _, e := range numbers {
+ if e < min {
+ continue
+ }
+ r = append(r, e)
+ }
+ return r
+ }(3)
+
+ fmt.Println("original number :", numbers)
+ fmt.Println("filtered number :", newNumbers)
+}
+```
+
+Output program:
+
+![Penerapan IIFE](images/A_fungsi_closure_2_iife.png)
+
+Ciri khas dari penulisan IIFE adalah adanya tanda kurung parameter yang ditulis di akhir deklarasi closure. Jika IIFE memiliki parameter, maka argument-nya juga ditulis. Contoh:
+
+```go
+var newNumbers = func(min int) []int {
+ // ...
+}(3)
+```
+
+Di contoh sederhana di atas, IIFE menghasilkan nilai balik yang ditampung variabel `newNumber`. Perlu diperhatikan bahwa yang ditampung adalah **nilai kembaliannya** bukan body fungsi atau **closure**-nya.
+
+> Closure bisa juga dengan gaya manifest typing, caranya dengan menuliskan skema closure-nya sebagai tipe data. Contoh:
+ var closure (func (string, int, []string) int)
closure = func (a string, b int, c []string) int {
// ..
}
+## A.21.3. Closure Sebagai Nilai Kembalian
+
+Salah satu keunikan lain dari closure adalah: closure bisa dijadikan sebagai nilai balik fungsi. Cukup aneh, tapi pada kondisi tertentu teknik ini sangat berguna. Sebagai contoh, di bawah ini dideklarasikan sebuah fungsi bernama `findMax()` yang salah satu nilai kembaliannya adalah berupa closure.
+
+```go
+package main
+
+import "fmt"
+
+func findMax(numbers []int, max int) (int, func() []int) {
+ var res []int
+ for _, e := range numbers {
+ if e <= max {
+ res = append(res, e)
+ }
+ }
+ return len(res), func() []int {
+ return res
+ }
+}
+```
+
+Nilai kembalian ke-2 pada fungsi di atas adalah closure dengan skema `func() []int`. Bisa dilihat di bagian akhir, ada fungsi tanpa nama yang dikembalikan.
+
+```go
+return len(res), func() []int {
+ return res
+}
+```
+
+> Fungsi tanpa nama yang akan dikembalikan boleh disimpan pada variabel terlebih dahulu. Contohnya:var getNumbers = func() []int {
return res
}
return len(res), getNumbers
+Tentang fungsi `findMax()` sendiri, fungsi ini dibuat untuk mempermudah pencarian angka-angka yang nilainya di bawah atau sama dengan angka tertentu. Fungsi ini mengembalikan dua buah nilai balik:
+
+- Nilai balik pertama adalah jumlah angkanya.
+- Nilai balik kedua berupa closure yang mengembalikan angka-angka yang dicari.
+
+Berikut merupakan contoh implementasi fungsi tersebut:
+
+```go
+func main() {
+ var max = 3
+ var numbers = []int{2, 3, 0, 4, 3, 2, 0, 4, 2, 0, 3}
+ var howMany, getNumbers = findMax(numbers, max)
+ var theNumbers = getNumbers()
+
+ fmt.Println("numbers\t:", numbers)
+ fmt.Printf("find \t: %d\n\n", max)
+
+ fmt.Println("found \t:", howMany) // 9
+ fmt.Println("value \t:", theNumbers) // [2 3 0 3 2 0 2 0 3]
+}
+```
+
+Output program:
+
+![Kombinasi parameter biasa dan variadic](images/A_fungsi_closure_3_combination.png)
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-fungsi-multiple-return.md b/en/content-en/A-fungsi-multiple-return.md
new file mode 100644
index 000000000..e30d1a649
--- /dev/null
+++ b/en/content-en/A-fungsi-multiple-return.md
@@ -0,0 +1,106 @@
+# A.19. Fungsi Multiple Return
+
+Di Go, suatu fungsi bisa saja mengembalikan nilai belik lebih dari 1 buah. Teknik ini bisa menjadi alternatif selain menggunakan tipe data kolektif seperti `map`, slice, atau `struct` sebagai nilai balik. Pada chapter ini kita akan belajar penerapannya.
+
+## A.19.1. Penerapan Fungsi Multiple Return
+
+Cara membuat fungsi agar memiliki banyak nilai balik tidaklah sulit, caranya pada saat deklarasi fungsi, tulis semua tipe data nilai balik yang ingin dikembalikan. Kemudian dalam body fungsi, pada penggunaan keyword `return`, tulis semua data yang ingin dikembalikan. Contoh:
+
+```go
+package main
+
+import "fmt"
+import "math"
+
+func calculate(d float64) (float64, float64) {
+ // hitung luas
+ var area = math.Pi * math.Pow(d / 2, 2)
+ // hitung keliling
+ var circumference = math.Pi * d
+
+ // kembalikan 2 nilai
+ return area, circumference
+}
+```
+
+Fungsi `calculate()` di atas memiliki satu buah parameter yaitu `d` (diameter). Di dalam fungsi terdapat operasi perhitungan nilai **luas** dan **keliling** dari nilai `d`. Kedua hasilnya kemudian dijadikan sebagai return value.
+
+Cara pendefinisian banyak nilai balik bisa dilihat pada kode di atas, langsung tulis tipe data semua nilai balik dipisah tanda koma, lalu ditambahkan kurung di antaranya.
+
+```go
+func calculate(d float64) (float64, float64)
+```
+
+Tak lupa di bagian penulisan keyword `return` harus dituliskan juga semua data yang dijadikan nilai balik (dengan pemisah tanda koma).
+
+```go
+return area, circumference
+```
+
+Sekarang, coba panggil fungsi `calculate()` yang sudah dibuat untuk mencari nilai luas dan keliling dari suatu diameter.
+
+```go
+func main() {
+ var diameter float64 = 15
+ var area, circumference = calculate(diameter)
+
+ fmt.Printf("luas lingkaran\t\t: %.2f \n", area)
+ fmt.Printf("keliling lingkaran\t: %.2f \n", circumference)
+}
+```
+
+Output program:
+
+![Penerapan teknik multiple return](images/A_fungsi_multiple_return_1_multiple_return.png)
+
+Fungsi `calculate()` memiliki banyak nilai balik, maka dalam pemanggilannya harus disiapkan juga sejumlah variabel untuk menampung nilai balik fungsi (sesuai dengan jumlah nilai balik yang dideklarasikan).
+
+```go
+var area, circumference = calculate(diameter)
+```
+
+## A.19.2. Fungsi Dengan Predefined Return Value
+
+Keunikan lainnya yang jarang ditemui di bahasa lain adalah, di Go variabel yang digunakan sebagai nilai balik bisa didefinisikan di awal.
+
+```go
+func calculate(d float64) (area float64, circumference float64) {
+ area = math.Pi * math.Pow(d / 2, 2)
+ circumference = math.Pi * d
+
+ return
+}
+```
+
+Fungsi `calculate` kita modifikasi menjadi lebih sederhana. Bisa dilihat di kode di atas, ada cukup banyak perbedaan dibanding fungsi `calculate` sebelumnya. Perhatikan kode berikut.
+
+```go
+func calculate(d float64) (area float64, circumference float64) {
+```
+
+Fungsi dideklarasikan memiliki 2 buah tipe data, dan variabel yang nantinya dijadikan nilai balik juga dideklarasikan. Variabel `area` yang bertipe `float64`, dan `circumference` bertipe `float64`.
+
+Karena variabel nilai balik sudah ditentukan di awal, untuk mengembalikan nilai cukup dengan memanggil `return` tanpa perlu diikuti variabel apapun. Nilai terakhir `area` dan `circumference` sebelum pemanggilan keyword `return` adalah hasil dari fungsi di atas.
+
+## A.19.3. Penjelasan tambahan
+
+Ada beberapa hal baru dari kode di atas yang perlu dibahas, diantaranya `math.Pow()` dan `math.Pi`.
+
+#### ◉ Penggunaan Fungsi `math.Pow()`
+
+Fungsi `math.Pow()` digunakan untuk operasi pangkat nilai. `math.Pow(2, 3)` berarti 2 pangkat 3, hasilnya 8. Fungsi ini berada dalam package `math`.
+
+#### ◉ Penggunaan Konstanta `math.Pi`
+
+`math.Pi` adalah konstanta bawaan `package math` yang merepresentasikan **Pi** atau **22/7**.
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-fungsi-sebagai-parameter.md b/en/content-en/A-fungsi-sebagai-parameter.md
new file mode 100644
index 000000000..b714c0a39
--- /dev/null
+++ b/en/content-en/A-fungsi-sebagai-parameter.md
@@ -0,0 +1,111 @@
+# A.22. Fungsi Sebagai parameter
+
+Pada chapter sebelumnya kita telah belajar tentang fungsi yang mengembalikan nilai balik berupa fungsi. Kali ini topiknya tidak kalah unik, yaitu tentang fungsi yang memiliki parameter sebuah fungsi.
+
+Di Go, fungsi bisa dijadikan sebagai tipe data variabel, maka sangat memungkinkan untuk menjadikannya sebagai parameter.
+
+## A.22.1. Penerapan Fungsi Sebagai Parameter
+
+Cara membuat parameter fungsi adalah dengan langsung menuliskan skema fungsi nya sebagai tipe data. Contohnya bisa dilihat pada kode berikut.
+
+```go
+package main
+
+import "fmt"
+import "strings"
+
+func filter(data []string, callback func(string) bool) []string {
+ var result []string
+ for _, each := range data {
+ if filtered := callback(each); filtered {
+ result = append(result, each)
+ }
+ }
+ return result
+}
+```
+
+Parameter `callback` merupakan sebuah closure yang dideklarasikan bertipe `func(string) bool`. Closure tersebut dipanggil di tiap perulangan dalam fungsi `filter()`.
+
+Fungsi `filter()` sendiri kita buat untuk filtering data array (yang datanya didapat dari parameter pertama), dengan kondisi filter bisa ditentukan sendiri. Di bawah ini adalah contoh pemanfaatan fungsi tersebut.
+
+```go
+func main() {
+ var data = []string{"wick", "jason", "ethan"}
+ var dataContainsO = filter(data, func(each string) bool {
+ return strings.Contains(each, "o")
+ })
+ var dataLenght5 = filter(data, func(each string) bool {
+ return len(each) == 5
+ })
+
+ fmt.Println("data asli \t\t:", data)
+ // data asli : [wick jason ethan]
+
+ fmt.Println("filter ada huruf \"o\"\t:", dataContainsO)
+ // filter ada huruf "o" : [jason]
+
+ fmt.Println("filter jumlah huruf \"5\"\t:", dataLenght5)
+ // filter jumlah huruf "5" : [jason ethan]
+}
+```
+
+Ada cukup banyak hal yang terjadi di dalam tiap pemanggilan fungsi `filter()` di atas. Berikut adalah penjelasannya:
+
+ 1. Data array (yang didapat dari parameter pertama) akan di-looping.
+ 2. Di tiap perulangannya, closure `callback` dipanggil, dengan disisipkan data tiap elemen perulangan sebagai parameter.
+ 3. Closure `callback` berisikan kondisi filtering, dengan hasil bertipe `bool` yang kemudian dijadikan nilai balik dikembalikan.
+ 5. Di dalam fungsi `filter()` sendiri, ada proses seleksi kondisi (yang nilainya didapat dari hasil eksekusi closure `callback`). Ketika kondisinya bernilai `true`, maka data elemen yang sedang diulang dinyatakan lolos proses filtering.
+ 6. Data yang lolos ditampung variabel `result`. Variabel tersebut dijadikan sebagai nilai balik fungsi `filter()`.
+
+![Filtering data](images/A_fungsi_sebagai_parameter_1_filtering.png)
+
+Pada `dataContainsO`, parameter kedua fungsi `filter()` berisikan statement untuk deteksi apakah terdapat substring `"o"` di dalam nilai variabel `each` (yang merupakan data tiap elemen), jika iya, maka kondisi filter bernilai `true`, dan sebaliknya.
+
+Pada contoh ke-2 (`dataLength5`), closure `callback` berisikan statement untuk deteksi jumlah karakter tiap elemen. Jika ada elemen yang jumlah karakternya adalah 5, berarti elemen tersebut lolos filter.
+
+Memang butuh usaha ekstra untuk memahami pemanfaatan closure sebagai parameter fungsi. Tapi setelah paham, penerapan teknik ini pada kondisi yang tepat akan sangat berguna.
+
+## A.22.2. Alias Skema Closure
+
+Kita sudah mempelajari bahwa closure bisa dimanfaatkan sebagai tipe parameter, contohnya seperti pada fungsi `filter()`. Di fungsi tersebut kebetulan skema tipe parameter closure-nya tidak terlalu panjang, hanya ada satu buah parameter dan satu buah nilai balik.
+
+Untuk fungsi yang skema-nya cukup panjang, akan lebih baik jika menggunakan alias dalam pendefinisiannya, apalagi ketika ada parameter fungsi lain yang juga menggunakan skema yang sama, maka kita tidak perlu menuliskan skema panjang fungsi tersebut berulang-ulang.
+
+Membuat alias fungsi berarti menjadikan skema fungsi tersebut menjadi tipe data baru. Caranya dengan menggunakan keyword `type`. Contoh:
+
+```go
+type FilterCallback func(string) bool
+
+func filter(data []string, callback FilterCallback) []string {
+ // ...
+}
+```
+
+Skema `func(string) bool` diubah menjadi tipe dengan nama `FilterCallback`. Tipe tersebut kemudian digunakan sebagai tipe data parameter `callback`.
+
+## A.22.3. Penjelasan tambahan
+
+Di bawah ini merupakan penjelasan tambahan mengenai fungsi `strings.Contains()`.
+
+#### ◉ Penggunaan Fungsi `string.Contains()`
+
+Inti dari fungsi ini adalah untuk deteksi apakah sebuah substring adalah bagian dari string, jika iya maka akan bernilai `true`, dan sebaliknya. Contoh penggunaannya:
+
+```go
+var result = strings.Contains("Golang", "ang")
+// true
+```
+
+Variabel `result` bernilai `true` karena string `"ang"` merupakan bagian dari string `"Golang"`.
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-fungsi-variadic.md b/en/content-en/A-fungsi-variadic.md
new file mode 100644
index 000000000..056ad76f7
--- /dev/null
+++ b/en/content-en/A-fungsi-variadic.md
@@ -0,0 +1,156 @@
+# A.20. Fungsi Variadic
+
+Go mengadopsi konsep **variadic function** atau pembuatan fungsi dengan parameter bisa menampung nilai sejenis yang tidak terbatas jumlahnya.
+
+Parameter variadic memiliki sifat yang mirip dengan slice, yaitu nilai dari parameter-parameter yang disisipkan bertipe data sama, dan kesemuanya cukup ditampung oleh satu variabel saja. Cara pengaksesan tiap nilai juga mirip, yaitu dengan menggunakan index.
+
+Pada chapter ini kita akan belajar mengenai cara penerapan fungsi variadic.
+
+## A.20.1. Penerapan Fungsi Variadic
+
+Deklarasi parameter variadic sama dengan cara deklarasi variabel biasa, pembedanya adalah pada parameter jenis ini ditambahkan tanda titik tiga kali (`...`) tepat setelah penulisan variabel, sebelum tipe data. Nantinya semua nilai yang disisipkan sebagai parameter akan ditampung oleh variabel tersebut.
+
+Contoh program:
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ var avg = calculate(2, 4, 3, 5, 4, 3, 3, 5, 5, 3)
+ var msg = fmt.Sprintf("Rata-rata : %.2f", avg)
+ fmt.Println(msg)
+}
+
+func calculate(numbers ...int) float64 {
+ var total int = 0
+ for _, number := range numbers {
+ total += number
+ }
+
+ var avg = float64(total) / float64(len(numbers))
+ return avg
+}
+```
+
+Output program:
+
+![Contoh penerapan parameter variadic](images/A_fungsi_variadic_1_variadic_param.png)
+
+Bisa dilihat pada fungsi `calculate()`, parameter `numbers` dideklarasikan dengan disisipkan tanda 3 titik (`...`), menandakan bahwa `numbers` adalah sebuah parameter variadic dengan tipe data `int`.
+
+```go
+func calculate(numbers ...int) float64 {
+```
+
+Pemanggilan fungsi dilakukan seperti biasa, hanya saja jumlah parameter yang disisipkan bisa banyak.
+
+```go
+var avg = calculate(2, 4, 3, 5, 4, 3, 3, 5, 5, 3)
+```
+
+Nilai tiap parameter bisa diakses seperti cara pengaksesan tiap elemen slice. Pada contoh di atas metode yang dipilih adalah `for` - `range`.
+
+```go
+for _, number := range numbers {
+```
+
+## A.20.2. Penjelasan tambahan
+
+Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan:
+
+#### ◉ Penggunaan Fungsi `fmt.Sprintf()`
+
+Fungsi `fmt.Sprintf()` pada dasarnya sama dengan `fmt.Printf()`, hanya saja fungsi ini tidak menampilkan nilai, melainkan mengembalikan nilainya dalam bentuk string. Pada case di atas, nilai kembalian `fmt.Sprintf()` ditampung oleh variabel `msg`.
+
+Selain `fmt.Sprintf()`, ada juga `fmt.Sprint()` dan `fmt.Sprintln()`.
+
+#### ◉ Penggunaan Fungsi `float64()`
+
+Sebelumnya sudah dibahas bahwa `float64` merupakan tipe data. Tipe data jika ditulis sebagai fungsi (penandanya ada tanda kurungnya) menandakan bahwa digunakan untuk keperluan **casting**. Casting sendiri adalah teknik untuk konversi tipe sebuah data ke tipe lain. Sebagian besar tipe data dasar yang telah dipelajari pada chapter [A.9. Variabel](/A-variabel.html) bisa di-casting.
+
+Cara penerapan casting: panggil saja tipe data yang diingunkan seperti pemanggilan fungsi, lalu masukan data yang ingin dikonversi sebagai argument pemanggilan fungsi tersebut.
+
+Pada contoh di atas, variabel `total` yang tipenya adalah `int`, dikonversi menjadi `float64`, begitu juga `len(numbers)` yang menghasilkan `int` dikonversi ke `float64`.
+
+Variabel `avg` perlu dijadikan `float64` karena penghitungan rata-rata lebih sering menghasilkan nilai desimal.
+
+Operasi bilangan (perkalian, pembagian, dan lainnya) di Go hanya bisa dilakukan jika tipe datanya sejenis. Maka dari itulah perlu adanya casting ke tipe `float64` pada tiap operand.
+
+## A.20.3. Pengisian Parameter Fungsi Variadic Menggunakan Data Slice
+
+Slice bisa digunakan sebagai argument pada fungsi variadic. Caranya penerapannya: tulis saja nama variabel tapi disertai dengan tanda titik tiga kali, dituliskan tepat setelah nama variabel yang dijadikan parameter. Contohnya bisa dilihat pada kode berikut:
+
+```go
+var numbers = []int{2, 4, 3, 5, 4, 3, 3, 5, 5, 3}
+var avg = calculate(numbers...)
+var msg = fmt.Sprintf("Rata-rata : %.2f", avg)
+
+fmt.Println(msg)
+```
+
+Pada kode di atas, variabel `numbers` bertipe data slice int, disisipkan pada pemanggilan fungsi `calculate()` sebagai argument parameter fungsi variadic (bisa dilihat tanda 3 titik setelah penulisan variabel). Teknik ini sangat berguna pada case dimana sebuah data slice perlu untuk digunakan sebagai argument parameter variadic.
+
+Agar lebih jelas, perhatikan 2 kode berikut. Intinya sama, hanya cara penulisannya yang berbeda.
+
+```go
+var numbers = []int{2, 4, 3, 5, 4, 3, 3, 5, 5, 3}
+var avg = calculate(numbers...)
+
+// atau
+
+var avg = calculate(2, 4, 3, 5, 4, 3, 3, 5, 5, 3)
+```
+
+Pada deklarasi parameter fungsi variadic, tanda 3 titik (`...`) dituliskan sebelum tipe data parameter. Sedangkan pada pemanggilan fungsi dengan menyisipkan parameter array, tanda tersebut dituliskan di belakang variabelnya.
+
+## A.20.4. Fungsi Dengan Parameter Biasa & Variadic
+
+Parameter variadic bisa dikombinasikan dengan parameter biasa, dengan syarat parameter variadic-nya harus diposisikan di akhir. Contohnya bisa dilihat pada kode berikut.
+
+```go
+import "fmt"
+import "strings"
+
+func yourHobbies(name string, hobbies ...string) {
+ var hobbiesAsString = strings.Join(hobbies, ", ")
+
+ fmt.Printf("Hello, my name is: %s\n", name)
+ fmt.Printf("My hobbies are: %s\n", hobbiesAsString)
+}
+```
+
+Nilai parameter pertama fungsi `yourHobbies()` akan ditampung oleh `name`, sedangkan nilai parameter kedua dan seterusnya akan ditampung oleh `hobbies` sebagai slice.
+
+Cara pemanggilannya masih sama seperi pada fungsi biasa, contoh:
+
+```go
+func main() {
+ yourHobbies("wick", "sleeping", "eating")
+}
+```
+
+Jika parameter kedua dan seterusnya ingin diisi dengan data dari slice, maka gunakan tanda titik tiga kali seperti ini:
+
+```go
+func main() {
+ var hobbies = []string{"sleeping", "eating"}
+ yourHobbies("wick", hobbies...)
+}
+```
+
+Output program:
+
+![Kombinasi parameter biasa dan variadic](images/A_fungsi_variadic_2_parameter_combination.png)
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-fungsi.md b/en/content-en/A-fungsi.md
new file mode 100644
index 000000000..e0f867d04
--- /dev/null
+++ b/en/content-en/A-fungsi.md
@@ -0,0 +1,186 @@
+# A.18. Fungsi
+
+Dalam konteks pemrograman, fungsi adalah sekumpulan blok kode yang dibungkus dengan nama tertentu. Penerapan fungsi yang tepat akan menjadikan kode lebih modular dan juga *dry* (singkatan dari *don't repeat yourself*) yang artinya kita tidak perlu menuliskan banyak kode untuk kegunaan yang sama berulang kali. Cukup deklarasikan sekali saja blok kode sebagai suatu fungsi, lalu panggil sesuai kebutuhan.
+
+Pada chapter ini kita akan belajar tentang penerapannya di Go.
+
+## A.18.1. Penerapan Fungsi
+
+Mungkin pembaca sadar, bahwa sebenarnya kita sudah mengimplementasikan fungsi pada banyak praktek sebelumnya, yaitu fungsi `main()`. Fungsi `main()` sendiri merupakan fungsi utama pada program Go, yang akan dieksekusi ketika program dijalankan.
+
+Selain fungsi `main()`, kita juga bisa membuat fungsi lainnya. Dan caranya cukup mudah, yaitu dengan menuliskan keyword `func` kemudian diikuti nama fungsi, lalu kurung `()` (yang bisa diisi parameter), dan diakhiri dengan kurung kurawal untuk membungkus blok kode.
+
+Parameter merupakan variabel yang menempel di fungsi yang nilainya ditentukan saat pemanggilan fungsi tersebut. Parameter sifatnya opsional, suatu fungsi bisa tidak memiliki parameter, atau bisa saja memeliki satu atau banyak parameter (tergantung kebutuhan).
+
+> Data yang digunakan sebagai value parameter saat pemanggilan fungsi biasa disebut dengan argument parameter (atau argument).
+
+Agar lebih jelas, silakan lihat dan praktekan kode contoh implementasi fungsi berikut ini:
+
+```go
+package main
+
+import "fmt"
+import "strings"
+
+func main() {
+ var names = []string{"John", "Wick"}
+ printMessage("halo", names)
+}
+
+func printMessage(message string, arr []string) {
+ var nameString = strings.Join(arr, " ")
+ fmt.Println(message, nameString)
+}
+```
+
+Pada kode di atas, sebuah fungsi baru dibuat dengan nama `printMessage()` memiliki 2 buah parameter yaitu string `message` dan slice string `arr`.
+
+Fungsi tersebut dipanggil dalam `main()`, dalam pemanggilannya disisipkan dua buah argument parameter.
+
+1. Argument parameter pertama adalah string `"halo"` yang ditampung parameter `message`
+2. Argument parameter ke-2 adalah slice string `names` yang nilainya ditampung oleh parameter `arr`
+
+Di dalam `printMessage()`, nilai `arr` yang merupakan slice string digabungkan menjadi sebuah string dengan pembatas adalah karakter **spasi**. Penggabungan slice dapat dilakukan dengan memanfaatkan fungsi `strings.Join()` (berada di dalam package `strings`).
+
+![Contoh penggunaan fungsi](images/A_fungsi_1_function.png)
+
+## A.18.2. Fungsi Dengan Return Value / Nilai Balik
+
+Selain parameter, fungsi bisa memiliki attribute **return value** atau nilai balik. Fungsi yang memiliki return value, saat deklarasinya harus ditentukan terlebih dahulu tipe data dari nilai baliknya.
+
+> Fungsi yang tidak mengembalikan nilai apapun (contohnya seperti fungsi `main()` dan `printMessage()`) biasa disebut dengan **void function**
+
+Program berikut merupakan contoh penerapan fungsi yang memiliki return value.
+
+```go
+package main
+
+import (
+ "fmt"
+ "math/rand"
+ "time"
+)
+
+var randomizer = rand.New(rand.NewSource(time.Now().Unix()))
+
+func main() {
+ var randomValue int
+
+ randomValue = randomWithRange(2, 10)
+ fmt.Println("random number:", randomValue)
+
+ randomValue = randomWithRange(2, 10)
+ fmt.Println("random number:", randomValue)
+
+ randomValue = randomWithRange(2, 10)
+ fmt.Println("random number:", randomValue)
+}
+
+func randomWithRange(min, max int) int {
+ var value = randomizer.Int()%(max-min+1) + min
+ return value
+}
+```
+
+Fungsi `randomWithRange()` didesain untuk *generate* angka acak sesuai dengan range yang ditentukan lewat parameter, yang kemudian angka tersebut dijadikan nilai balik fungsi.
+
+![Fungsi dengan nilai balik](images/A_fungsi_2_function_return_type.png)
+
+Cara menentukan tipe data nilai balik fungsi adalah dengan menuliskan tipe data yang diinginkan setelah kurung parameter. Bisa dilihat pada kode di atas, bahwa `int` merupakan tipe data nilai balik fungsi `randomWithRange()`.
+
+```go
+func randomWithRange(min, max int) int
+```
+
+Sedangkan cara untuk mengembalikan nilai itu sendiri adalah dengan menggunakan keyword `return` diikuti data yang dikembalikan. Pada contoh di atas, `return value` artinya nilai variabel `value` dijadikan nilai kembalian fungsi.
+
+Eksekusi keyword `return` akan menjadikan proses dalam blok fungsi berhenti pada saat itu juga. Semua statement setelah keyword tersebut tidak akan dieksekusi.
+
+Dari kode di atas mungkin ada beberapa hal yang belum pernah kita lakukan pada pembahasan-pembahasan sebelumnya, kita akan bahas satu-persatu.
+
+## A.18.3. Penggunaan Fungsi `rand.New()`
+
+Fungsi `rand.New()` digunakan untuk membuat object randomizer, yang dari object tersebut kita bisa mendapatkan nilai random/acak hasil generator. Dalam penerapannya, fungsi `rand.New()` membutuhkan argument yaitu random source seed, yang bisa kita buat lewat statement `rand.NewSource(time.Now().Unix())`.
+
+```go
+var randomizer = rand.New(rand.NewSource(time.Now().Unix()))
+```
+
+> Dalam penggunaan fungsi `rand.NewSource()`, argument bisa diisi dengan nilai apapun, salah satunya adalah `time.Now().Unix()`.
+>
+> Lebih detailnya mengenai random dan apa peran seed dibahas pada chapter [A.39. Random](A-random.html).
+
+Fungsi `rand.New()` berada dalam package `math/rand`. Package tersebut harus di-import terlebih dahulu sebelum bisa menggunakan fungsi-fungsi yang ada didalamnya. Package `time` juga perlu di-import karena di contoh ini fungsi `(time.Now().Unix())` digunakan.
+
+## A.18.4. Import Banyak Package
+
+Penulisan keyword `import` untuk banyak package bisa dilakukan dengan dua cara, dengan menuliskannya di tiap package, atau cukup sekali saja, bebas silakan pilih sesuai selera.
+
+```go
+import "fmt"
+import "math/rand"
+import "time"
+
+// atau
+
+import (
+ "fmt"
+ "math/rand"
+ "time"
+)
+```
+
+## A.18.5. Deklarasi Parameter Bertipe Data Sama
+
+Khusus untuk fungsi yang tipe data parameternya sama, bisa ditulis dengan gaya yang unik. Tipe datanya dituliskan cukup sekali saja di akhir. Contohnya bisa dilihat pada kode berikut.
+
+```go
+func nameOfFunc(paramA type, paramB type, paramC type) returnType
+func nameOfFunc(paramA, paramB, paramC type) returnType
+
+func randomWithRange(min int, max int) int
+func randomWithRange(min, max int) int
+```
+
+## A.18.6. Penggunaan Keyword `return` Untuk Menghentikan Proses Dalam Fungsi
+
+Selain sebagai penanda nilai balik, keyword `return` juga bisa dimanfaatkan untuk menghentikan proses dalam blok fungsi di mana ia ditulis. Contohnya bisa dilihat pada kode berikut.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ divideNumber(10, 2)
+ divideNumber(4, 0)
+ divideNumber(8, -4)
+}
+
+func divideNumber(m, n int) {
+ if n == 0 {
+ fmt.Printf("invalid divider. %d cannot divided by %d\n", m, n)
+ return
+ }
+
+ var res = m / n
+ fmt.Printf("%d / %d = %d\n", m, n, res)
+}
+```
+
+Fungsi `divideNumber()` dirancang tidak memiliki nilai balik. Fungsi ini dibuat untuk membungkus proses pembagian 2 bilangan, lalu menampilkan hasilnya.
+
+Di dalamnya terdapat proses validasi nilai variabel pembagi, jika nilainya adalah 0, maka akan ditampilkan pesan bahwa pembagian tidak bisa dilakukan, lalu proses dihentikan pada saat itu juga (dengan memanfaatkan keyword `return`). Jika nilai pembagi valid, maka proses pembagian diteruskan.
+
+![Keyword return menjadikan proses dalam fungsi berhenti](images/A_fungsi_3_function_return_as_break.png)
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-go-command.md b/en/content-en/A-go-command.md
new file mode 100644
index 000000000..832d29a48
--- /dev/null
+++ b/en/content-en/A-go-command.md
@@ -0,0 +1,110 @@
+# A.6. Command
+
+Pengembangan aplikasi Go pastinya tak akan jauh dari hal-hal yang berbau CLI atau *Command Line Interface*. Di Go, proses inisialisasi project, kompilasi, testing, eksekusi program, semuanya dilakukan lewat command line.
+
+Go menyediakan command `go`, dan pada chapter ini kita akan mempelajari beberapa di antaranya.
+
+> Pada pembelajaran chapter ini, pembaca tidak harus menghafal dan mempraktekan semuanya, cukup ikuti saja pembelajaran agar mulai familiar. Perihal prakteknya sendiri akan dimulai pada chapter selanjutnya, yaitu [A.7. Program Pertama: Hello World](/A-hello-world.html).
+
+## A.6.1. Command `go mod init`
+
+*Command* `go mod init` digunakan untuk inisialisasi project pada Go yang menggunakan Go Modules. Untuk nama project bisa menggunakan apapun, tapi umumnya disamakan dengan nama direktori/folder.
+
+Nama project ini penting karena nantinya berpengaruh pada *import path sub packages* yang ada dalam project tersebut.
+
+```
+mkdir
+ go run
](images/A_go_command_1_go_run.png)
+
+*Command* `go run` hanya bisa digunakan pada file yang nama package-nya adalah `main`. Lebih jelasnya dibahas pada chapter selanjutnya, yaitu ([A.7. Program Pertama: Hello World](/A-hello-world.html)).
+
+Jika ada banyak file yang package-nya `main` dan file-file tersebut berada pada satu direktori level dengan file utama, maka eksekusinya adalah dengan menuliskan semua file sebagai argument *command* `go run`. Contohnya bisa dilihat pada kode berikut.
+
+```bash
+go run main.go library.go
+```
+
+## A.6.3. Command `go test`
+
+Go menyediakan package `testing`, berguna untuk keperluan pembuatan file test. Pada penerapannya, ada aturan yang wajib diikuti yaitu nama file test harus berakhiran `_test.go`.
+
+Berikut adalah contoh penggunaan *command* `go test` untuk testing file `main_test.go`.
+
+```bash
+go test main_test.go
+```
+
+![Unit testing menggunakan go test
](images/A_go_command_3_go_test.png)
+
+## A.6.4. Command `go build`
+
+*Command* ini digunakan untuk mengkompilasi file program.
+
+Sebenarnya ketika eksekusi program menggunakan `go run` didalamnya terjadi proses kompilasi juga. File hasil kompilasi kemudian disimpan pada folder temporary untuk selanjutnya langsung dieksekusi.
+
+Berbeda dengan `go build`, *command* ini menghasilkan file *executable* atau *binary* pada folder yang sedang aktif. Contoh praktiknya bisa dilihat di bawah ini.
+
+![Kompilasi file program menghasilkan file executable](images/A_go_command_4_go_build.png)
+
+Di contoh, project `project-pertama` di-build, hasilnya adalah file baru bernama `project-pertama.exe` berada di folder yang sama. File *executable* tersebut kemudian dieksekusi.
+
+*Default* nama file binary atau executable adalah sesuai dengan nama project. Untuk mengubah nama file executable, gunakan flag `-o`. Contoh:
+
+```
+go build -o go get
](images/A_go_command_6_go_get.png)
+
+Pada contoh di atas, bisa dilihat bahwa URL `github.com/segmentio/kafka-go` merupakan URL package kafka-go. Package yang sudah terunduh tersimpan dalam temporary folder yang ter-link dengan project folder di mana *command* `go get` dieksekusi, menjadikan project tersebut bisa meng-*import* package yang telah di-download.
+
+Untuk mengunduh package/dependency versi terbaru, gunakan flag `-u` pada command `go get`, contohnya:
+
+```
+go get -u github.com/segmentio/kafka-go
+```
+
+Command `go get` **harus dijalankan dalam folder project**. Jika dijalankan di-luar path project maka dependency yang ter-unduh akan ter-link dengan GOPATH, bukan dengan project.
+
+## A.6.6. Command `go mod download`
+
+*Command* `go mod download` digunakan untuk men-download dependency.
+
+## A.6.7. Command `go mod tidy`
+
+*Command* `go mod tidy` digunakan untuk memvalidasi dependency sekaligus men-download-nya jika memang belum ter-download.
+
+## A.6.8. Command `go mod vendor`
+
+Command ini digunakan untuk vendoring. Lebih detailnya akan dibahas di akhir serial chapter A, pada chapter [A.61. Go Vendoring](/A-go-vendoring.html).
+
+---
+
+
diff --git a/en/content-en/A-go-vendoring.md b/en/content-en/A-go-vendoring.md
new file mode 100644
index 000000000..c77495803
--- /dev/null
+++ b/en/content-en/A-go-vendoring.md
@@ -0,0 +1,71 @@
+# A.61. Go Vendoring
+
+Pada bagian ini kita akan belajar cara pemanfaatan vendoring untuk menyimpan copy dependency di lokal dalam folder project.
+
+## A.61.1. Penjelasan
+
+Vendoring di Go memberikan kita kapabilitas untuk mengunduh semua dependency atau *3rd party*, untuk disimpan di lokal dalam folder project, dalam subfolder bernama `vendor`.
+
+Dengan adanya folder tersebut, maka Go tidak akan *lookup* 3rd party ke cache folder ataupun ke GOPATH, melainkan langsung mengambil dari yang ada dalam folder `vendor`. Jadi kalau dependency sudah ada di dalam `vendor`, maka kita tidak perlu download lagi dari internet menggunakan command `go mod download` ataupun `go mod tidy`.
+
+Ok lanjut ke praktek ya.
+
+## A.61.2. Praktek Vendoring
+
+Kita akan coba praktekan untuk vendoring sebuah 3rd party bernama [gubrak](https://github.com/novalagung/gubrak/v2).
+
+Buat folder project baru dengan nama `belajar-vendor` dengan isi satu file `main.go`. Lalu go get library gubrak.
+
+```bash
+mkdir belajar-vendor
+cd belajar-vendor
+go mod init belajar-vendor
+go get -u github.com/novalagung/gubrak/v2
+```
+
+Isi `main.go` dengan blok kode berikut, untuk menampilkan angka random dengan range 10-20.
+
+```go
+package main
+
+import (
+ "fmt"
+ gubrak "github.com/novalagung/gubrak/v2"
+)
+
+func main() {
+ fmt.Println(gubrak.RandomInt(10, 20))
+}
+```
+
+Setelah itu jalankan command `go mod vendor` untuk vendoring *3rd party library* yang dipergunakan, dalam contoh ini adalah gubrak.
+
+![Vendoring](images/A_go_vendoring_1_vendor.png)
+
+Bisa dilihat, sekarang library gubrak *source code*-nya disimpan dalam folder `vendor`. Nah ini juga akan berlaku untuk semua *library* lainnya yg digunakan jika ada.
+
+## A.61.3 Build dan Run Project yang Menerapkan Vendoring
+
+Cara agar Go lookup ke folder `vendor` saat build adalah dengan menambahkan flag `-mod=vendor` sewaktu build atau run project.
+
+```
+go run -mod=vendor main.go
+go build -mod=vendor -o executable
+```
+
+## A.61.3. Manfaat Vendoring
+
+Manfaat vendoring adalah pada sisi kompatibilitas & kestabilan 3rd party, selain itu kita tidak perlu repot mendownload dependency karena semuanya sudah ada di lokal.
+
+Konsekuensi penerapan vendoring adalah size project menjadi cukup besar. Untuk penggunaan vendor apakah wajib? menurut saya tidak. Sesuaikan kebutuhan saja.
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-golang-generics.md b/en/content-en/A-golang-generics.md
new file mode 100644
index 000000000..56d32cc4c
--- /dev/null
+++ b/en/content-en/A-golang-generics.md
@@ -0,0 +1,285 @@
+# A.65. Go Generics
+
+Pada chapter ini kita akan belajar tentang penerapan Generics di Go.
+
+## A.65.1. Konsep Generic Programming
+
+Generic Programming adalah salah satu metode dalam penulisan kode program, di mana tipe data dalam kode didefinisikan menggunakan suatu tipe yang tipe pastinya ditulis belakangan saat kode tersebut di-call atau dieksekusi. Konsep generic ini cukup umum diterapkan terutama pada bahasa pemrograman yang mengadopsi static typing.
+
+Di Go, kita punya tipe `any` atau `interface{}` yang biasa difungsikan sebagai penampung data yang tidak pasti tipe datanya. Generic berbeda dibanding `any`. Tipe `any` dalam prakteknya membungkus data asli atau *underlying value*-nya, dengan pengaksesan data asli tersebut dilakukan via metode *type assertion*, contohnya `data.(int)`.
+
+Berbeda dibanding `any`, pada Generic kita perlu mendefinisikan cakupan tipe data yang kompatibel untuk digunakan saat pemanggilan kode.
+
+Ok, mari kita lanjut ke praktek saja agar tidak makin bingung.
+
+## A.65.2. Penerapan Generic pada Fungsi
+
+Mari kita mulai pembelajaran dengan kode sederhana berikut:
+
+```go
+package main
+
+import "fmt"
+
+func Sum(numbers []int) int {
+ var total int
+ for _, e := range numbers {
+ total += e
+ }
+ return total
+}
+
+func main() {
+ total1 := Sum([]int{1, 2, 3, 4, 5})
+ fmt.Println("total:", total1)
+}
+```
+
+Pada kode di atas, didefinisikan sebuah fungsi `Sum()` yang tugasnya menghitung total atau *summary* dari data slice numerik yang disisipkan di parameter. Dalam `main()`, kita panggil fungsi tersebut untuk menghitung total dari sejumlah data dengan tipe `[]int`. Saya rasa sampai sini cukup jelas.
+
+Fungsi `Sum()` memiliki satu limitasinya, yaitu hanya bisa digunakan pada data yang tipenya `[]int`, tidak bisa untuk tipe slice numerik lain. Bagaimana jika menggunakan tipe `interface{}`? apakah bisa? bisa saja sebenarnya, tapi pastinya lebih report karena sulit untuk menerapkan *type assertion* kalau tidak tau pasti cakupan tipe yang di-support oleh parameter `numbers` itu apa saja.
+
+> Alternatifnya, penggunaan `interface{}` bisa dibarengi dengan penerapan [reflection API](/A-reflect.html).
+
+Nah, agar tidak repot, di sini kita akan terapkan Generic. Kode akan dimodifikasi atas agar bisa menampung tipe data slice numerik lainnya diluar tipe `[]int`.
+
+Ok, sekarang ubah kode fungsi `Sum` menjadi seperti di bawah ini:
+
+```go
+func Sum[V int](numbers []V) V {
+ var total V
+ for _, e := range numbers {
+ total += e
+ }
+ return total
+}
+```
+
+Notasi penulisan di atas mungkin akan cukup asing teruntuk pembaca yang belum pernah menggunakan Generic pada bahasa selain Go. Tidak apa, di sini kita belajar dari awal :-)
+
+Penulisan notasi fungsi dengan Generic kurang lebih sebagai berikut:
+
+```go
+func FuncName[dataType
+ GOPATH
di sistem operasi non-Windows](images/A_gopath_workspace_1_path.png)
+
+Setelah `GOPATH` berhasil dikenali, perlu disiapkan 3 buah sub folder di dalamnya, dengan kriteria sebagai berikut:
+
+ - Folder `src`, adalah path di mana project Go disimpan
+ - Folder `pkg`, berisi file hasil kompilasi
+ - Folder `bin`, berisi file executable hasil build
+
+![Struktur folder dalam worskpace](images/A_gopath_workspace_2_workspace.png)
+
+Struktur di atas merupakan struktur standar workspace Go. Jadi pastikan penamaan dan hirarki folder adalah sama.
+
+---
+
+
diff --git a/en/content-en/A-goroutine.md b/en/content-en/A-goroutine.md
new file mode 100644
index 000000000..c6406a0e6
--- /dev/null
+++ b/en/content-en/A-goroutine.md
@@ -0,0 +1,100 @@
+# A.30. Goroutine
+
+Goroutine secara konsep mirip seperti *thread*, meskipun sebenarnya berbeda. Sebuah *native thread* bisa berisikan sangat banyak goroutine. Mungkin lebih pas kalau goroutine disebut sebagai **mini thread**. Goroutine sangat ringan, hanya dibutuhkan sekitar **2kB** memori saja untuk satu buah goroutine. Eksekusi goroutine bersifat *asynchronous*, menjadikannya tidak saling tunggu dengan goroutine lain.
+
+> Karena goroutine sangat ringan, maka eksekusi banyak goroutine bukan masalah. Akan tetapi jika jumlah goroutine sangat banyak sekali (contoh 1 juta goroutine dijalankan pada komputer dengan RAM terbatas), memang proses akan jauh lebih cepat selesai, tapi memory/RAM pasti bengkak.
+>
+> Selain itu, dalam pengaplikasiannya jangan hanya terpaku pada size goroutine yang kecil tersebut, tapi pertimbangkan juga kode/proses/logic yang dibuat di dalam goroutine itu sekompleks apa, karena hal tersebut sangat berpengaruh dengan konsumsi resource hardware.
+
+Goroutine merupakan salah satu bagian paling penting dalam *concurrent programming* di Go. Salah satu yang membuat goroutine sangat istimewa adalah eksekusi-nya dijalankan di multi core processor. Kita bisa tentukan berapa banyak core yang aktif, makin banyak akan makin cepat.
+
+Mulai chapter **A.29** ini hingga **A.34**, lalu dilanjut **A.56** dan **A.57**, kita akan membahas tentang fitur-fitur yang disediakan Go untuk kebutuhan *concurrent programming*.
+
+> Concurrency atau konkurensi berbeda dengan paralel. Paralel adalah eksekusi banyak proses secara bersamaan. Sedangkan konkurensi adalah komposisi dari sebuah proses. Konkurensi merupakan struktur, sedangkan paralel adalah bagaimana eksekusinya berlangsung.
+
+## A.30.1. Penerapan Goroutine
+
+Untuk menerapkan goroutine, proses yang akan dieksekusi sebagai goroutine harus dibungkus ke dalam sebuah fungsi, ini hukumnya wajib. Kemudian nantinya saat pemanggilan fungsi, tambahkan keyword `go` di depannya, dengan ini maka goroutine baru dibuat dengan tugas adalah menjalankan proses yang ada dalam fungsi tersebut.
+
+Berikut merupakan contoh implementasi sederhana tentang goroutine. Program di bawah ini menampilkan 10 baris teks, 5 dieksekusi dengan cara biasa, dan 5 lainnya dieksekusi sebagai goroutine baru.
+
+```go
+package main
+
+import "fmt"
+import "runtime"
+
+func print(till int, message string) {
+ for i := 0; i < till; i++ {
+ fmt.Println((i + 1), message)
+ }
+}
+
+func main() {
+ runtime.GOMAXPROCS(2)
+
+ go print(5, "halo")
+ print(5, "apa kabar")
+
+ var input string
+ fmt.Scanln(&input)
+}
+```
+
+Pada kode di atas, Fungsi `runtime.GOMAXPROCS(n)` digunakan untuk menentukan jumlah core yang diaktifkan untuk eksekusi program.
+
+Pembuatan goroutine baru ditandai dengan keyword `go`. Contohnya pada statement `go print(5, "halo")`, di situ fungsi `print()` dieksekusi sebagai goroutine baru.
+
+Fungsi `fmt.Scanln()` mengakibatkan proses jalannya aplikasi berhenti di baris itu (**blocking**) hingga user menekan tombol enter. Hal ini perlu dilakukan karena ada kemungkinan waktu selesainya eksekusi goroutine `print()` lebih lama dibanding waktu selesainya goroutine utama `main()`, mengingat bahwa keduanya sama-sama asnychronous. Jika itu terjadi, goroutine yang belum selesai secara paksa dihentikan prosesnya karena goroutine utama sudah selesai dijalankan.
+
+Output program:
+
+![Implementasi goroutine](images/A_goroutine_1_goroutine.png)
+
+Bisa dilihat di output, tulisan `"halo"` dan `"apa kabar"` bermunculan selang-seling. Ini disebabkan karena statement `print(5, "halo")` dijalankan sebagai goroutine, menjadikannya tidak saling tunggu dengan `print(5, "apa kabar")`.
+
+Pada gambar di atas, program dieksekusi 2 kali. Hasil eksekusi pertama berbeda dengan kedua, penyebabnya adalah karena kita menggunakan 2 prosesor. Goroutine mana yang dieksekusi terlebih dahulu tergantung kedua prosesor tersebut.
+
+## A.30.2. Penjelasan tambahan
+
+Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan:
+
+#### ◉ Penggunaan Fungsi `runtime.GOMAXPROCS()`
+
+Fungsi ini digunakan untuk menentukan jumlah core atau processor yang digunakan dalam eksekusi program.
+
+Jumlah yang diinputkan secara otomatis akan disesuaikan dengan jumlah asli *logical processor* yang ada. Jika jumlahnya lebih, maka dianggap menggunakan sejumlah prosesor yang ada.
+
+#### ◉ Penggunaan Fungsi `fmt.Scanln()`
+
+Fungsi ini akan meng-capture semua karakter sebelum user menekan tombol enter, lalu menyimpannya pada variabel.
+
+```go
+func Scanln(a ...interface{}) (n int, err error)
+```
+
+Kode di atas merupakan skema fungsi `fmt.Scanln()`. Fungsi tersebut bisa menampung parameter bertipe `interface{}` berjumlah tak terbatas. Tiap parameter akan menampung karakter-karakter inputan user yang sudah dipisah dengan tanda spasi. Agar lebih jelas, silakan perhatikan contoh berikut.
+
+```go
+var s1, s2, s3 string
+fmt.Scanln(&s1, &s2, &s3)
+
+// user inputs: "trafalgar d law"
+
+fmt.Println(s1) // trafalgar
+fmt.Println(s2) // d
+fmt.Println(s3) // law
+```
+
+Bisa dilihat pada kode di atas, untuk menampung inputan text `trafalgar d law`, dibutuhkan 3 buah variabel. Juga perlu diperhatikan bahwa yang disisipkan sebagai parameter pada pemanggilan fungsi `fmt.Scanln()` adalah referensi variabel, bukan nilai aslinya.
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-hash-sha1.md b/en/content-en/A-hash-sha1.md
new file mode 100644
index 000000000..bbea5df35
--- /dev/null
+++ b/en/content-en/A-hash-sha1.md
@@ -0,0 +1,106 @@
+# A.47. Hash SHA1
+
+Hash adalah algoritma enkripsi satu arah untuk mengubah text menjadi deretan karakter acak. Jumlah karakter hasil hash selalu sama. Hash termasuk *one-way encryption*, hasil dari hash tidak bisa dikembalikan ke text asli.
+
+SHA1 atau **Secure Hash Algorithm 1** merupakan salah satu algoritma hashing yang sering digunakan untuk enkripsi data. Hasil dari sha1 adalah data dengan lebar **20 byte** atau **160 bit**, biasa ditampilkan dalam bentuk bilangan heksadesimal 40 digit.
+
+Pada chapter ini kita akan belajar tentang pemanfaatan sha1 dan teknik salting dalam hash.
+
+## A.47.1. Penerapan Hash SHA1
+
+Go menyediakan package `crypto/sha1`, berisikan library untuk keperluan *hashing*. Cara penerapannya cukup mudah, contohnya bisa dilihat pada kode berikut.
+
+```go
+package main
+
+import "crypto/sha1"
+import "fmt"
+
+func main() {
+ var text = "this is secret"
+ var sha = sha1.New()
+ sha.Write([]byte(text))
+ var encrypted = sha.Sum(nil)
+ var encryptedString = fmt.Sprintf("%x", encrypted)
+
+ fmt.Println(encryptedString)
+ // f4ebfd7a42d9a43a536e2bed9ee4974abf8f8dc8
+}
+```
+
+Variabel hasil dari `sha1.New()` adalah objek bertipe `hash.Hash`, memiliki dua buah method `Write()` dan `Sum()`.
+
+ - Method `Write()` digunakan untuk menge-set data yang akan di-hash. Data harus dalam bentuk `[]byte`.
+ - Method `Sum()` digunakan untuk eksekusi proses hash, menghasilkan data yang sudah di-hash dalam bentuk `[]byte`. Method ini membutuhkan sebuah parameter, isi dengan nil.
+
+Untuk mengambil bentuk heksadesimal string dari data yang sudah di-hash, bisa memanfaatkan fungsi `fmt.Sprintf` dengan layout format `%x`.
+
+![Hashing menggunakan SHA1](images/A_hash_1_hash_sha1.png)
+
+## A.47.2. Metode Salting Pada Hash SHA1
+
+Salt dalam konteks kriptografi adalah data acak yang digabungkan pada data asli sebelum proses hash dilakukan.
+
+Hash merupakan enkripsi satu arah dengan lebar data yang sudah pasti, sangat mungkin sekali kalau hasil hash untuk beberapa data adalah sama. Di sinilah kegunaan **salt**, teknik ini berguna untuk mencegah serangan menggunakan metode pencocokan data-data yang hasil hash-nya adalah sama *(dictionary attack)*.
+
+Langsung saja kita praktekkan. Pertama import package yang dibutuhkan. Lalu buat fungsi untuk hash menggunakan salt dari waktu sekarang.
+
+```go
+package main
+
+import "crypto/sha1"
+import "fmt"
+import "time"
+
+func doHashUsingSalt(text string) (string, string) {
+ var salt = fmt.Sprintf("%d", time.Now().UnixNano())
+ var saltedText = fmt.Sprintf("text: '%s', salt: %s", text, salt)
+ var sha = sha1.New()
+ sha.Write([]byte(saltedText))
+ var encrypted = sha.Sum(nil)
+
+ return fmt.Sprintf("%x", encrypted), salt
+}
+```
+
+Salt yang digunakan adalah hasil dari ekspresi `time.Now().UnixNano()`. Hasilnya akan selalu unik setiap detiknya, karena scope terendah waktu pada fungsi tersebut adalah *nano second* atau nano detik.
+
+Selanjutnya test fungsi yang telah dibuat beberapa kali.
+
+```go
+func main() {
+ var text = "this is secret"
+ fmt.Printf("original : %s\n\n", text)
+
+ var hashed1, salt1 = doHashUsingSalt(text)
+ fmt.Printf("hashed 1 : %s\n\n", hashed1)
+ // 929fd8b1e58afca1ebbe30beac3b84e63882ee1a
+
+ var hashed2, salt2 = doHashUsingSalt(text)
+ fmt.Printf("hashed 2 : %s\n\n", hashed2)
+ // cda603d95286f0aece4b3e1749abe7128a4eed78
+
+ var hashed3, salt3 = doHashUsingSalt(text)
+ fmt.Printf("hashed 3 : %s\n\n", hashed3)
+ // 9e2b514bca911cb76f7630da50a99d4f4bb200b4
+
+ _, _, _ = salt1, salt2, salt3
+}
+```
+
+Hasil ekripsi fungsi `doHashUsingSalt()` akan selalu beda, karena salt yang digunakan adalah waktu.
+
+![Hashing dengan salt](images/A_hash_2_hash_salt_sha1.png)
+
+Metode ini sering dipakai untuk enkripsi password user. Salt dan data hasil hash harus disimpan pada database, karena digunakan dalam pencocokan password setiap user melakukan login.
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-hello-world.md b/en/content-en/A-hello-world.md
new file mode 100644
index 000000000..f1e954de0
--- /dev/null
+++ b/en/content-en/A-hello-world.md
@@ -0,0 +1,134 @@
+# A.7. Program Pertama: Hello World
+
+Semua persiapan sudah selesai, saatnya masuk pada sesi programming. Program pertama yang akan kita buat adalah cukup terkenal di kalangan programmer, yaitu program untuk memunculkan text **Hello world**.
+
+Proses pembelajaran di chapter ini akan disampaikan secara runtun dan komprehensif, *step-by-step* mulai dari awal. Mulai dari pembuatan project, pembuatan file program, sesi penulisan kode (coding), hingga eksekusi program.
+
+## A.7.1. Inisialisasi Project
+
+Buat direktori bernama `hello-world` bebas ditempatkan di mana. Lalu via CLI, masuk ke direktori tersebut dan jalankan *command* untuk inisialisasi project.
+
+```
+mkdir hello-world
+cd hello-world
+go mod init hello-world
+```
+
+![Inisialisasi project](images/A_hello_world_1_init_project.png)
+
+## A.7.2. Load Project Folder ke Editor
+
+Buka editor, di sini penulis menggunakan VSCode. Cari menu untuk menambahkan project, lalu pilih project folder `hello-world`. Untuk beberapa jenis editor, cara load project bisa cukup dengan klik-drag folder tersebut ke editor.
+
+![Load project folder ke editor](images/A_hello_world_2_load_project_to_editor.png)
+
+## A.7.3. Menyiapkan File Program
+
+File program di sini maksudnya adalah file yang isinya *source code* Go. Ciri khas file program adalah memiliki ekstensi `.go`.
+
+Di dalam project yang telah dibuat, siapkan sebuah file dengan nama bebas, yang jelas harus ber-ekstensi `.go`. Pada contoh ini saya menggunakan nama file `main.go`.
+
+Pembuatan file program bisa dilakukan lewat CLI atau browser, atau juga lewat editor. Pastikan file dibuat dalam project folder ya.
+
+![File program](images/A_hello_world_3_new_file_on_editor.png)
+
+## A.7.4. Program Pertama: Hello Word
+
+Setelah project folder dan file program sudah siap, saatnya untuk *coding*.
+
+Silakan salin kode berikut ke file program yang telah dibuat. Sebisa mungkin jangan copy paste. Biasakan untuk menulis dari awal, agar cepat terbiasa dan familiar dengan Go.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ fmt.Println("Hello world")
+}
+```
+
+Setelah kode disalin, buka terminal (atau CMD bagi pengguna Windows), lalu masuk ke direktori proyek, kemudian jalankan program menggunakan perintah `go run`.
+
+```bash
+cd hello-world
+go run main.go
+```
+
+Hasilnya, muncul tulisan **hello world** di layar console.
+
+![Menjalankan program](images/A_hello_world_4_execute_hello_world.png)
+
+Selamat! Anda telah berhasil membuat program Go!
+
+---
+
+Berikut merupakan pembahasan untuk tiap baris kode yang sudah ditulis di atas.
+
+## A.7.5. Penggunaan Keyword `package`
+
+Setiap file program harus memiliki **package**. Setiap project harus ada minimal satu file dengan nama *package* `main`. File yang ber-*package* `main`, akan dieksekusi pertama kali ketika program di jalankan.
+
+Cara menentukan *package* dengan menggunakan keyword `package`, berikut adalah contoh penulisannya.
+
+```go
+package
+ interface{}
](images/A_interface_kosong_1_empty_interface.png)
+
+Agar tidak bingung, coba perhatikan kode berikut.
+
+```go
+var data map[string]interface{}
+
+data = map[string]interface{}{
+ "name": "ethan hunt",
+ "grade": 2,
+ "breakfast": []string{"apple", "manggo", "banana"},
+}
+```
+
+Pada kode di atas, disiapkan variabel `data` dengan tipe `map[string]interface{}`, yaitu sebuah koleksi dengan key bertipe `string` dan nilai bertipe interface kosong `interface{}`.
+
+Kemudian variabel tersebut di-inisialisasi, ditambahkan lagi kurung kurawal setelah keyword deklarasi untuk kebutuhan pengisian data, `map[string]interface{}{ /* data */ }`.
+
+Dari situ terlihat bahwa `interface{}` bukanlah sebuah objek, melainkan tipe data.
+
+## A.28.2. Type Alias `Any`
+
+Tipe `any` merupakan alias dari `interface{}`, keduanya adalah sama.
+
+```go
+var data map[string]any
+
+data = map[string]any{
+ "name": "ethan hunt",
+ "grade": 2,
+ "breakfast": []string{"apple", "manggo", "banana"},
+}
+```
+
+## A.28.3. Casting Variabel Any / Interface Kosong
+
+Variabel bertipe `interface{}` bisa ditampilkan ke layar sebagai `string` dengan memanfaatkan fungsi print, seperti `fmt.Println()`. Tapi perlu diketahui bahwa nilai yang dimunculkan tersebut bukanlah nilai asli, melainkan bentuk text dari nilai aslinya.
+
+Hal ini penting diketahui, karena untuk melakukan operasi yang membutuhkan nilai asli pada variabel yang bertipe `interface{}`, diperlukan casting ke tipe aslinya. Contoh seperti pada kode berikut.
+
+```go
+package main
+
+import "fmt"
+import "strings"
+
+func main() {
+ var secret interface{}
+
+ secret = 2
+ var number = secret.(int) * 10
+ fmt.Println(secret, "multiplied by 10 is :", number)
+
+ secret = []string{"apple", "manggo", "banana"}
+ var gruits = strings.Join(secret.([]string), ", ")
+ fmt.Println(gruits, "is my favorite fruits")
+}
+```
+
+Pertama, variabel `secret` menampung nilai bertipe numerik. Ada kebutuhan untuk mengalikan nilai yang ditampung variabel tersebut dengan angka `10`. Maka perlu dilakukan casting ke tipe aslinya, yaitu `int`, setelahnya barulah nilai bisa dioperasikan, yaitu `secret.(int) * 10`.
+
+Pada contoh kedua, `secret` berisikan array string. Kita memerlukan string tersebut untuk digabungkan dengan pemisah tanda koma. Maka perlu di-casting ke `[]string` terlebih dahulu sebelum bisa digunakan di `strings.Join()`, contohnya pada `strings.Join(secret.([]string), ", ")`.
+
+![Casting pada variabel bertipe interface{}
](images/A_interface_kosong_2_interface_casting.png)
+
+Teknik casting pada `any` disebut dengan **type assertions**.
+
+## A.28.4. Casting Variabel Interface Kosong Ke Objek Pointer
+
+Variabel `interface{}` bisa menyimpan data apa saja, termasuk data objek, pointer, ataupun gabungan keduanya. Di bawah ini merupakan contoh penerapan interface untuk menampung data objek pointer.
+
+```go
+type person struct {
+ name string
+ age int
+}
+
+var secret interface{} = &person{name: "wick", age: 27}
+var name = secret.(*person).name
+fmt.Println(name)
+```
+
+Variabel `secret` dideklarasikan bertipe `interface{}` menampung referensi objek cetakan struct `person`. Cara casting dari `interface{}` ke struct pointer adalah dengan menuliskan nama struct-nya dan ditambahkan tanda asterisk (`*`) di awal, contohnya seperti `secret.(*person)`. Setelah itu barulah nilai asli bisa diakses.
+
+![Casting interface{}
ke variabel objek](images/A_interface_kosong_3_interface_pointer.png)
+
+## A.28.5. Kombinasi Slice, `map`, dan `interface{}`
+
+Tipe `[]map[string]interface{}` adalah salah satu tipe yang paling sering digunakan untuk menyimpan sekumpulan data berbasis *key-value*. Tipe tersebut merupakan alternatif dari slice struct.
+
+Pada contoh berikut, variabel `person` dideklarasikan berisi data slice `map` berisikan 2 item dengan key adalah `name` dan `age`.
+
+```go
+var person = []map[string]interface{}{
+ {"name": "Wick", "age": 23},
+ {"name": "Ethan", "age": 23},
+ {"name": "Bourne", "age": 22},
+}
+
+for _, each := range person {
+ fmt.Println(each["name"], "age is", each["age"])
+}
+```
+
+Dengan memanfaatkan slice dan `interface{}`, kita bisa membuat data array yang isinya adalah bisa apa saja. Silakan perhatikan contoh berikut.
+
+```go
+var fruits = []interface{}{
+ map[string]interface{}{"name": "strawberry", "total": 10},
+ []string{"manggo", "pineapple", "papaya"},
+ "orange",
+}
+
+for _, each := range fruits {
+ fmt.Println(each)
+}
+```
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-interface.md b/en/content-en/A-interface.md
new file mode 100644
index 000000000..faf0ecf26
--- /dev/null
+++ b/en/content-en/A-interface.md
@@ -0,0 +1,188 @@
+# A.27. Interface
+
+Interface adalah definisi suatu kumpulan method yang tidak memiliki isi, jadi hanya definisi header/schema-nya saja. Kumpulan method tersebut ditulis dalam satu block interface dengan nama tertentu.
+
+Interface merupakan tipe data. Objek bertipe interface memiliki zero value yaitu `nil`. Variabel bertipe interface digunakan untuk menampung nilai objek konkret yang memiliki definisi method minimal sama dengan yang ada di interface.
+
+## A.27.1. Penerapan Interface
+
+Untuk menerapkan interface, pertama siapkan deklarasi tipe baru menggunakan keyword `type` dan tipe data `interface` lalu siapkan juga isinya (definisi method-nya).
+
+```go
+package main
+
+import "fmt"
+import "math"
+
+type hitung interface {
+ luas() float64
+ keliling() float64
+}
+```
+
+Di atas, interface `hitung` dideklarasikan memiliki 2 buah method yaitu `luas()` dan `keliling()`. Interface ini nantinya digunakan sebagai tipe data pada variabel untuk menampung objek bangun datar hasil dari struct yang akan dibuat.
+
+Dengan adanya interface `hitung` ini, maka perhitungan luas dan keliling bangun datar bisa dilakukan tanpa perlu tahu jenis bangun datarnya sendiri itu apa.
+
+Selanjutnya, siapkan struct bangun datar `lingkaran`, struct ini memiliki definisi method yang sebagian adalah ada di interface `hitung`.
+
+```go
+type lingkaran struct {
+ diameter float64
+}
+
+func (l lingkaran) jariJari() float64 {
+ return l.diameter / 2
+}
+
+func (l lingkaran) luas() float64 {
+ return math.Pi * math.Pow(l.jariJari(), 2)
+}
+
+func (l lingkaran) keliling() float64 {
+ return math.Pi * l.diameter
+}
+```
+
+Struct `lingkaran` memiliki tiga buah method yaitu `jariJari()`, `luas()`, dan `keliling()`.
+
+Berikutnya, siapkan struct bangun datar `persegi` berikut:
+
+```go
+type persegi struct {
+ sisi float64
+}
+
+func (p persegi) luas() float64 {
+ return math.Pow(p.sisi, 2)
+}
+
+func (p persegi) keliling() float64 {
+ return p.sisi * 4
+}
+```
+
+Perbedaan struct `persegi` dengan `lingkaran` terletak pada method `jariJari()`. Struct `persegi` tidak memiliki method tersebut. Tetapi meski demikian, variabel objek hasil cetakan 2 struct ini akan tetap bisa ditampung oleh variabel cetakan interface `hitung`, karena dua method yang ter-definisi di interface tersebut juga ada pada struct `persegi` dan `lingkaran`, yaitu method `luas()` dan `keliling()`.
+
+Sekarang buat implementasi perhitungan di fungsi `main()`.
+
+```go
+func main() {
+ var bangunDatar hitung
+
+ bangunDatar = persegi{10.0}
+ fmt.Println("===== persegi")
+ fmt.Println("luas :", bangunDatar.luas())
+ fmt.Println("keliling :", bangunDatar.keliling())
+
+ bangunDatar = lingkaran{14.0}
+ fmt.Println("===== lingkaran")
+ fmt.Println("luas :", bangunDatar.luas())
+ fmt.Println("keliling :", bangunDatar.keliling())
+ fmt.Println("jari-jari :", bangunDatar.(lingkaran).jariJari())
+}
+```
+
+Perhatikan kode di atas. Variabel objek `bangunDatar` bertipe interface `hitung`. Variabel tersebut digunakan untuk menampung objek konkrit buatan struct `lingkaran` dan `persegi`.
+
+Dari variabel tersebut, method `luas()` dan `keliling()` diakses. Secara otomatis Golang akan mengarahkan pemanggilan method pada interface ke method asli milik struct yang bersangkutan.
+
+![Pemanfaatan interface](images/A_interface_1_interface.png)
+
+Method `jariJari()` pada struct `lingkaran` tidak akan bisa diakses karena tidak terdefinisi dalam interface `hitung`. Pengaksesannya secara paksa menyebabkan error.
+
+Untuk mengakses method yang tidak ter-definisi di interface, variabel-nya harus di-casting terlebih dahulu ke tipe asli variabel konkritnya (pada kasus ini tipenya `lingkaran`), setelahnya method akan bisa diakses.
+
+Cara casting objek interface sedikit unik, yaitu dengan menuliskan nama tipe tujuan dalam kurung, ditempatkan setelah nama interface dengan menggunakan notasi titik (seperti cara mengakses property, hanya saja ada tanda kurung nya). Contohnya bisa dilihat di kode berikut. Statement `bangunDatar.(lingkaran)` adalah contoh casting pada objek interface.
+
+```go
+var bangunDatar hitung = lingkaran{14.0}
+var bangunLingkaran lingkaran = bangunDatar.(lingkaran)
+
+bangunLingkaran.jariJari()
+```
+
+> Metode casting pada tipe data interface biasa disebut dengan **type assertion**
+
+Perlu diketahui juga, jika ada interface yang menampung objek konkrit yang mana struct-nya tidak memiliki salah satu method yang terdefinisi di interface, maka error akan muncul. Intinya kembali ke aturan awal, variabel interface hanya bisa menampung objek yang minimal memiliki semua method yang terdefinisi di interface tersebut.
+
+## A.27.2. Embedded Interface
+
+Interface bisa di-embed ke interface lain, sama seperti struct. Cara penerapannya juga sama, cukup dengan menuliskan nama interface yang ingin di-embed ke dalam body interface tujuan.
+
+Pada contoh berikut, disiapkan interface bernama `hitung2d` dan `hitung3d`. Kedua interface tersebut kemudian di-embed ke interface baru bernama `hitung`.
+
+```go
+package main
+
+import "fmt"
+import "math"
+
+type hitung2d interface {
+ luas() float64
+ keliling() float64
+}
+
+type hitung3d interface {
+ volume() float64
+}
+
+type hitung interface {
+ hitung2d
+ hitung3d
+}
+```
+
+Interface `hitung2d` berisikan method untuk kalkulasi luas dan keliling, sedang `hitung3d` berisikan method untuk mencari volume bidang. Kedua interface tersebut embed ke interface `hitung`, menjadikannya memiliki kemampuan untuk mengakses method `luas()`, `keliling()`, dan `volume()`.
+
+Next, siapkan struct baru bernama `kubus` yang memiliki method `luas()`, `keliling()`, dan `volume()`.
+
+```go
+type kubus struct {
+ sisi float64
+}
+
+func (k *kubus) volume() float64 {
+ return math.Pow(k.sisi, 3)
+}
+
+func (k *kubus) luas() float64 {
+ return math.Pow(k.sisi, 2) * 6
+}
+
+func (k *kubus) keliling() float64 {
+ return k.sisi * 12
+}
+```
+
+Objek hasil cetakan struct `kubus` di atas, nantinya akan ditampung oleh objek cetakan interface `hitung` yang isinya merupakan gabungan interface `hitung2d` dan `hitung3d`.
+
+Terakhir, buat implementasi-nya di fungsi `main()`.
+
+```go
+func main() {
+ var bangunRuang hitung = &kubus{4}
+
+ fmt.Println("===== kubus")
+ fmt.Println("luas :", bangunRuang.luas())
+ fmt.Println("keliling :", bangunRuang.keliling())
+ fmt.Println("volume :", bangunRuang.volume())
+}
+```
+
+Bisa dilihat di kode di atas, lewat interface `hitung`, method `luas()`, `keliling()`, dan `volume()` bisa di akses.
+
+Pada chapter [A.23. Pointer](/A-pointer.html) dijelaskan bahwa method pointer bisa diakses lewat variabel objek biasa dan variabel objek pointer. Variabel objek yang dicetak menggunakan struct yang memiliki method pointer, jika ditampung ke dalam variabel interface, harus diambil referensi-nya terlebih dahulu. Contohnya bisa dilihat pada kode di atas `var bangunRuang hitung = &kubus{4}`.
+
+![Embedded interface](images/A_interface_2_embedded_interface.png)
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-json.md b/en/content-en/A-json.md
new file mode 100644
index 000000000..67e505925
--- /dev/null
+++ b/en/content-en/A-json.md
@@ -0,0 +1,141 @@
+# A.53. JSON Data
+
+**JSON** atau *Javascript Object Notation* adalah notasi standar penulisan data yang umum digunakan untuk komunikasi antar aplikasi/service. JSON sendiri sebenarnya merupakan subset dari *javascript*.
+
+Go menyediakan package `encoding/json` yang berisikan banyak fungsi untuk kebutuhan operasi json.
+
+Pada chapter ini, kita akan belajar cara untuk konverstri string yang ditulis dalam format json menjadi objek Go, dan sebaliknya.
+
+## A.53.1. Decode JSON Ke Variabel Objek Struct
+
+Di Go, data json dituliskan sebagai `string`. Dengan menggunakan `json.Unmarshal`, json string bisa dikonversi menjadi bentuk objek, entah itu dalam bentuk `map[string]interface{}` ataupun objek struct.
+
+Program berikut ini adalah contoh cara decoding json ke bentuk objek. Pertama import package yang dibutuhkan, lalu siapkan struct `User`.
+
+```go
+package main
+
+import "encoding/json"
+import "fmt"
+
+type User struct {
+ FullName string `json:"Name"`
+ Age int
+}
+```
+
+Struct `User` ini nantinya digunakan untuk membuat variabel baru penampung hasil decode json string. Proses decode sendiri dilakukan lewat fungsi `json.Unmarshal()`, dalam penggunaannya data json string dimasukan sebagai argument pemanggilan fungsi.
+
+Contoh praktiknya bisa dilihat di bawah ini.
+
+```go
+func main() {
+ var jsonString = `{"Name": "john wick", "Age": 27}`
+ var jsonData = []byte(jsonString)
+
+ var data User
+
+ var err = json.Unmarshal(jsonData, &data)
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+
+ fmt.Println("user :", data.FullName)
+ fmt.Println("age :", data.Age)
+}
+```
+
+Fungsi unmarshal hanya menerima data json dalam bentuk `[]byte`, maka dari itu data json string perlu di-casting terlebih dahulu ke tipe `[]byte`, sebelum akhirnya digunakan pada pemanggilan fungsi `json.Unmarshal()`.
+
+Perlu diperhatikan, argument ke-2 pemanggilan fungsi tersebut harus diisi dengan variabel **pointer** yang nantinya akan menampung hasil operasi decoding.
+
+![Decode data json ke variabel objek](images/A_json_1_decode.png)
+
+Property `FullName` milik struct `User` memiliki **tag** `json:"Name"`. Tag tersebut digunakan untuk mapping informasi field json ke property struct.
+
+Data json yang akan di-parsing memiliki 2 property yaitu `Name` dan `Age`. Di contoh, penulisan `Age` di data json dan pada struktur struct adalah sama, berbeda dengan `Name` yang ada di data json tapi tidak ada di struct.
+
+Dengan menambahkan tag json, maka property `FullName` struct akan secara cerdas menampung data json property `Name`.
+
+> Pada operasi decoding data json string ke variabel objek struct, semua level akses property struct penampung harus publik.
+
+## A.53.2. Decode JSON Ke `map[string]interface{}` & `interface{}`
+
+Tak hanya ke objek cetakan struct, target decoding data json juga bisa berupa variabel bertipe `map[string]interface{}`.
+
+```go
+var data1 map[string]interface{}
+json.Unmarshal(jsonData, &data1)
+
+fmt.Println("user :", data1["Name"])
+fmt.Println("age :", data1["Age"])
+```
+
+Variabel bertipe `interface{}` juga bisa digunakan untuk menampung hasil decode. Dengan catatan pada pengaksesan nilai property, harus dilakukan casting terlebih dahulu ke `map[string]interface{}`.
+
+```go
+var data2 interface{}
+json.Unmarshal(jsonData, &data2)
+
+var decodedData = data2.(map[string]interface{})
+fmt.Println("user :", decodedData["Name"])
+fmt.Println("age :", decodedData["Age"])
+```
+
+## A.53.3. Decode Array JSON Ke Array Objek
+
+Operasi decode data dari array json ke slice/array objek caranya juga sama. Langsung praktek saja agar lebih jelas. Siapkan sebuah variabel baru untuk menampung hasil decode dengan tipe slice struct, lalu gunakan pada fungsi `json.Unmarshal()`.
+
+```go
+var jsonString = `[
+ {"Name": "john wick", "Age": 27},
+ {"Name": "ethan hunt", "Age": 32}
+]`
+
+var data []User
+
+var err = json.Unmarshal([]byte(jsonString), &data)
+if err != nil {
+ fmt.Println(err.Error())
+ return
+}
+
+fmt.Println("user 1:", data[0].FullName)
+fmt.Println("user 2:", data[1].FullName)
+```
+
+## A.53.4. Encode Objek Ke JSON String
+
+Setelah sebelumnya dijelaskan beberapa cara decode data dari json string ke objek, sekarang kita akan belajar cara **encode** data objek di Go ke bentuk json string.
+
+Fungsi `json.Marshal()` digunakan untuk encoding data ke json string. Sumber data bisa berupa variabel objek cetakan struct, data bertipe `map[string]interface{}`, slice, atau lainnya.
+
+Pada contoh berikut, data slice struct dikonversi ke dalam bentuk json string. Hasil konversi adalah data bertipe `[]byte`, maka pastikan untuk meng-casting terlebih dahulu ke tipe `string` agar bisa ditampilkan bentuk json string-nya.
+
+```go
+var object = []User{{"john wick", 27}, {"ethan hunt", 32}}
+var jsonData, err = json.Marshal(object)
+if err != nil {
+ fmt.Println(err.Error())
+ return
+}
+
+var jsonString = string(jsonData)
+fmt.Println(jsonString)
+```
+
+Output program:
+
+![Encode data ke JSON](images/A_json_2_encode.png)
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-komentar.md b/en/content-en/A-komentar.md
new file mode 100644
index 000000000..ea364f2ce
--- /dev/null
+++ b/en/content-en/A-komentar.md
@@ -0,0 +1,56 @@
+# A.8. Komentar
+
+Komentar biasa dimanfaatkan untuk untuk menyisipkan catatan pada kode program, atau untuk menulis penjelasan/deskripsi mengenai suatu blok kode, atau bisa juga digunakan untuk me-*remark* kode (men-non-aktifkan kode yg tidak digunakan). Komentar selalu diabaikan ketika kompilasi maupun eksekusi program.
+
+Ada 2 jenis komentar di Go, *inline* & *multiline*. Pada pembahasan ini akan dijelaskan tentang penerapan dan perbedaan kedua jenis komentar tersebut.
+
+## A.8.1. Komentar *Inline*
+
+Penulisan komentar jenis ini di awali dengan tanda **double slash** (`//`) lalu diikuti pesan komentarnya. Komentar inline hanya berlaku untuk satu baris pesan saja. Jika pesan komentar lebih dari satu baris, maka tanda `//` harus ditulis lagi di baris selanjutnya.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ // komentar kode
+ // menampilkan pesan hello world
+ fmt.Println("hello world")
+
+ // fmt.Println("baris ini tidak akan dieksekusi")
+}
+```
+
+Mari kita praktekan kode di atas. Siapkan file program baru dalam project folder (bisa buat project baru atau gunakan project yang sudah ada). Kemudian isi file dengan kode di atas, lalu jalankan.
+
+![Contoh komentar inline](images/A_komentar_1_inline_comment.png)
+
+Hasilnya hanya tulisan **hello world** saja yang muncul di layar, karena semua yang di awali tanda double slash `//` diabaikan oleh compiler.
+
+## A.8.2. Komentar *Multiline*
+
+Komentar yang cukup panjang akan lebih rapi jika ditulis menggunakan teknik komentar multiline. Ciri dari komentar jenis ini adalah penulisannya diawali dengan tanda `/*` dan diakhiri `*/`.
+
+```go
+/*
+ komentar kode
+ menampilkan pesan hello world
+*/
+fmt.Println("hello world")
+
+// fmt.Println("baris ini tidak akan dieksekusi")
+```
+
+Sifat komentar ini sama seperti komentar inline, yaitu sama-sama diabaikan oleh compiler.
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-konstanta.md b/en/content-en/A-konstanta.md
new file mode 100644
index 000000000..468de16e4
--- /dev/null
+++ b/en/content-en/A-konstanta.md
@@ -0,0 +1,109 @@
+# A.11. Konstanta
+
+Konstanta adalah jenis variabel yang nilainya tidak bisa diubah setelah dideklarasikan. Inisialisasi nilai konstanta hanya dilakukan sekali saja di awal, setelah itu variabel tidak bisa diubah nilainya.
+
+## A.11.1. Penggunaan Konstanta
+
+Data seperti **pi** (22/7), kecepatan cahaya (299.792.458 m/s), adalah contoh data yang tepat untuk dideklarasikan sebagai konstanta (daripada variabel), karena nilainya sudah pasti dan tidak akan berubah.
+
+Cara penerapan konstanta sama seperti deklarasi variabel biasa, perbedaannya ada pada keyword yang digunakan, yaitu `const` (bukan `var`).
+
+```go
+const firstName string = "john"
+fmt.Print("halo ", firstName, "!\n")
+```
+
+Teknik type inference bisa diterapkan pada konstanta, caranya cukup dengan menghilangkan tipe data pada saat deklarasi.
+
+```go
+const lastName = "wick"
+fmt.Print("nice to meet you ", lastName, "!\n")
+```
+
+#### ◉ Penggunaan Fungsi `fmt.Print()`
+
+Fungsi ini memiliki peran yang sama seperti fungsi `fmt.Println()`, perbedaannya fungsi `fmt.Print()` tidak menghasilkan baris baru di akhir output-nya.
+
+Perbedaan lainnya: nilai argument parameter yang ditulis saat pemanggilan fungsi akan di-print tanpa pemisah. Tidak seperti pada fungsi `fmt.Println()` yang nilai argument paremeternya dipisah menggunakan karakter spasi.
+
+```go
+fmt.Println("john wick")
+fmt.Println("john", "wick")
+
+fmt.Print("john wick\n")
+fmt.Print("john ", "wick\n")
+fmt.Print("john", " ", "wick\n")
+```
+
+Kode di atas menunjukkan perbedaan antara `fmt.Println()` dan `fmt.Print()`. Output yang dihasilkan oleh 5 statement di atas adalah sama, meski cara yang digunakan berbeda.
+
+Bila menggunakan `fmt.Println()`, maka tidak perlu menambahkan spasi di tiap kata, karena fungsi tersebut akan secara otomatis menambahkannya di sela-sela text. Berbeda dengan `fmt.Print()` yang perlu ditambahkan spasi, karena fungsi ini tidak menambahkan spasi secara otomatis di sela-sela nilai text yang digabungkan.
+
+## A.11.2. Deklarasi Multi Konstanta
+
+Sama seperti variabel, konstanta juga dapat dideklarasikan secara bersamaan. Berikut adalah contoh deklarasi konstanta dengan tipe data dan nilai yang berbeda.
+
+```go
+const (
+ square = "kotak"
+ isToday bool = true
+ numeric uint8 = 1
+ floatNum = 2.2
+)
+```
+
+- `square`, dideklarasikan dengan metode _type inference_ dengan tipe data **string** dan nilainya **"kotak"**
+- `isToday`, dideklarasikan dengan metode _manifest typing_ dengan tipe data **bool** dan nilainya **true**
+- `numeric`, dideklarasikan dengan metode _manifest typing_ dengan tipe data **uint8** dan nilainya **1**
+- `floatNum`, dideklarasikan dengan metode _type inference_ dengan tipe data **float** dan nilainya **2.2**
+
+Contoh deklarasi konstanta dengan tipe data dan nilai yang sama:
+
+```go
+const (
+ a = "konstanta"
+ b
+)
+```
+
+> Ketika tipe data dan nilai tidak dituliskan dalam deklarasi konstanta, maka tipe data dan nilai yang dipergunakan adalah sama seperti konstanta yang dideklarasikan diatasnya.
+
+- `a` dideklarasikan dengan metode _type inference_ dengan tipe data **string** dan nilainya **"konstanta"**
+- `b` dideklarasikan dengan metode _type inference_ dengan tipe data **string** dan nilainya **"konstanta"**
+
+Berikut contoh gabungan dari keduanya:
+
+```go
+const (
+ today string = "senin"
+ sekarang
+ isToday2 = true
+)
+```
+
+- `today` dideklarasikan dengan metode _manifest typing_ dengan tipe data **string** dan nilainya **"senin"**
+- `sekarang` dideklarasikan dengan metode _manifest typing_ dengan tipe data **string** dan nilainya **"senin"**
+- `isToday2` dideklarasikan dengan metode _type inference_ dengan tipe data **bool** dan nilainya **true**
+
+Berikut contoh deklrasi _multiple_ konstanta dalam satu baris:
+
+```go
+const satu, dua = 1, 2
+const three, four string = "tiga", "empat"
+```
+
+- `satu`, dideklarasikan dengan metode _type inference_ dengan tipe data **int** dan nilainya **1**
+- `dua`, dideklarasikan dengan metode _type inference_ dengan tipe data **int** dan nilainya **2**
+- `three`, dideklarasikan dengan metode _manifest typing_ dengan tipe data **string** dan nilainya **"tiga"**
+- `four`, dideklarasikan dengan metode _manifest typing_ dengan tipe data **string** dan nilainya **"empat"**
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-map.md b/en/content-en/A-map.md
new file mode 100644
index 000000000..4d58dc576
--- /dev/null
+++ b/en/content-en/A-map.md
@@ -0,0 +1,182 @@
+# A.17. Map
+
+**Map** adalah tipe data asosiatif yang ada di Go yang berbentuk *key-value pair*. Data/value yang disimpan di map selalu disertai dengan key. Key sendiri harus unik, karena digunakan sebagai penanda (atau identifier) untuk pengaksesan value yang disimpan di map.
+
+Kalau dilihat, `map` mirip seperti slice, hanya saja identifier yang digunakan untuk pengaksesan bukanlah index numerik, melainkan bisa dalam tipe data apapun sesuai dengan yang diinginkan.
+
+## A.17.1. Penggunaan Map
+
+Cara pengaplikasian map cukup mudah, dengan menuliskan keyword `map` diikuti tipe data key dan value-nya. Silakan perhatikan contoh di bawah ini agar lebih jelas.
+
+```go
+var chicken map[string]int
+chicken = map[string]int{}
+
+chicken["januari"] = 50
+chicken["februari"] = 40
+
+fmt.Println("januari", chicken["januari"]) // januari 50
+fmt.Println("mei", chicken["mei"]) // mei 0
+```
+
+Variabel `chicken` dideklarasikan bertipe data map, dengan key ditentukan tipenya adalah `string` dan tipe value-nya `int`. Dari kode tersebut bisa dilihat bagaimana cara penerapan keyword `map` untuk pembuatan variabel.
+
+Kode `map[string]int` merepresentasikan tipe data `map` dengan key bertipe `string` dan value bertipe `int`.
+
+Zero value atau nilai default variabel `map` adalah `nil`. Dari sini maka penting untuk menginisialisasi nilai awal map agar tidak `nil`. Jika dibiarkan `nil`, ketika map digunakan untuk menampung data pasti memunculkan error.
+
+Cara untuk inisialisasi map dengan menambahkan kurung kurawal buka tutup di akhir penulisan map, contoh: `map[string]int{}`.
+
+Cara menambahkan item pada map adalah dengan menuliskan variabel-nya, kemudian diikuti dengan `key` pada kurung siku variabel (mirip seperti cara pengaksesan elemen slice), lalu operator `=`, kemudian nilai/data yang ingin disimpan. Contohnya seperti `chicken["februari"] = 40`. Sedangkan cara mengakses item map dengan cukup dengan menuliskan nama variabel diikuti kurung siku dan `key`.
+
+Pengisian data pada map bersifat **overwrite**, artinya variabel sudah memiliki item dengan key yang sama, maka value item yang lama (dengan key sama) akan ditimpa dengan value baru.
+
+![Pengaksesan data map](images/A_map_1_map_set_get.png)
+
+Pengaksesan item menggunakan key yang belum tersimpan di map, menghasilkan data berupa nilai default sesuai tipe data value. Contohnya kode `chicken["mei"]` menghasilkan nilai 0 (nilai default tipe `int`), hal ini karena variabel map `chicken` tidak memiliki item dengan key `"mei"`.
+
+## A.17.2. Inisialisasi Nilai Map
+
+Zero value dari map adalah `nil`. Disarankan untuk menginisialisasi secara explisit nilai awalnya agar tidak `nil`.
+
+```go
+var data map[string]int
+data["one"] = 1
+// akan muncul error!
+
+data = map[string]int{}
+data["one"] = 1
+// tidak ada error
+```
+
+Nilai variabel bertipe map bisa didefinisikan di awal, caranya dengan menambahkan kurung kurawal setelah tipe data, kemudian menuliskan key dan value di dalam kurung kurawal tersebut. Cara ini sekilas mirip dengan definisi nilai array/slice namun dalam bentuk key-value.
+
+```go
+// cara horizontal
+var chicken1 = map[string]int{"januari": 50, "februari": 40}
+
+// cara vertical
+var chicken2 = map[string]int{
+ "januari": 50,
+ "februari": 40,
+}
+```
+
+Key dan value dituliskan dengan pembatas tanda titik dua (`:`). Sedangkan tiap itemnya dituliskan dengan pembatas tanda koma (`,`). Khusus deklarasi dengan gaya vertikal, tanda koma perlu dituliskan setelah item terakhir.
+
+Variabel `map` bisa di-inisialisasi dengan tanpa nilai awal, caranya menggunakan tanda kurung kurawal, contoh: `map[string]int{}`. Atau bisa juga dengan menggunakan keyword `make` dan `new`. Contohnya bisa dilihat pada kode berikut. Ketiga cara di bawah ini intinya adalah sama.
+
+```go
+var chicken3 = map[string]int{}
+var chicken4 = make(map[string]int)
+var chicken5 = *new(map[string]int)
+```
+
+Khusus inisialisasi data menggunakan keyword `new`, yang dihasilkan adalah data pointer. Untuk mengambil nilai aslinya bisa dengan menggunakan tanda asterisk (`*`). Topik pointer nantinya dibahas lebih detail pada chapter [A.23. Pointer](/A-pointer.html).
+
+## A.17.3. Iterasi Item Map Menggunakan `for` - `range`
+
+Item variabel `map` bisa di iterasi menggunakan `for` - `range`. Cara penerapannya masih sama seperti pada slice, dengan perbedaan pada map data yang dikembalikan di tiap perulangan adalah key dan value (bukan indeks dan elemen). Contohnya bisa dilihat pada kode berikut.
+
+```go
+var chicken = map[string]int{
+ "januari": 50,
+ "februari": 40,
+ "maret": 34,
+ "april": 67,
+}
+
+for key, val := range chicken {
+ fmt.Println(key, " \t:", val)
+}
+```
+
+![Perulangan Map](images/A_map_2_map_for_range.png)
+
+## A.17.4. Menghapus Item Map
+
+Fungsi `delete()` digunakan untuk menghapus item dengan key tertentu pada variabel map. Cara penggunaannya, dengan memasukan objek map dan key item yang ingin dihapus sebagai argument pemanggilan fungsi `delete()`.
+
+```go
+var chicken = map[string]int{"januari": 50, "februari": 40}
+
+fmt.Println(len(chicken)) // 2
+fmt.Println(chicken)
+
+delete(chicken, "januari")
+
+fmt.Println(len(chicken)) // 1
+fmt.Println(chicken)
+```
+
+Operasi di atas membuat item dengan key `"januari"` dalam variabel map `chicken` dihapus.
+
+![Hapus item Map](images/A_map_3_map_delete_item.png)
+
+Penggunaan fungsi `len()` pada map mengembalikan informasi jumlah item.
+
+## A.17.5. Deteksi Keberadaan Item Dengan Key Tertentu
+
+Ada cara untuk mengetahui apakah dalam variabel map terdapat item dengan key tertentu atau tidak, yaitu dengan memanfaatkan 2 variabel sebagai penampung nilai kembalian pengaksesan item. Return value ke-2 sifatnya opsional, boleh ditulis boleh juga tidak. Isinya nilai `bool`, jika berisi `true` menandakan bahwa item yang dicari ada di map, jika `false` maka tidak ada.
+
+```go
+var chicken = map[string]int{"januari": 50, "februari": 40}
+var value, isExist = chicken["mei"]
+
+if isExist {
+ fmt.Println(value)
+} else {
+ fmt.Println("item is not exists")
+}
+```
+
+## A.17.6. Kombinasi Slice & Map
+
+Slice dan `map` bisa dikombinasikan, dan pada praktiknya cukup sering digunakan, contohnya untuk keperluan penyimpanan data array yang berisikan informasi siswa, dan banyak lainnya.
+
+Cara penerapannya cukup mudah, contohnya `[]map[string]int`, tipe tersebut artinya adalah sebuah slice yang tipe setiap elemen-nya adalah `map[string]int`. Agar lebih jelas, silakan praktekan contoh berikut.
+
+```go
+var chickens = []map[string]string{
+ map[string]string{"name": "chicken blue", "gender": "male"},
+ map[string]string{"name": "chicken red", "gender": "male"},
+ map[string]string{"name": "chicken yellow", "gender": "female"},
+}
+
+for _, chicken := range chickens {
+ fmt.Println(chicken["gender"], chicken["name"])
+}
+```
+
+Variabel `chickens` di atas berisikan 3 buah item bertipe `map[string]string`. Ketiga item tersebut dideklarasikan memiliki 2 key yang sama, yaitu `name` dan `gender`.
+
+Penulisan tipe data tiap item adalah opsional. Boleh ditulis atau tidak. Contoh alternatif penulisan:
+
+```go
+var chickens = []map[string]string{
+ {"name": "chicken blue", "gender": "male"},
+ {"name": "chicken red", "gender": "male"},
+ {"name": "chicken yellow", "gender": "female"},
+}
+```
+
+Dalam `[]map[string]string`, tiap elemen bisa saja memiliki key yang berbeda-beda, contohnya seperti kode berikut.
+
+```go
+var data = []map[string]string{
+ {"name": "chicken blue", "gender": "male", "color": "brown"},
+ {"address": "mangga street", "id": "k001"},
+ {"community": "chicken lovers"},
+}
+```
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-method.md b/en/content-en/A-method.md
new file mode 100644
index 000000000..cdd2afb53
--- /dev/null
+++ b/en/content-en/A-method.md
@@ -0,0 +1,166 @@
+# A.25. Method
+
+**Method** adalah fungsi yang menempel pada suatu tipe data, misalnya custom `struct`. Method bisa diakses lewat variabel objek yang dibuat dari tipe custom struct tersebut.
+
+Keunggulan method dibanding fungsi biasa adalah method memiliki akses ke property struct hingga level akses *private*. Selain itu, dengan menggunakan method, suatu proses bisa di-enkapsulasi dengan baik.
+
+> Perihal topik level nantinya dibahas secara terpisah pada chapter berikutnya
+
+## A.25.1. Penerapan Method
+
+Cara penerapan method sedikit berbeda dibanding fungsi. Saat proses deklarasi, pada method perlu ditentukan juga siapa pemiliknya. Contohnya bisa dilihat pada kode berikut, dua method diciptakan sebagai property dari struct bernama `student`.
+
+```go
+package main
+
+import "fmt"
+import "strings"
+
+type student struct {
+ name string
+ grade int
+}
+
+func (s student) sayHello() {
+ fmt.Println("halo", s.name)
+}
+
+func (s student) getNameAt(i int) string {
+ return strings.Split(s.name, " ")[i-1]
+}
+```
+
+Cara deklarasi method mirip seperti fungsi, tapi dalam penulisannya perlu ditambahkan deklarasi variabel objek di sela-sela keyword `func` dan nama fungsi. Pada contoh di atas struct `student` ditentukan sebagai pemilik method.
+
+`func (s student) sayHello()` maksudnya adalah fungsi `sayHello` dideklarasikan sebagai method milik struct `student`. Di contoh, struct `student` memiliki dua buah method yaitu `sayHello()` dan `getNameAt()`.
+
+Contoh pemanfaatan method bisa dilihat pada kode berikut.
+
+```go
+func main() {
+ var s1 = student{"john wick", 21}
+ s1.sayHello()
+
+ var name = s1.getNameAt(2)
+ fmt.Println("nama panggilan :", name)
+}
+```
+
+Output program:
+
+![Penggunaan method](images/A_method_1_method.png)
+
+Cara mengakses method sama seperti pada pengaksesan property, yaitu dengan cukup panggil saja nama methodnya.
+
+```go
+s1.sayHello()
+var name = s1.getNameAt(2)
+```
+
+Method memiliki sifat yang sama persis dengan fungsi biasa, yaitu bisa memiliki parameter, nilai balik, dan sifat-sifat lainnya.
+
+Dari segi sintaks, perbedaan yang paling terlihat hanya di bagian penulisan deklarasi dan cara pengaksesan. Silakan lihat kode berikut agar lebih jelas:
+
+```go
+func sayHello() { }
+func (s student) sayHello() { }
+
+func getNameAt(i int) string { }
+func (s student) getNameAt(i int) string { }
+```
+
+## A.25.2. Method Pointer
+
+Method pointer adalah method yang dimana variabel objek pemilik method tersebut adalah berbentuk pointer.
+
+Kelebihan method jenis ini adalah ketika kita melakukan manipulasi nilai pada property lain yang masih satu struct, nilai pada property tersebut bisa diubah di-level reference-nya. Lebih jelasnya perhatikan kode berikut.
+
+```go
+package main
+
+import "fmt"
+
+type student struct {
+ name string
+ grade int
+}
+
+func (s student) changeName1(name string) {
+ fmt.Println("---> on changeName1, name changed to", name)
+ s.name = name
+}
+
+func (s *student) changeName2(name string) {
+ fmt.Println("---> on changeName2, name changed to", name)
+ s.name = name
+}
+
+func main() {
+ var s1 = student{"john wick", 21}
+ fmt.Println("s1 before", s1.name)
+ // john wick
+
+ s1.changeName1("jason bourne")
+ fmt.Println("s1 after changeName1", s1.name)
+ // john wick
+
+ s1.changeName2("ethan hunt")
+ fmt.Println("s1 after changeName2", s1.name)
+ // ethan hunt
+}
+```
+
+Output program:
+
+![Penggunaan method pointer](images/A_method_2_method_pointer.png)
+
+Setelah statement `s1.changeName1("jason bourne")` dieksekusi, nilai `s1.name` tidak berubah. Sebenarnya nilainya berubah tapi hanya dalam method `changeName1()` saja, nilai pada reference objek-nya tidak berubah.
+
+Keistimewaan lain method pointer adalah method itu sendiri bisa dipanggil dari objek pointer maupun objek biasa.
+
+```go
+// pengaksesan method dari variabel objek biasa
+var s1 = student{"john wick", 21}
+s1.sayHello()
+
+// pengaksesan method dari variabel objek pointer
+var s2 = &student{"ethan hunt", 22}
+s2.sayHello()
+```
+
+## A.25.3. Penjelasan tambahan
+
+Berikut merupakan penjelasan tambahan untuk beberapa hal dari kode yang sudah dipraktekan:
+
+#### ◉ Penggunaan Fungsi `strings.Split()`
+
+Pada chapter ini ada fungsi baru yang kita gunakan saat praktek, yaitu `strings.Split()`. Fungsi ini berguna untuk memisahkan string menggunakan pemisah yang kita tentukan sendiri. Hasilnya berupa array berisikan kumpulan substring.
+
+```go
+strings.Split("ethan hunt", " ")
+// ["ethan", "hunt"]
+```
+
+Pada contoh di atas, string `"ethan hunt"` dipisah menggunakan separator spasi `" "`, hasilnya adalah array berisikan 2 elemen, `"ethan"` dan `"hunt"`.
+
+## A.25.3. Apakah `fmt.Println()` & `strings.Split()` Juga Merupakan Method?
+
+Setelah tahu apa itu method dan bagaimana penggunaannya, mungkin akan muncul di benak kita bahwa kode seperti `fmt.Println()`, `strings.Split()` dan lainnya-yang-berada-pada-package-lain adalah merupakan method. Jawabannya,**bukan!**. `fmt` di situ bukanlah variabel objek, dan `Println()` bukan merupakan method.
+
+`fmt` adalah nama **package** yang di-import (bisa dilihat pada kode `import "fmt"`). Sedangkan `Println()` adalah **nama fungsi**. Untuk mengakses fungsi yang berada pada package lain, harus dituliskan juga nama package-nya, contoh:
+
+- Statement `fmt.Println()` berarti pengaksesan fungsi `Println()` yang berada di package `fmt`
+- Statement `strings.Split()` berarti pengaksesan fungsi `Split()` yang berada di package `strings`
+
+Lebih detailnya dibahas pada chapter selanjutnya.
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-mongodb.md b/en/content-en/A-mongodb.md
new file mode 100644
index 000000000..c8c862f63
--- /dev/null
+++ b/en/content-en/A-mongodb.md
@@ -0,0 +1,336 @@
+# A.57. NoSQL MongoDB
+
+Go tidak menyediakan interface generic untuk NoSQL, jadi implementasi driver tiap brand NoSQL di Go biasanya berbeda satu dengan lainnya.
+
+Pada chapter ini kita akan belajar cara berkomunikasi dengan NoSQL MongoDB server menggunakan official driver untuk go, yaitu [mongo-go-driver](https://github.com/mongodb/mongo-go-driver).
+
+## A.57.1. Persiapan
+
+Ada beberapa hal yang perlu disiapkan sebelum mulai masuk ke bagian coding.
+
+ 1. Instal mongo-go-driver menggunakan `go get`.
+
+ ```
+ cd
+ ||
| kiri **atau** kanan |
+| `!` | negasi / nilai kebalikan |
+
+Contoh penggunaan:
+
+```go
+var left = false
+var right = true
+
+var leftAndRight = left && right
+fmt.Printf("left && right \t(%t) \n", leftAndRight)
+
+var leftOrRight = left || right
+fmt.Printf("left || right \t(%t) \n", leftOrRight)
+
+var leftReverse = !left
+fmt.Printf("!left \t\t(%t) \n", leftReverse)
+```
+
+Hasil dari operator logika sama dengan hasil dari operator perbandingan, yaitu berupa boolean.
+
+![Penerapan operator logika](images/A_operator_2_operator_logical.png)
+
+Berikut penjelasan statemen operator logika pada kode di atas.
+
+ - `leftAndRight` bernilai `false`, karena hasil dari `false` **dan** `true` adalah `false`.
+ - `leftOrRight` bernilai `true`, karena hasil dari `false` **atau** `true` adalah `true`.
+ - `leftReverse` bernilai `true`, karena **negasi** (atau lawan dari) `false` adalah `true`.
+
+Template `\t` digunakan untuk menambahkan indent tabulasi. Biasa dimanfaatkan untuk merapikan tampilan output pada console.
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-perulangan.md b/en/content-en/A-perulangan.md
new file mode 100644
index 000000000..3c82069ef
--- /dev/null
+++ b/en/content-en/A-perulangan.md
@@ -0,0 +1,171 @@
+# A.14. Perulangan
+
+Perulangan adalah proses mengulang-ulang eksekusi blok kode tanpa henti, selama kondisi yang dijadikan acuan terpenuhi. Biasanya disiapkan variabel untuk iterasi atau variabel penanda kapan perulangan akan diberhentikan.
+
+Di Go keyword perulangan hanya **for** saja, tetapi meski demikian, kemampuannya merupakan gabungan `for`, `foreach`, dan `while` ibarat bahasa pemrograman lain.
+
+## A.14.1. Perulangan Menggunakan Keyword `for`
+
+Ada beberapa cara standar menggunakan `for`. Cara pertama dengan memasukkan variabel counter perulangan beserta kondisinya setelah keyword. Perhatikan dan praktekan kode berikut.
+
+```go
+for i := 0; i < 5; i++ {
+ fmt.Println("Angka", i)
+}
+```
+
+Perulangan di atas hanya akan berjalan ketika variabel `i` bernilai di bawah `5`, dengan ketentuan setiap kali perulangan, nilai variabel `i` akan di-iterasi atau ditambahkan 1 (`i++` artinya ditambah satu, sama seperti `i = i + 1`). Karena `i` pada awalnya bernilai 0, maka perulangan akan berlangsung 5 kali, yaitu ketika `i` bernilai 0, 1, 2, 3, dan 4.
+
+![Penggunaan
+ for
](images/A_perulangan_1_for.png)
+
+## A.14.2. Penggunaan Keyword `for` Dengan Argumen Hanya Kondisi
+
+Cara ke-2 adalah dengan menuliskan kondisi setelah keyword `for` (hanya kondisi). Deklarasi dan iterasi variabel counter tidak dituliskan setelah keyword, hanya kondisi perulangan saja. Konsepnya mirip seperti `while` milik bahasa pemrograman lain.
+
+Kode berikut adalah contoh `for` dengan argumen hanya kondisi (seperti `if`), output yang dihasilkan sama seperti penerapan `for` cara pertama.
+
+```go
+var i = 0
+
+for i < 5 {
+ fmt.Println("Angka", i)
+ i++
+}
+```
+
+## A.14.3. Penggunaan Keyword `for` Tanpa Argumen
+
+Cara ke-3 adalah `for` ditulis tanpa kondisi. Dengan ini akan dihasilkan perulangan tanpa henti (sama dengan `for true`). Pemberhentian perulangan dilakukan dengan menggunakan keyword `break`.
+
+```go
+var i = 0
+
+for {
+ fmt.Println("Angka", i)
+
+ i++
+ if i == 5 {
+ break
+ }
+}
+```
+
+Dalam perulangan tanpa henti di atas, variabel `i` yang nilai awalnya `0` di-inkrementasi. Ketika nilai `i` sudah mencapai `5`, keyword `break` digunakan, dan perulangan akan berhenti.
+
+## A.14.4. Penggunaan Keyword `for` - `range`
+
+Cara ke-4 adalah perulangan dengan menggunakan kombinasi keyword `for` dan `range`. Cara ini biasa digunakan untuk me-looping data gabungan (misalnya string, array, slice, map). Detailnya akan dibahas dalam chapter-chapter selanjutnya ([A.15. Array](/A-array.html), [A.16. Slice](/A-slice.html), [A.17. Map](/A-map.html)).
+
+```go
+var xs = "123" // string
+for i, v := range xs {
+ fmt.Println("Index=", i, "Value=", v)
+}
+
+var ys = [5]int{10, 20, 30, 40, 50} // array
+for _, v := range ys {
+ fmt.Println("Value=", v)
+}
+
+var zs = ys[0:2] // slice
+for _, v := range zs {
+ fmt.Println("Value=", v)
+}
+
+var kvs = map[byte]int{'a': 0, 'b': 1, 'c': 2} // map
+for k, v := range kvs {
+ fmt.Println("Key=", k, "Value=", v)
+}
+
+// boleh juga baik k dan atau v nya diabaikan
+for range kvs {
+ fmt.Println("Done")
+}
+
+// selain itu, bisa juga dengan cukup menentukan nilai numerik perulangan
+for i := range 5 {
+ fmt.Print(i) // 01234
+}
+```
+
+## A.14.5. Penggunaan Keyword `break` & `continue`
+
+Keyword `break` digunakan untuk menghentikan secara paksa sebuah perulangan, sedangkan `continue` dipakai untuk memaksa maju ke perulangan berikutnya.
+
+Berikut merupakan contoh penerapan `continue` dan `break`. Kedua keyword tersebut dimanfaatkan untuk menampilkan angka genap berurutan yang lebih besar dari 0 dan kurang dari atau sama dengan 8.
+
+```go
+for i := 1; i <= 10; i++ {
+ if i % 2 == 1 {
+ continue
+ }
+
+ if i > 8 {
+ break
+ }
+
+ fmt.Println("Angka", i)
+}
+```
+
+Kode di atas akan lebih mudah dicerna jika dijelaskan secara berurutan. Berikut adalah penjelasannya.
+
+ 1. Dilakukan perulangan mulai angka 1 hingga 10 dengan `i` sebagai variabel iterasi.
+ 2. Ketika `i` adalah ganjil (dapat diketahui dari `i % 2`, jika hasilnya `1`, berarti ganjil), maka akan dipaksa lanjut ke perulangan berikutnya.
+ 3. Ketika `i` lebih besar dari 8, maka perulangan akan berhenti.
+ 4. Nilai `i` ditampilkan.
+
+![Penerapan keyword for
, break
, dan continue
](images/A_perulangan_2_for_break_continue.png)
+
+## A.14.6. Perulangan Bersarang
+
+Tak hanya seleksi kondisi yang bisa bersarang, perulangan juga bisa. Cara pengaplikasiannya kurang lebih sama, tinggal tulis blok statement perulangan di dalam perulangan.
+
+```go
+for i := 0; i < 5; i++ {
+ for j := i; j < 5; j++ {
+ fmt.Print(j, " ")
+ }
+
+ fmt.Println()
+}
+```
+
+Pada kode di atas, untuk pertama kalinya fungsi `fmt.Println()` dipanggil tanpa disisipkan parameter. Cara seperti ini bisa digunakan untuk menampilkan baris baru. Kegunaannya sama seperti output dari statement `fmt.Print("\n")`.
+
+![Perulangan bersarang](images/A_perulangan_3_nested_for.png)
+
+## A.14.7. Pemanfaatan Label Dalam Perulangan
+
+Di perulangan bersarang, `break` dan `continue` akan berlaku pada blok perulangan di mana ia digunakan saja. Ada cara agar kedua keyword ini bisa tertuju pada perulangan terluar atau perulangan tertentu, yaitu dengan memanfaatkan teknik pemberian **label**.
+
+Program untuk memunculkan matriks berikut merupakan contoh penerapan label perulangan.
+
+```go
+outerLoop:
+for i := 0; i < 5; i++ {
+ for j := 0; j < 5; j++ {
+ if i == 3 {
+ break outerLoop
+ }
+ fmt.Print("matriks [", i, "][", j, "]", "\n")
+ }
+}
+```
+
+Tepat sebelum keyword `for` terluar, terdapat baris kode `outerLoop:`. Maksud dari kode tersebut adalah disiapkan sebuah label bernama `outerLoop` untuk `for` di bawahnya. Nama label bisa diganti dengan nama lain (dan harus diakhiri dengan tanda titik dua atau *colon* (`:`) ).
+
+Pada `for` bagian dalam, terdapat seleksi kondisi untuk pengecekan nilai `i`. Ketika nilai tersebut sama dengan `3`, maka `break` dipanggil dengan target adalah perulangan yang dilabeli `outerLoop`, perulangan tersebut akan dihentikan.
+
+![Penerapan label dalam perulangan](images/A_perulangan_4_for_label.png)
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-pipeline-context-cancellation.md b/en/content-en/A-pipeline-context-cancellation.md
new file mode 100644
index 000000000..2a54bc4a2
--- /dev/null
+++ b/en/content-en/A-pipeline-context-cancellation.md
@@ -0,0 +1,457 @@
+# A.64. Concurrency Pattern: Context Cancellation Pipeline
+
+Pada chapter ini kita akan belajar tentang salah satu *concurrency pattern* di Go, yaitu **cancellation**. Cancellation merupakan mekanisme untuk menggagalkan secara paksa proses konkuren yang sedang berjalan, entah itu karena ada timeout, ada error, atau ada faktor lain.
+
+Di sini kita akan gunakan salah satu API milik Go yang tersedia untuk *cancellation*, yaitu `context.Context`.
+
+Context digunakan untuk mendefinisikan tipe *context* yang di dalamnya ada beberapa hal yaitu: informasi *deadlines*, signal *cancellation*, dan data untuk keperluan komunikasi antar API atau antar proses.
+
+## A.64.1. Skenario Praktek
+
+Kita akan modifikasi file program `1-generate-dummy-files-concurrently.go` yang pada chapter sebelumnya ([A.63. Concurrency Pattern: Simplified Fan-in Fan-out Pipeline](/A-simplified-fan-in-fan-out-pipeline.html)) sudah dibuat. Pada program tersebut akan kita tambahkan mekanisme cancellation ketika ada timeout.
+
+Jadi kurang lebih akan ada dua result:
+
+- Proses sukses, karena *execution time* di bawah timeout.
+- Proses digagalkan secara paksa ditengah jalan, karena *running time* sudah melebihi batas timeout.
+
+## A.64.2. Program Generate Dummy File *Concurrently*
+
+Ok langsung saja, pertama yang perlu dipersiapkan adalah tulis dulu kode program versi *concurrent* tanpa *cancellation*. Bisa langsung copy-paste, atau tulis dari awal dengan mengikut tutorial ini secara keseluruhan. Untuk penjelasan detail program versi sekuensial silakan merujuk ke chapter sebelumnya saja, di sini kita tulis langsung agar bisa cepat dimulai bagian program konkuren.
+
+#### ◉ Import Packages dan Definisi Variabel
+
+```go
+package main
+
+import (
+ "fmt"
+ "log"
+ "math/rand"
+ "os"
+ "path/filepath"
+ "sync"
+ "time"
+)
+
+const totalFile = 3000
+const contentLength = 5000
+
+var tempPath = filepath.Join(os.Getenv("TEMP"), "chapter-A.61-pipeline-cancellation-context")
+```
+
+#### ◉ Definisi struct `FileInfo`
+
+```go
+type FileInfo struct {
+ Index int
+ FileName string
+ WorkerIndex int
+ Err error
+}
+```
+
+#### ◉ Fungsi `main()`
+
+```go
+func main() {
+ log.Println("start")
+ start := time.Now()
+
+ generateFiles()
+
+ duration := time.Since(start)
+ log.Println("done in", duration.Seconds(), "seconds")
+}
+```
+
+#### ◉ Fungsi `randomString()`
+
+```go
+func randomString(length int) string {
+ randomizer := rand.New(rand.NewSource(time.Now().Unix()))
+ letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+ b := make([]rune, length)
+ for i := range b {
+ s := randomizer.Intn(len(letters))
+ b[i] = letters[s]
+ }
+
+ return string(b)
+}
+```
+
+#### ◉ Fungsi `generateFiles()`
+
+```go
+func generateFiles() {
+ os.RemoveAll(tempPath)
+ os.MkdirAll(tempPath, os.ModePerm)
+
+ // pipeline 1: job distribution
+ chanFileIndex := generateFileIndexes()
+
+ // pipeline 2: the main logic (creating files)
+ createFilesWorker := 100
+ chanFileResult := createFiles(chanFileIndex, createFilesWorker)
+
+ // track and print output
+ counterTotal := 0
+ counterSuccess := 0
+ for fileResult := range chanFileResult {
+ if fileResult.Err != nil {
+ log.Printf("error creating file %s. stack trace: %s", fileResult.FileName, fileResult.Err)
+ } else {
+ counterSuccess++
+ }
+ counterTotal++
+ }
+
+ log.Printf("%d/%d of total files created", counterSuccess, counterTotal)
+}
+```
+#### ◉ Fungsi `generateFileIndexes()`
+
+```go
+func generateFileIndexes() <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ go func() {
+ for i := 0; i < totalFile; i++ {
+ chanOut <- FileInfo{
+ Index: i,
+ FileName: fmt.Sprintf("file-%d.txt", i),
+ }
+ }
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+#### ◉ Fungsi `createFiles()`
+
+```go
+func createFiles(chanIn <-chan FileInfo, numberOfWorkers int) <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ wg := new(sync.WaitGroup)
+ wg.Add(numberOfWorkers)
+
+ go func() {
+ for workerIndex := 0; workerIndex < numberOfWorkers; workerIndex++ {
+ go func(workerIndex int) {
+ for job := range chanIn {
+ filePath := filepath.Join(tempPath, job.FileName)
+ content := randomString(contentLength)
+ err := os.WriteFile(filePath, []byte(content), os.ModePerm)
+
+ log.Println("worker", workerIndex, "working on", job.FileName, "file generation")
+
+ chanOut <- FileInfo{
+ FileName: job.FileName,
+ WorkerIndex: workerIndex,
+ Err: err,
+ }
+ }
+
+ wg.Done()
+ }(workerIndex)
+ }
+ }()
+
+ go func() {
+ wg.Wait()
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Hasil eksekusi program:
+
+![Concurrent without cancellation](images/A_pipeline_context_cancellation_1_concurrent_without_cancellation.png)
+
+## A.64.3. Program Generate Dummy File *Concurrently* dan Mekanisme *Cancellation*
+
+Ok, sekarang kita akan refactor kode tersebut, kita tambahkan mekanisme *cancellation* menggunakan `context.Context` API. Silakan duplikasi file program, lalu ikuti petunjuk berikut.
+
+#### ◉ Import package `context`
+
+Tambahkan package `context` dalam block import packages.
+
+```go
+import (
+ "context"
+
+ // ...
+)
+```
+
+#### ◉ Tambahkan definisi konstanta timeout
+
+Di sini saya tentukan timeout adalah 3 detik. Nantinya kita akan modifikasi angka timeout untuk keperluan testing.
+
+```go
+const timeoutDuration = 3 * time.Second
+```
+
+#### ◉ Penerapan context di fungsi `main()`
+
+Pada fungsi main, lakukan sedikit perubahan. Yang sebelumnya ada statement berikut:
+
+```go
+generateFiles()
+```
+
+Ubah menjadi berikut:
+
+```go
+ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
+defer cancel()
+generateFilesWithContext(ctx)
+```
+
+Fungsi `generateFilesWithContext()` merupakan fungsi yang sama persis dengan `generateFiles()` (yang tentunya akan kita buat). Perbedaannya adalah hanya pada fungsi baru ini ada satu argument baru yaitu data `context.Context`.
+
+Ini merupakan salah satu idiomatic Go untuk cara penulisan fungsi yang *cancellable*. Umumnya akan ada fungsi tanpa context dan fungsi yang ada context-nya. Contohnya seperti berikut:
+
+```go
+generateFiles()
+generateFilesWithContext(ctx)
+```
+
+Dimisalkan lagi jika argument context adalah wajib pada sebuah fungsi, maka cukup gunakan 1 fungsi saja, yang ada `WithContext()`-nya dihapus, tapi satu fungsi yang ada ditambahkan context. Contohnya:
+
+```go
+generateFiles(ctx)
+```
+
+Pada contoh ini kita akan siapkan dua fungsi, yang ada context-nya dan yang tidak.
+
+Ok lanjut ke pembahasan. Fungsi `context.WithTimeout` digunakan untuk menambahkan timeout pada sebuah context. Parameter pertama fungsi ini adalah objek context juga. Pada contoh di atas, karena sebelumnya belum ada objek context, maka kita buat objek context baru lewat `context.Background()`.
+
+Cara pembuatan object context sendiri sebenarnya ada 3:
+
+1. Menggunakan fungsi `context.Background()`. Fungsi tersebut menghasilkan objek context yang data di dalamnya adalah kosong dan tidak memiliki deadline. Context ini biasanya digunakan untuk inisialisasi object context baru yang nanti akan di-chain dengan fungsi `context.With...`.
+2. Menggunakan fungsi `context.TODO()`. Fungsi ini menghasilkan objek context baru seperti `context.Background()`. Context buatan fungsi TODO ini biasanya digunakan dalam situasi ketika belum jelas nantinya harus menggunakan jenis context apa (apakah dengan timeout, apakah dengan cancel).
+3. Menggunakan fungsi `context.With...`. Fungsi ini sebenarnya bukan digunakan untuk inisialisasi objek konteks baru, tapi digunakan untuk menambahkan informasi tertentu pada *copied* context yang disisipkan di parameter pertama pemanggilan fungsi. Ada 3 buah fungsi `context.With...` yang bisa digunakan, yaitu:
+
+ - Fungsi `context.WithCancel(ctx) (ctx, cancel)`. Fungsi ini digunakan untuk menambahkan fasilitas *cancellable* pada context yang disisipkan sebagai parameter pertama pemanggilan fungsi. Lewat nilai balik kedua, yaitu `cancel` yang tipenya `context.CancelFunc`, kita bisa secara paksa meng-*cancel* context ini.
+ - Fungsi `context.WithDeadline(ctx, time.Time) (ctx, cancel)`. Fungsi ini juga menambahkan fitur *cancellable* pada context, tapi selain itu juga menambahkan informasi deadline yang mana jika waktu sekarang sudah melebihi deadline yang sudah ditentukan maka context otomatis di-cancel secara paksa.
+ - Fungsi `context.WithTimeout(ctx, time.Duration) (ctx, cancel)`. Fungsi ini sama seperti `context.WithDeadline()`, bedanya pada parameter kedua argument bertipe durasi (bukan objek `time.Time`).
+
+Kesamaan dari ketiga fungsi `context.With...` adalah sama-sama menambahkan fasilitas *cancellable* yang bisa dieksekusi lewat nilai balik kedua fungsi tersebut (yang tipenya `context.CancelFunc`).
+
+Jadi pada contoh yang kita tulis di atas, kurang lebih yang akan dilakukan adalah:
+
+* Kira buat object context baru lewat `context.Background()`.
+* Objek context tersebut ditambahkan fasilitas *cancellable* di dalamnya, dan juga auto cancel ketika timeout menggunakan `context.WithTimeout()`, dengan durasi timeout adalah `timeoutDuration`.
+* Fungsi `generateFilesWithContext()` dipanggil dengan disisipkan object context.
+* Callback `context.CancelFunc` dipanggil secara deferred. Ini merupakan idiomatic Go dalam penerapan context. Meskipun context sudah punya timeout atau deadline dan kita tidak perlu meng-*cancel* context secara manual, sangat dianjurkan untuk tetap memanggil callback `cancel()` tersebut secara deferred.
+
+#### ◉ Modifikasi fungsi `generateFiles()`
+
+Isi dari fungsi `generateFiles()` kita ubah menjadi pemanggilan fungsi `generateFilesWithContext()` dengan parameter context kosong.
+
+```go
+func generateFiles() {
+ generateFilesWithContext(context.Background())
+}
+```
+
+Pada fungsi `generateFilesWithContext()` sendiri, isinya adalah isi `generateFiles()` sebelumnya tapi ditambahkan beberapa hal. Silakan tulis dulu kode berikut.
+
+```go
+func generateFilesWithContext(ctx context.Context) {
+ os.RemoveAll(tempPath)
+ os.MkdirAll(tempPath, os.ModePerm)
+
+ done := make(chan int)
+
+ go func() {
+ // pipeline 1: job distribution
+ chanFileIndex := generateFileIndexes(ctx)
+
+ // pipeline 2: the main logic (creating files)
+ createFilesWorker := 100
+ chanFileResult := createFiles(ctx, chanFileIndex, createFilesWorker)
+
+ // track and print output
+ counterSuccess := 0
+ for fileResult := range chanFileResult {
+ if fileResult.Err != nil {
+ log.Printf("error creating file %s. stack trace: %s", fileResult.FileName, fileResult.Err)
+ } else {
+ counterSuccess++
+ }
+ }
+
+ // notify that the process is complete
+ done <- counterSuccess
+ }()
+
+ select {
+ case <-ctx.Done():
+ log.Printf("generation process stopped. %s", ctx.Err())
+ case counterSuccess := <-done:
+ log.Printf("%d/%d of total files created", counterSuccess, totalFile)
+ }
+}
+```
+
+Penambahan yang dimaksud adalah, statement pipelines dibungkus dengan sebuah goroutine IIFE, yang di akhir fungsi kita kirim informasi jumlah file yang berhasil di-generate (`counterSuccess`) ke sebuah channel bernama `done`.
+
+Channel `done` ini kita gunakan sebagai indikator bahwa proses pipeline sudah selesai secara keseluruhan.
+
+Selain goroutine, di akhir fungsi `generateFilesWithContext()` sendiri ditambahkan *channel selection* dengan isi dua buah cases.
+
+Case pertama adalah ketika channel done pada context `ctx` menerima data. Cara penggunaannya seperti ini `<-ctx.Done()`. Ketika channel done milik context ini menerima data, berarti context telah di-cancel secara paksa. Cancel-nya bisa karena memang context melebihi timeout yang sudah ditentukan, atau di-cancel secara eksplisit lewat pemanggilan callback `context.CancelFunc`. Untuk mengetahui alasan cancel bisa dengan cara mengakses method error milik contex, yaitu: `ctx.Err()`.
+
+Jadi pada contoh di atas, ketika context timeout atau di-cancel secara eksplisit (via callback `cancel`), maka case pertama akan terpenuhi dan message ditampilkan.
+
+Untuk case kedua akan terpenuhi ketika proses pipeline sudah selesai secara keseluruhan. Bisa dilihat di akhir goroutine, di situ channel `done` dikirimi informasi `counterSuccess`. Ketika ini terjadi maka kondisi case kedua terpenuhi, lalu ditampilkan informasi file yang sudah sukses dibuat.
+
+Nah jadi lewat seleksi kondisi 2 case di atas, kita bisa dengan mudah mengidentifikasi apakah proses selesai sepenuhnya, ataukah cancelled ditengah jalan karena timeout ataupun karena di-cancel secara eksplisit.
+
+Selain beberapa hal yang sudah saya sampaikan, ada *minor changes* lainnya, yaitu pada pemanggilan fungsi `generateFileIndexes()` dan `createFiles()` ditambahkan argument context.
+
+#### ◉ Penambahan context pada fungsi `generateFiles()`
+
+Kenapa ini perlu? karena **meski eksekusi fungsi `generateFilesWithContext()` otomatis di stop ketika cancelled, proses di dalamnya akan tetap berjalan jika tidak di-*handle* dengan baik *cancellation*-nya.**
+
+Maka dari itu kita perlu memodifikasi, memastikan bahwa cancellation juga diberlakukan dalam level sub proses.
+
+Silakan tulis kode berikut pada fungsi `generateFileIndexes()`.
+
+```go
+func generateFileIndexes(ctx context.Context) <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ go func() {
+ for i := 0; i < totalFile; i++ {
+ select {
+ case <-ctx.Done():
+ break
+ default:
+ chanOut <- FileInfo{
+ Index: i,
+ FileName: fmt.Sprintf("file-%d.txt", i),
+ }
+ }
+ }
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Dibanding sebelumnya, perbedaannya adalah ada *channel selection*. Jadi di bagian pengiriman jobs, ketika sebelum semua jobs dikirim tapi ada notif untuk cancel maka kita akan skip pengiriman *remainin* jobs secara paksa.
+
+* Jika ada notif cancel paksa, maka case pertama akan terpenuhi, dan perulangan di-`break`.
+* Selebihnya, pengiriman jobs akan berlangsung seperti normalnya.
+
+#### ◉ Penambahan context pada fungsi `createFiles()`
+
+Hal yang sama (cancel di level sub prosees) juga perlu diterapkan pada `createFiles()`, karena jika tidak, maka proses pembuatan file akan tetap berjalan sesuai dengan jumlah jobs yang dikirim meskipun sudah di-cancel secara paksa.
+
+Sebenarnya penambahan cancellation pada fungsi `generateFiles()` sudah cukup, karena ketika cancelled maka sisa jobs tidak akan dikirim. Tapi pada contoh ini penulis ingin ketika cancelled, maka tidak hanya pengiriman jobs tapi eksekusi jobs juga di-stop secara paksa (meski mungkin masih ada sebagian jobs yang masih dalam antrian).
+
+Silakan tulis kode berikut.
+
+```go
+func createFiles(ctx context.Context, chanIn <-chan FileInfo, numberOfWorkers int) <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ wg := new(sync.WaitGroup)
+ wg.Add(numberOfWorkers)
+
+ go func() {
+ for workerIndex := 0; workerIndex < numberOfWorkers; workerIndex++ {
+ go func(workerIndex int) {
+ for job := range chanIn {
+ select {
+ case <-ctx.Done():
+ break
+ default:
+ filePath := filepath.Join(tempPath, job.FileName)
+ content := randomString(contentLength)
+ err := os.WriteFile(filePath, []byte(content), os.ModePerm)
+
+ log.Println("worker", workerIndex, "working on", job.FileName, "file generation")
+
+ chanOut <- FileInfo{
+ FileName: job.FileName,
+ WorkerIndex: workerIndex,
+ Err: err,
+ }
+ }
+ }
+
+ wg.Done()
+ }(workerIndex)
+ }
+ }()
+
+ go func() {
+ wg.Wait()
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Penambahannya juga sama seperti fungsi-fungsi yang lain, yaitu dengan menambahkan *channel selection*. Ketika ada notifikasi cancel maka perulangan jobs di break. Selebihnya maka harus berjalan seperti normalnya.
+
+## A.64.4. Test Eksekusi Program
+
+Jalankan program ke-dua, lihat hasilnya. Karena sebelumnya kita sudah set durasi timeout adalah **3 detik**, maka jika proses belum selesai sebelum durasi tersebut akan di-cancel secara paksa.
+
+![Context timeout](images/A_pipeline_context_cancellation_2_context_timeout.png)
+
+Cukup mudah bukan?
+
+Silakan coba modifikasi durasinya dengan nilai lebih besar, misalnya **15 detik**, lalu coba jalankan.
+
+![Concurrent without cancellation](images/A_pipeline_context_cancellation_1_concurrent_without_cancellation.png)
+
+Bisa dilihat, di gambar di atas, jika program selesai sebelum **15 detik** maka aman.
+
+## A.64.5. Cancel Context Secara Paksa (Tanpa Timeout)
+
+Coba lakukan modifikasi sedikit pada bagian pemanggilan generate files, dari:
+
+```go
+ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
+defer cancel()
+generateFilesWithContext(ctx)
+```
+
+Ke:
+
+```go
+ctx, cancel := context.WithCancel(context.Background())
+defer cancel()
+time.AfterFunc(timeoutDuration, cancel)
+generateFilesWithContext(ctx)
+```
+
+Lalu coba jalankan, maka hasilnya adalah tetap sama. Jika eksekusi program melebihi context maka akan di-cancel secara paksa, selebihnya aman.
+
+Perbedannya ada pada penerapan *cancellation*-nya. Pada contoh ini kita tidak menggunakan timeout, melainkan menggunakan *explicit cancel* dengan mensimulasi timeout menggunakan `time.AfterFunc()`.
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-pointer.md b/en/content-en/A-pointer.md
new file mode 100644
index 000000000..1d782695a
--- /dev/null
+++ b/en/content-en/A-pointer.md
@@ -0,0 +1,110 @@
+# A.23. Pointer
+
+Pointer adalah *reference* atau alamat memori. Variabel pointer berarti variabel yang berisi alamat memori suatu nilai. Sebagai contoh sebuah variabel bertipe integer memiliki nilai **4**, maka yang dimaksud pointer adalah **alamat memori di mana nilai 4 disimpan**, bukan nilai 4 itu sendiri.
+
+Variabel-variabel yang memiliki *reference* atau alamat pointer yang sama, saling berhubungan satu sama lain dan nilainya pasti sama. Ketika ada perubahan nilai, maka akan memberikan efek kepada variabel lain (yang referensi-nya sama) yaitu nilainya ikut berubah.
+
+## A.23.1. Penerapan Pointer
+
+Variabel bertipe pointer ditandai dengan adanya tanda **asterisk** (`*`) tepat sebelum penulisan tipe data ketika deklarasi.
+
+```go
+var number *int
+var name *string
+```
+
+Nilai default variabel pointer adalah `nil` (kosong). Variabel pointer tidak bisa menampung nilai yang bukan pointer, dan sebaliknya variabel biasa tidak bisa menampung nilai pointer.
+
+Ada dua hal penting yang perlu diketahui mengenai pointer:
+
+- Variabel biasa bisa diambil nilai pointernya, caranya dengan menambahkan tanda **ampersand** (`&`) tepat sebelum nama variabel. Metode ini disebut dengan **referencing**.
+- Dan sebaliknya, nilai asli variabel pointer juga bisa diambil, dengan cara menambahkan tanda **asterisk** (`*`) tepat sebelum nama variabel. Metode ini disebut dengan **dereferencing**.
+
+OK, langsung saja kita praktekan.
+
+```go
+var numberA int = 4
+var numberB *int = &numberA
+
+fmt.Println("numberA (value) :", numberA) // 4
+fmt.Println("numberA (address) :", &numberA) // 0xc20800a220
+
+fmt.Println("numberB (value) :", *numberB) // 4
+fmt.Println("numberB (address) :", numberB) // 0xc20800a220
+```
+
+Variabel `numberB` dideklarasikan bertipe pointer `int` dengan nilai awal adalah referensi variabel `numberA` (bisa dilihat pada kode `&numberA`). Dengan ini, variabel `numberA` dan `numberB` menampung data dengan referensi alamat memori yang sama.
+
+![Penggunaan variabel pointer](images/A_pointer_1_pointer.png)
+
+Variabel pointer jika di-print akan menghasilkan string alamat memori (dalam notasi heksadesimal), contohnya seperti `numberB` yang diprint menghasilkan `0xc20800a220`.
+
+Nilai asli sebuah variabel pointer bisa didapatkan dengan cara di-dereference terlebih dahulu (bisa dilihat pada kode `*numberB`).
+
+## A.23.2. Efek Perubahan Nilai Pointer
+
+Ketika salah satu variabel pointer di ubah nilainya, sedang ada variabel lain yang memiliki referensi memori yang sama, maka nilai variabel lain tersebut juga akan berubah.
+
+```go
+var numberA int = 4
+var numberB *int = &numberA
+
+fmt.Println("numberA (value) :", numberA)
+fmt.Println("numberA (address) :", &numberA)
+fmt.Println("numberB (value) :", *numberB)
+fmt.Println("numberB (address) :", numberB)
+
+fmt.Println("")
+
+numberA = 5
+
+fmt.Println("numberA (value) :", numberA)
+fmt.Println("numberA (address) :", &numberA)
+fmt.Println("numberB (value) :", *numberB)
+fmt.Println("numberB (address) :", numberB)
+```
+
+Variabel `numberA` dan `numberB` memiliki referensi memori yang sama. Perubahan pada salah satu nilai variabel tersebut akan memberikan efek pada variabel lainnya. Pada contoh di atas, `numberA` nilainya di ubah menjadi `5`. membuat nilai asli variabel `numberB` ikut berubah menjadi `5`.
+
+![Variabel pointer diubah nilainya](images/A_pointer_2_pointer_change.png)
+
+## A.23.3. Parameter Pointer
+
+Parameter bisa juga dirancang sebagai pointer. Cara penerapannya kurang lebih sama, dengan cara mendeklarasikan parameter sebagai pointer.
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+ var number = 4
+ fmt.Println("before :", number) // 4
+
+ change(&number, 10)
+ fmt.Println("after :", number) // 10
+}
+
+func change(original *int, value int) {
+ *original = value
+}
+```
+
+Fungsi `change()` memiliki 2 parameter, yaitu `original` yang tipenya adalah pointer `int`, dan `value` yang bertipe `int`. Di dalam fungsi tersebut nilai asli parameter pointer `original` diubah.
+
+Fungsi `change()` kemudian diimplementasikan di `main`. Variabel `number` yang nilai awalnya adalah `4` diambil referensi-nya lalu digunakan sebagai parameter pada pemanggilan fungsi `change()`.
+
+Nilai variabel `number` berubah menjadi `10` karena perubahan yang terjadi di dalam fungsi `change` adalah pada variabel pointer.
+
+![Fungsi berparameter pointer](images/A_pointer_3_pointer_parameter.png)
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-properti-public-dan-private.md b/en/content-en/A-properti-public-dan-private.md
new file mode 100644
index 000000000..600bc6063
--- /dev/null
+++ b/en/content-en/A-properti-public-dan-private.md
@@ -0,0 +1,346 @@
+# A.26. Properti Public dan Private (Exported vs Unexported)
+
+Chapter ini membahas tentang *property modifier* public dan private yang ada di pemrograman Go. Peran dari *property modifier* adalah sebagai penentu kapan suatu struct, fungsi, atau method bisa diakses dari package lain dan kapan tidak.
+
+Di Go sebenarnya tidak ada istilah *public modifier* dan *private modifier*. Yang ada adalah **exported** (yang kalau di bahasa lain ekuivalen dengan *public modifier*), dan **unexported** untuk *private modifier*.
+
+## A.26.1. Intro
+
+Intro ini ditulis agar pembaca tau ekspektasi chapter ini sebenarnya apa.
+
+Pembahasan kali ini memiliki beberapa perbedaan dibanding chapter lainnya. Jika pembaca mengikuti pembelajaran di chapter ini secara berurutan, dan benar-benar membaca penjelasan serta pembahasan yang sudah tertulis, maka nantinya **pasti menemui 3 buah error**.
+
+Di setiap error tersebut, sebenarnya sudah terlampir informasi berikut:
+
+1. Screenshot error
+2. Penjelasan penyebab terjadinya error
+3. Cara resolve atau mengatasi error
+
+Penulis menerima cukup banyak email dari pembaca mengenai beberapa error di chapter ini. Kesimpulan penulis:
+
+**Pembaca bingung karena mendapati error, dan tidak tau apa yang harus dilakukan. Padahal sudah ada keterangan yang cukup jelas bahwa error tersebut pasti muncul, dan sudah disediakan juga penjelasan beserta cara mengatasinya. Ini kemungkinan besar disebabkan karena pembaca hanya copy-paste source code dari chapter ini, tanpa benar-benar membaca penjelasan yang padahal sudah ditulis cukup detail**.
+
+> Saya sangat anjurkan untuk **tidak hanya *copas* source code, usahakan dibaca! dipelajari! dan dipahami!** *No hard feeling* ya 👌😁
+
+## A.26.2. Exported Package dan Unexported Package
+
+Pengembangan aplikasi dalam *real development* pasti membutuhkan banyak sekali file program. Tidak mungkin dalam satu buah project semua source code di tulis di hanya 1 package `main` saja, umumnya akan dipisah ke beberapa package berbeda yang masing-masing punya tugas sendiri yang berbeda satu sama lain.
+
+Project folder selain berisikan file-file `.go` juga bisa berisikan sub-folder lainnya. Di Go, setiap folder atau sub-folder adalah satu package, file-file yang ada di dalam sebuah folder package-nya harus sama. Dan package pada file-file tersebut harus berbeda dengan package pada file-file lainnya yang berada pada folder berbeda.
+
+> Sederhananya, 1 folder adalah 1 package.
+
+Dalam sebuah package, biasanya kita menulis sangat banyak komponen, bisa berupa fungsi, struct, variabel, atau lainnya. Komponen-komponen tersebut bisa secara leluasa dipergunakan di kode yang masih berada di dalam package yang sama. Contohnya seperti program yang telah kita praktekan pada chapter sebelum-sebelumnya, dalam package `main` ada banyak yang di-*define*: fungsi, variabel, closure, struct, dan lainnya; semuanya bisa langsung dimanfaatkan.
+
+Jika dalam satu program terdapat lebih dari 1 package, atau ada package lain selain `main`, maka komponen dalam package lain tersebut tidak bisa diakses secara bebas dari file yang package-nya `main`, perlu dilihat dulu level akses yang sudah ditentukan apa.
+
+Go mengenal 2 jenis level akses atau hak akses:
+
+ - Hak akses **Exported** atau **public**. Menandakan bahwa komponen boleh untuk diakses dari package lain
+ - Hak akses **Unexported** atau **private**. Berarti komponen hanya bisa diakses dari file yang package-nya sama, bisa dalam satu file yang sama atau di file berbeda yang masih 1 folder yang package-nya pastinya sama.
+
+Cara menentukan level akses atau modifier di Go sangat mudah, yaitu dengan mengacu ke **character case** huruf pertama nama fungsi, struct, variabel, atau lainnya. Ketika namanya diawali dengan huruf kapital maka level aksesnya adalah *exported* (atau *public*). Dan sebaliknya, jika diawali huruf kecil, berarti *unexported* (atau private).
+
+## A.26.3. Penggunaan Package, Import, Dan Hak Akses *Exported* dan *Unexported*
+
+Agar lebih mudah dipahami, maka langsung saja kita praktekan.
+
+Pertama buat folder proyek baru bernama `belajar-golang-level-akses`, gunakan nama folder tersebut sebagai nama project. Kemudian buat file baru bernama `main.go` di dalamnya, lalu tentukan nama package file tersebut sebagai **main**.
+
+Kemudian, buat sub-folder baru bernama `library` di dalam folder `belajar-golang-level-akses`. Di dalam folder `library`, buat file baru `library.go`, set nama package-nya **library**.
+
+![Struktur folder dan file](images/A_properti_public_private_1_folder_structure.png)
+
+Buka file `library.go` lalu isi dengan kode berikut.
+
+```go
+package library
+
+import "fmt"
+
+func SayHello() {
+ fmt.Println("hello")
+}
+
+func introduce(name string) {
+ fmt.Println("nama saya", name)
+}
+```
+
+File `library.go` yang telah dibuat ditentukan nama package-nya adalah `library` (sesuai dengan nama folder), isinya dua buah fungsi `SayHello()` dan `introduce()`.
+
+ - Fungsi `SayHello()`, level aksesnya adalah publik, ditandai dengan nama fungsi diawali huruf besar.
+ - Fungsi `introduce()` dengan level akses private, ditandai oleh huruf kecil di awal nama fungsi.
+
+Selanjutnya kita siapkan beberapa kode tambahan untuk keperluan testing apakah memang fungsi yang ber-modifier private dalam package `library` tidak bisa diakses dari package lain.
+
+Buka file `main.go`, lalu tulis kode berikut.
+
+```go
+package main
+
+import "belajar-golang-level-akses/library"
+
+func main() {
+ library.SayHello()
+ library.introduce("ethan")
+}
+```
+
+Bisa dilihat bahwa package `library` yang telah dibuat tadi, di-import ke dalam package `main`.
+
+Di awal telah ditentukan bahwa nama project (yang juga merupakan nama folder) adalah `belajar-golang-level-akses`, maka untuk import package lain yang merupakan subfolder, pada syntax import harus dituliskan lengkap, contoh: `belajar-golang-level-akses/library`.
+
+> Penanda root folder adalah tempat di mana file `go.mod` berada
+
+Kembali ke pembahasan kode, silakan perhatikan kode berikut:
+
+```go
+library.SayHello()
+library.introduce("ethan")
+```
+
+Cara pemanggilan fungsi yang berada dalam package lain adalah dengan menuliskan nama package target diikut dengan nama fungsi menggunakan *dot notation* atau tanda titik, seperti `library.SayHello()` atau `library.introduce("ethan")`.
+
+OK, sekarang coba jalankan kode yang sudah disiapkan di atas, hasilnya error.
+
+![Error saat menjalankan program](images/A_properti_public_private_2_error.png)
+
+Error di atas disebabkan oleh fungsi `introduce()` yang berada dalam package `library` memiliki level akses *unexported* (atau *private*), maka fungsi ini tidak bisa diakses dari package lain (pada kasus ini package `main`). Solusi agar bisa diakses adalah dengan mengubah level aksesnya ke *exported* (atau *public*), atau bisa dengan mengubah cara pemanggilannya.
+
+Ok, sekarang kita akan coba cara ke-2, yaitu mengubah cara pemanggilannya. Tambahkan parameter `name` pada fungsi `SayHello()`, lalu masih di dalam fungsi tersebut panggil fungsi `introduce()` dan gunakan parameter `name`-nya.
+
+```go
+func SayHello(name string) {
+ fmt.Println("hello")
+ introduce(name)
+}
+```
+
+Di fungsi `main()`, cukup panggil fungsi `library.SayHello()` saja. Isi parameternya dengan nilai string apapun, misalnya `"ethan"`.
+
+```go
+func main() {
+ library.SayHello("ethan")
+}
+```
+
+Coba jalankan lagi.
+
+![Contoh penerapan pemanggilan fungsi dari package berbeda](images/A_properti_public_private_2_success.png)
+
+## A.26.4. Penggunaan Hak Akses *Exported* dan *Unexported* pada Struct dan Propertinya
+
+Level akses *exported* (atau public) dan *unexported* (atau private) juga bisa diterapkan di fungsi, struct, method, maupun property variabel. Cara penggunaannya sama seperti pada pembahasan sebelumnya, yaitu dengan menentukan **character case** huruf pertama nama komponen, apakah huruf besar atau kecil.
+
+Ok, lanjut ke praktek berikutnya. Hapus isi file `library.go`, lalu buat struct baru dengan nama `student` di dalamnya.
+
+```go
+package library
+
+type student struct {
+ Name string
+ grade int
+}
+```
+
+Buat contoh sederhana penerapan struct di atas pada file `main.go`.
+
+```go
+package main
+
+import "belajar-golang-level-akses/library"
+import "fmt"
+
+func main() {
+ var s1 = library.student{"ethan", 21}
+ fmt.Println("name ", s1.Name)
+ fmt.Println("grade", s1.grade)
+}
+```
+
+Setelah itu jalankan program.
+
+![Error saat menjalankan program](images/A_properti_public_private_3_error.png)
+
+Error muncul lagi, kali ini penyebabnya adalah karena struct `student` level aksesnya adalah *unexported*. Ubah ke bentuk *exported* dengan cara mengubah huruf awalnya menjadi huruf besar, kemudian jalankan ulang.
+
+```go
+// file library/library.go
+type Student struct {
+ Name string
+ grade int
+}
+
+// file main.go
+var s1 = library.Student{"ethan", 21}
+fmt.Println("name ", s1.Name)
+fmt.Println("grade", s1.grade)
+```
+
+Output program:
+
+![Error lain muncul saat menjalankan program](images/A_properti_public_private_4_error.png)
+
+Error masih tetap muncul, tapi kali ini berbeda. Error yang baru ini disebabkan karena salah satu properti dari struct `Student` adalah *unexported*. Properti yg dimaksud adalah `grade`. Solusinya ubah ke bentuk *exported*, lalu jalankan ulang program.
+
+```go
+// pada library/library.go
+type Student struct {
+ Name string
+ Grade int
+}
+
+// pada main.go
+var s1 = library.Student{"ethan", 21}
+fmt.Println("name ", s1.Name)
+fmt.Println("grade", s1.Grade)
+```
+
+Dari contoh program di atas, bisa disimpulkan bahwa untuk menggunakan `struct` yang berada di package lain, selain nama struct-nya harus berbentuk *exported*, properti yang diakses juga harus *exported* juga.
+
+![Contoh penerapan pemanfaatan struct dan propertynya dari package berbeda](images/A_properti_public_private_4_success.png)
+
+## A.26.5. Import Dengan Prefix Tanda Titik
+
+Seperti yang kita tahu, untuk mengakses fungsi/struct/variabel yg berada di package lain, nama package nya perlu ditulis, contohnya seperti pada penggunaan `library.Student` dan `fmt.Println()`.
+
+Di Go, komponen yang berada di package lain yang di-import bisa dijadikan se-level dengan komponen package peng-import, caranya dengan menambahkan tanda titik (`.`) setelah penulisan keyword `import`. Maksud dari se-level di sini adalah, semua property di package lain yg di-import bisa diakses tanpa perlu menuliskan nama package, seolah-olah property tersebut berada di file yang sama. Contoh:
+
+```go
+import (
+ . "belajar-golang-level-akses/library"
+ "fmt"
+)
+
+func main() {
+ var s1 = Student{"ethan", 21}
+ fmt.Println("name ", s1.Name)
+ fmt.Println("grade", s1.Grade)
+}
+```
+
+Pada kode di atas package `library` di-import menggunakan tanda titik. Dengan itu, pemanggilan struct `Student` tidak perlu dengan menuliskan nama package nya.
+
+> PERINGATAN!
+>
+> Penggunaan tanda titik pada saat import package bisa menyebabkan kode menjadi ambigu, karena alasan tersebut teknik import ini kurang direkomendasikan.
+
+## A.26.6. Pemanfaatan Alias Saat Import Package
+
+Fungsi yang berada di package lain bisa diakses dengan cara menuliskan nama-package diikuti nama fungsi-nya, contohnya seperti `fmt.Println()`. Package yang sudah di-import tersebut bisa diubah nama pemanggilannya dengan menerapkan teknik alias yang dituliskan saat import. Contohnya bisa dilihat pada kode berikut.
+
+```go
+import (
+ f "fmt"
+)
+
+func main() {
+ f.Println("Hello World!")
+}
+```
+
+Pada kode di-atas, package `fmt` di tentukan aliasnya adalah `f`, untuk mengakses `Println()` cukup dengan `f.Println()`.
+
+## A.26.7. Mengakses Property Dalam File Yang Package-nya Sama
+
+Jika property yang ingin di akses masih dalam satu package tapi file-nya berbeda, cara mengaksesnya bisa langsung dengan memanggil namanya seperti biasa. Hanya saja saat eksekusi, file-file lain yang yang nama package-nya sama tersebut harus ikut disertakan dalam command `go run`.
+
+Langsung saja kita praktekan, buat file baru dalam folder `belajar-golang-level-akses` dengan nama `partial.go`.
+
+![File
+ partial.go
disiapkan setara dengan file main.go
](images/A_properti_public_private_5_structure.png)
+
+Tulis kode berikut pada file `partial.go`. File tersebut kita tentukan nama package-nya adalah `main` (sama dengan nama package file `main.go`).
+
+```go
+package main
+
+import "fmt"
+
+func sayHello(name string) {
+ fmt.Println("halo", name)
+}
+```
+
+Hapus semua isi file `main.go`, ganti dengan kode berikut.
+
+```go
+package main
+
+func main() {
+ sayHello("ethan")
+}
+```
+
+Sekarang terdapat 2 file berbeda (`main.go` dan `partial.go`) dengan package adalah sama, `main`. Pada saat `go build` atau `go run`, semua file dengan nama package `main` harus dituliskan sebagai argumen command.
+
+```
+go run main.go partial.go
+```
+
+Fungsi `sayHello` pada file `partial.go` bisa dikenali meski level aksesnya adalah *unexported*. Hal ini karena kedua file tersebut (`main.go` dan `partial.go`) memiliki nama package yang sama.
+
+> Alternatif yang lebih praktis untuk menjalankan program bisa dengan perintah `go run *.go`, dengan cara ini maka tidak perlu menuliskan nama file-nya satu per satu.
+
+![Pemanggilan fungsi unexported dari dalam package yang sama](images/A_properti_public_private_6_multi_main.png)
+
+## A.26.8. Penjelasan Tambahan
+
+#### ◉ Fungsi `init()`
+
+Selain fungsi `main()`, terdapat juga fungsi spesial yaitu `init()`. Fungsi ini otomatis dipanggil saat pertama kali program dijalankan. Jika fungsi ini ditulis di package-package lain yang di-import di `main`, maka semua fungsi `init()` tersebut dipanggil lebih dulu sebelum fungsi `main()`.
+
+Agar lebih jelas mari praktekan. Buka file `library.go`, hapus isinya lalu isi dengan kode berikut.
+
+```go
+package library
+
+import "fmt"
+
+var Student = struct {
+ Name string
+ Grade int
+}{}
+
+func init() {
+ Student.Name = "John Wick"
+ Student.Grade = 2
+
+ fmt.Println("--> library/library.go imported")
+}
+```
+
+Pada package tersebut, variabel `Student` dibuat dengan isi anonymous struct. Dalam fungsi init, nilai `Name` dan `Grade` variabel di-set.
+
+Selanjutnya buka file `main.go`, isi dengan kode berikut.
+
+```go
+package main
+
+import "belajar-golang-level-akses/library"
+import "fmt"
+
+func main() {
+ fmt.Printf("Name : %s\n", library.Student.Name)
+ fmt.Printf("Grade : %d\n", library.Student.Grade)
+}
+```
+
+Package `library` di-import, dan variabel `Student` dikonsumsi pada fungsi `main()`. Sewaktu package di-import, fungsi `init()` yang berada di dalamnya langsung dieksekusi.
+
+Di dalam fungsi `init()`, property variabel objek `Student` diisi dan sebuah pesan ditampilkan ke console.
+
+![Fungsi init()
](images/A_properti_public_private_7_init.png)
+
+Di Go, setiap package masing-masing boleh memiliki fungsi `init()`. Fungsi tersebut hanya akan dieksekusi ketika package di-import dengan urutan eksekusinya adalah sesuai dengan package mana yg di-import terlebih dahulu. Dan kesemua fungsi `init()` dipanggil sebelum fungsi `main()`.
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-random.md b/en/content-en/A-random.md
new file mode 100644
index 000000000..6b6ec5692
--- /dev/null
+++ b/en/content-en/A-random.md
@@ -0,0 +1,126 @@
+# A.39. Random
+
+Pada chapter ini kita akan belajar pemanfaatan package `math/rand` untuk pembuatan data acak atau random.
+
+## A.39.1. Definisi
+
+Random Number Generator (RNG) merupakan sebuah perangkat (bisa software, bisa hardware) yang menghasilkan data deret/urutan angka yang sifatnya acak.
+
+RNG bisa berupa hardware yang murni bisa menghasilkan data angka acak, atau bisa saja sebuah [pseudo-random](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) yang menghasilkan deret angka-angka yang **terlihat acak** tetapi sebenarnya tidak benar-benar acak. Deret angka tersebut sebenarnya merupakan hasil kalkulasi algoritma deterministik dan probabilitas. Jadi untuk pseudo-random ini, asalkan kita tau *state*-nya maka kita akan bisa menebak hasil deret angka random-nya.
+
+Dalam per-randoman-duniawi terdapat istilah **seed** atau titik mulai (*starting point*). Seed ini digunakan oleh RNG untuk pembuatan angka random.
+
+Sedikit ilustrasi mengenai korelasi antara seed dengan RNG, agar lebih jelas.
+
+- Dimisalkan saya menggunakan seed yaitu angka `10`, maka ketika fungsi RNG dijalankan untuk pertama kalinya, output angka yang dihasilkan pasti `5221277731205826435`. Angka random tersebut pasti *fix* dan akan selalu menjadi hasil pertama ketika seed yang digunakan adalah angka `10`.
+- Misalnya lagi, fungsi RNG di-eksekusi untuk ke-dua kalinya, maka angka random kedua yang dihasilkan adalah pasti `3852159813000522384`. Dan seterusnya.
+- Misalkan lagi, fungsi RNG di-eksekusi lagi, maka angka random ketiga pasti `8532807521486154107`.
+- Jadi untuk seed angka `10`, akan selalu menghasilkan angka random ke-1: `5221277731205826435`, ke-2: `3852159813000522384`, ke-3 `8532807521486154107`. Meskipun fungsi random dijalankan di program yang berbeda, di waktu yang berbeda, di environment yang berbeda, jika seed adalah `10` maka deret angka random yang dihasilkan pasti sama seperti contoh di atas.
+
+## A.39.2. Package `math/rand`
+
+Go menyediakan package `math/rand`, isinya banyak sekali API untuk keperluan pembuatan angka random. Package ini mengadopsi **PRNG** atau *pseudo-random* number generator. Deret angka random yang dihasilkan sangat tergantung dengan angka **seed** yang digunakan.
+
+Cara penggunaan package ini sangat mudah, cukup import `math/rand`, lalu tentukan nilai seed, kemudian panggil fungsi untuk generate angka random-nya. Lebih jelasnya silakan cek contoh berikut.
+
+```go
+package main
+
+import (
+ "fmt"
+ "math/rand"
+)
+
+func main() {
+ randomizer := rand.New(rand.NewSource(10))
+ fmt.Println("random ke-1:", randomizer.Int()) // 5221277731205826435
+ fmt.Println("random ke-2:", randomizer.Int()) // 3852159813000522384
+ fmt.Println("random ke-3:", randomizer.Int()) // 8532807521486154107
+}
+```
+
+Fungsi `rand.New(rand.NewSource(x))` digunakan untuk membuat object randomizer sekaligus penentuan nilai seed-nya. Dari object randomizer, method `Int()` bisa diakses, gunanya untuk generate angka random dalam bentuk numerik bertipe `int`. Statement `randomizer.Int()` ini setiap kali dipanggil akan menghasilkan angka berbeda, tapi jika diperhatikan angka-angka tersebut tidak berubah, pasti hanya angka-angka itu saja yang muncul.
+
+Silakan coba sendiri kode di atas di local masing-masing, hasilnya pasti:
+
+- Angka random ke-1 akan selalu `5221277731205826435`
+- Angka random ke-2 akan selalu `3852159813000522384`
+- Angka random ke-3 akan selalu `8532807521486154107`
+
+Jika perlu jalankan program di atas beberapa kali, hasilnya selalu sama untuk angka random ke-1, ke-2, dan seterusnya.
+
+![Random Golang](images/A_random_1.png)
+
+## A.39.3. Unique Seed
+
+Lalu bagaimana cara agar angka yang dihasilkan selalu berbeda setiap kali dipanggil? Apakah harus set ulang seed-nya? Jangan, karena kalau seed di-set ulang maka urutan deret random akan berubah. Seed hanya perlu di set sekali di awal. Lalu apa solusi yang benar?
+
+Jadi begini, setiap kali `randomizer.Int()` dipanggil, hasilnya itu selalu berbeda, tapi sangat bisa diprediksi jika kita tau seed-nya. Ada cara agar angka random yang dihasilkan tidak berulang-ulang seperti yang ada di contoh, caranya yaitu dengan menggunakan angka unik *unique*/unik sebagai seed, contohnya seperti angka [unix nano](https://en.wikipedia.org/wiki/GNU_nano) yang didapat dari informasi waktu sekarang.
+
+Coba modifikasi program dengan kode berikut, lalu jalankan ulang. Jangan lupa meng-import package `time` ya.
+
+```go
+randomizer := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
+fmt.Println("random ke-1:", randomizer.Int())
+fmt.Println("random ke-2:", randomizer.Int())
+fmt.Println("random ke-3:", randomizer.Int())
+```
+
+![Random Golang with unix nano seed](images/A_random_2.png)
+
+Bisa dilihat, setiap program dieksekusi angka random nya selalu berbeda, hal ini karena seed yang digunakan pasti berbeda di setiap eksekusi program. Disitu seed yang digunakan adalah data numerik unix nano dari informasi waktu sekarang.
+
+## A.39.4. Random Tipe Data Numerik Lainnya
+
+Di dalam package `math/rand`, ada banyak fungsi untuk generate angka random. Method `Int()` milik object randomizer hanya salah satu dari fungsi yang tersedia di dalam package tersebut, yang gunanya adalah menghasilkan angka random bertipe `int`.
+
+Selain itu, ada juga `randomizer.Float32()` yang menghasilkan angka random bertipe `float32`. Ada juga `randomizer.Uint32()` yang menghasilkan angka random bertipe *unsigned* int, dan lainnya.
+
+Contoh penerapan fungsi-fungsi tersebut:
+
+```go
+randomizer := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
+fmt.Println("random int:", randomizer.Int())
+fmt.Println("random float32:", randomizer.Float32())
+fmt.Println("random uint:", randomizer.Uint32())
+```
+
+lebih detailnya silakan merujuk ke https://golang.org/pkg/math/rand/
+
+## A.39.5. Angka Random Index Tertentu
+
+Gunakan `randomizer.Intn(n)` untuk mendapatkan angka random dengan batas `0` hingga `n - 1`, contoh: `randomizer.Intn(100)` akan mengembalikan angka acak dari 0 hingga 99.
+
+## A.39.6. Random Tipe Data String
+
+Untuk menghasilkan data random string, ada banyak cara yang bisa diterapkan, salah satunya adalah dengan memafaatkan alfabet dan hasil random numerik.
+
+```go
+var randomizer = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
+var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+func randomString(length int) string {
+ b := make([]rune, length)
+ for i := range b {
+ b[i] = letters[randomizer.Intn(len(letters))]
+ }
+ return string(b)
+}
+
+func main() {
+ fmt.Println("random string 5 karakter:", randomString(5))
+}
+```
+
+Dengan fungsi di atas kita bisa dengan mudah meng-generate string random dengan panjang karakter yang sudah ditentukan, misal `randomString(10)` akan menghasilkan random string 10 karakter.
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-reflect.md b/en/content-en/A-reflect.md
new file mode 100644
index 000000000..1d626f9f5
--- /dev/null
+++ b/en/content-en/A-reflect.md
@@ -0,0 +1,177 @@
+# A.29. Reflect
+
+Reflection adalah teknik untuk inspeksi variabel, mengambil informasi dari suatu variabel untuk dilihat metadatanya atau untuk keperluan manipulasi. Cakupan informasi yang bisa didapatkan lewat reflection sangat luas, seperti melihat struktur variabel, tipe, nilai pointer, dan banyak lagi.
+
+Go menyediakan package `reflect`, berisikan banyak sekali fungsi untuk keperluan reflection. Pada chapter ini, kita akan belajar tentang dasar penggunaan package tersebut.
+
+Dari banyak fungsi yang tersedia di dalam package tersebut, ada 2 fungsi yang paling penting untuk diketahui, yaitu `reflect.ValueOf()` dan `reflect.TypeOf()`.
+
+- Fungsi `reflect.ValueOf()` akan mengembalikan objek dalam tipe `reflect.Value`, yang berisikan informasi yang berhubungan dengan nilai/data variabel yang diinspeksi.
+- Sedangkan `reflect.TypeOf()` mengembalikan objek dalam tipe `reflect.Type`. Objek tersebut berisikan informasi yang berhubungan dengan tipe data variabel yang diinspeksi.
+
+## A.29.1. Mencari Tipe Data & Value Menggunakan Reflect
+
+Dengan reflection, tipe data dan nilai variabel dapat diketahui dengan mudah. Contoh penerapannya bisa dilihat pada kode berikut.
+
+```go
+package main
+
+import "fmt"
+import "reflect"
+
+func main() {
+ var number = 23
+ var reflectValue = reflect.ValueOf(number)
+
+ fmt.Println("tipe variabel :", reflectValue.Type())
+
+ if reflectValue.Kind() == reflect.Int {
+ fmt.Println("nilai variabel :", reflectValue.Int())
+ }
+}
+```
+
+![Pemanfaatan reflect](images/A_reflect_0_reflect.png)
+
+Fungsi `reflect.valueOf()` memiliki parameter yang bisa menampung segala jenis tipe data. Fungsi tersebut mengembalikan objek dalam tipe `reflect.Value`, yang berisikan informasi mengenai variabel yang bersangkutan.
+
+Objek `reflect.Value` memiliki beberapa method, salah satunya `Type()`. Method ini mengembalikan tipe data variabel yang bersangkutan dalam bentuk `string`.
+
+Statement `reflectValue.Int()` menghasilkan nilai `int` dari variabel `number`. Untuk menampilkan nilai variabel reflect, harus dipastikan dulu tipe datanya. Ketika tipe data adalah `int`, maka bisa menggunakan method `Int()`. Ada banyak lagi method milik struct `reflect.Value` yang bisa digunakan untuk pengambilan nilai dalam bentuk tertentu, contohnya: `reflectValue.String()` digunakan untuk mengambil nilai `string`, `reflectValue.Float64()` untuk nilai `float64`, dan lainnya.
+
+Perlu diketahui, fungsi yang digunakan harus sesuai dengan tipe data nilai yang ditampung variabel. Jika fungsi yang digunakan berbeda dengan tipe data variabelnya, maka dipastikan muncul error. Contohnya seperti pada variabel yang nilainya bertipe `float64`, penggunaan method `String()` di situ pasti menghasilkan error.
+
+Diperlukan adanya pengecekan tipe data pada nilai yang disimpan, agar penggunaan method untuk pengambilan nilai bisa tepat. Salah satunya bisa dengan cara yang dicontohkan di atas, yaitu dengan mengecek dahulu apa jenis tipe datanya menggunakan method `Kind()`, setelah itu diambil nilainya dengan method yang sesuai.
+
+List konstanta tipe data dan method yang bisa digunakan dalam *reflection* di Go bisa dilihat di https://pkg.go.dev/reflect#Kind
+
+## Pengaksesan Nilai Dalam Bentuk `interface{}`
+
+Jika nilai hanya diperlukan untuk ditampilkan ke output, bisa menggunakan `.Interface()`. Lewat method tersebut segala jenis nilai bisa diakses dengan mudah.
+
+```go
+var number = 23
+var reflectValue = reflect.ValueOf(number)
+
+fmt.Println("tipe variabel :", reflectValue.Type())
+fmt.Println("nilai variabel :", reflectValue.Interface())
+```
+
+Fungsi `Interface()` mengembalikan nilai interface kosong atau `interface{}` atau `any`. Nilai aslinya sendiri bisa diakses dengan meng-casting interface kosong tersebut menggunakan teknik *type assertion*.
+
+```go
+var nilai = reflectValue.Interface().(int)
+```
+
+## A.29.2. Pengaksesan Informasi Property Variabel Objek
+
+Reflect API bisa digunakan untuk melihat metadata suatu property variabel objek cetakan struct, dengan catatan property-property tersebut bermodifier public. Contohnya bisa dilihat pada kode berikut.
+
+Siapkan sebuah struct bernama `student`.
+
+```go
+type student struct {
+ Name string
+ Grade int
+}
+```
+
+Buat method baru untuk struct tersebut, dengan nama method `getPropertyInfo()`. Method ini berisikan kode untuk mengambil dan menampilkan informasi tiap property milik struct `student`.
+
+```go
+func (s *student) getPropertyInfo() {
+ var reflectValue = reflect.ValueOf(s)
+
+ if reflectValue.Kind() == reflect.Ptr {
+ reflectValue = reflectValue.Elem()
+ }
+
+ var reflectType = reflectValue.Type()
+
+ for i := 0; i < reflectValue.NumField(); i++ {
+ fmt.Println("nama :", reflectType.Field(i).Name)
+ fmt.Println("tipe data :", reflectType.Field(i).Type)
+ fmt.Println("nilai :", reflectValue.Field(i).Interface())
+ fmt.Println("")
+ }
+}
+```
+
+Terakhir, lakukan uji coba method `getPropertyInfo()` di fungsi `main()`.
+
+```go
+func main() {
+ var s1 = &student{Name: "wick", Grade: 2}
+ s1.getPropertyInfo()
+}
+```
+
+![Pengaksesan property menggunakan reflect](images/A_reflect_1_accessing_properties.png)
+
+Di dalam method `getPropertyInfo()` terjadi beberapa hal. Pertama objek `reflect.Value` dari variabel `s` diambil. Setelah itu dilakukan pengecekan apakah variabel objek tersebut merupakan pointer atau tidak (bisa dilihat dari `if reflectValue.Kind() == reflect.Ptr`, jika bernilai `true` maka variabel adalah pointer). jika ternyata variabel memang berisi pointer, maka perlu diambil data objek reflect aslinya via method `Elem()`.
+
+Masih di dalam method `getPropertyInfo()`, dilakukan perulangan sebanyak jumlah property yang ada pada struct `student`. Method `NumField()` mengembalikan jumlah property publik yang ada dalam struct.
+
+Di tiap perulangan, informasi tiap property struct diambil berurutan dengan lewat method `Field()`. Method ini ada pada tipe `reflect.Value` dan `reflect.Type`.
+
+- `reflectType.Field(i).Name` mengembalikan nama property
+- `reflectType.Field(i).Type` mengembalikan tipe data property
+- `reflectValue.Field(i).Interface()` mengembalikan nilai property dalam bentuk `interface{}`
+
+Pengambilan informasi property, selain menggunakan indeks, bisa diambil berdasarkan nama field dengan menggunakan method `FieldByName()`.
+
+## A.29.3. Pengaksesan Informasi Method Variabel Objek
+
+Informasi mengenai method juga bisa diakses lewat reflect, syaratnya masih sama seperti pada pengaksesan proprerty, yaitu harus bermodifier public.
+
+Pada contoh di bawah ini informasi method `SetName()` akan diambil lewat reflection. Siapkan method baru di struct `student`, dengan nama `SetName()`.
+
+```go
+func (s *student) SetName(name string) {
+ s.Name = name
+}
+```
+
+Buat contoh penerapannya di fungsi `main()`.
+
+```go
+func main() {
+ var s1 = &student{Name: "john wick", Grade: 2}
+ fmt.Println("nama :", s1.Name)
+
+ var reflectValue = reflect.ValueOf(s1)
+ var method = reflectValue.MethodByName("SetName")
+ method.Call([]reflect.Value{
+ reflect.ValueOf("wick"),
+ })
+
+ fmt.Println("nama :", s1.Name)
+}
+```
+
+![Eksekusi method lewat reflection](images/A_reflect_2_accessing_method_information.png)
+
+Pada kode di atas, disiapkan variabel `s1` yang merupakan instance struct `student`. Variabel tersebut memiliki property `Name` yang nilainya ditentukan di awal, yaitu string `"john wick"`.
+
+Setelah itu, data *reflection* nilai objek tersebut diambil, *reflection* method `SetName` juga diambil. Pengambilan *reflection* method dilakukan menggunakan `MethodByName` dengan argument adalah nama method yang diinginkan, atau bisa juga lewat indeks method-nya (menggunakan `Method(i)`).
+
+Setelah *reflection* method yang dicari sudah didapatkan, `Call()` dipanggil untuk eksekusi method.
+
+Jika eksekusi method diikuti pengisian parameter, maka parameternya harus ditulis dalam bentuk array `[]reflect.Value` berurutan sesuai urutan deklarasi parameter-nya. Dan nilai yang dimasukkan ke array tersebut harus dalam bentuk `reflect.Value` (gunakan `reflect.ValueOf()` untuk pengambilannya).
+
+```go
+[]reflect.Value{
+ reflect.ValueOf("wick"),
+}
+```
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-regex.md b/en/content-en/A-regex.md
new file mode 100644
index 000000000..8b9db8d91
--- /dev/null
+++ b/en/content-en/A-regex.md
@@ -0,0 +1,174 @@
+# A.45. Regexp
+
+Regexp atau regex atau **regular expression** adalah suatu teknik yang digunakan untuk pencocokan string yang memiliki pola tertentu. Regex biasa dimanfaatkan untuk pencarian dan pengubahan data string.
+
+Go mengadopsi spesifikasi regex **RE2**. Lebih detailnya mengenai RE2 bisa langsung cek dokumentasinya di [https://github.com/google/re2/wiki/Syntax](https://github.com/google/re2/wiki/Syntax).
+
+Pada chapter ini kita akan belajar mengenai pengaplikasian regex dengan memanfaatkan fungsi-fungsi dalam package `regexp`.
+
+## A.45.1. Penerapan Regexp
+
+Fungsi `regexp.Compile()` digunakan untuk mengkompilasi ekspresi regex. Fungsi tersebut mengembalikan objek bertipe `*regexp.Regexp`.
+
+Berikut merupakan contoh penerapan regex untuk pencarian karakter.
+
+```go
+package main
+
+import "fmt"
+import "regexp"
+
+func main() {
+ var text = "banana burger soup"
+ var regex, err = regexp.Compile(`[a-z]+`)
+
+ if err != nil {
+ fmt.Println(err.Error())
+ }
+
+ var res1 = regex.FindAllString(text, 2)
+ fmt.Printf("%#v \n", res1)
+ // []string{"banana", "burger"}
+
+ var res2 = regex.FindAllString(text, -1)
+ fmt.Printf("%#v \n", res2)
+ // []string{"banana", "burger", "soup"}
+}
+```
+
+Ekspresi `[a-z]+` maknanya adalah semua string yang merupakan alphabet yang hurufnya kecil. Ekspresi tersebut di-compile oleh `regexp.Compile()` lalu disimpan ke variabel objek `regex` bertipe `*regexp.Regexp`.
+
+Struct `regexp.Regexp` memiliki banyak method, salah satunya adalah `FindAllString()`, berfungsi untuk mencari semua string yang sesuai dengan ekspresi regex, dengan kembalian berupa slice string.
+
+Jumlah hasil pencarian dari `regex.FindAllString()` bisa ditentukan. Contohnya pada `res1`, ditentukan maksimal `2` data saja pada nilai kembalian. Jika batas di set `-1`, maka semua hasil yang cocok dikembalikan oleh fungsi tersebut.
+
+Ada cukup banyak method struct `*regexp.Regexp` yang bisa kita manfaatkan untuk keperluan pengelolaan string. Berikut merupakan pembahasan tiap method-nya.
+
+## A.45.2. Method `MatchString()`
+
+Method ini digunakan untuk mendeteksi apakah string memenuhi sebuah pola regexp.
+
+```go
+var text = "banana burger soup"
+var regex, _ = regexp.Compile(`[a-z]+`)
+
+var isMatch = regex.MatchString(text)
+fmt.Println(isMatch)
+// true
+```
+
+Pada contoh di atas `isMatch` bernilai `true` karena string `"banana burger soup"` memenuhi pola regex `[a-z]+`.
+
+## A.45.3. Method `FindString()`
+
+Digunakan untuk mencari string yang memenuhi kriteria regexp yang telah ditentukan.
+
+```go
+var text = "banana burger soup"
+var regex, _ = regexp.Compile(`[a-z]+`)
+
+var str = regex.FindString(text)
+fmt.Println(str)
+// "banana"
+```
+
+Fungsi ini hanya mengembalikan 1 buah hasil saja. Jika ada banyak substring yang sesuai dengan ekspresi regexp, akan dikembalikan yang pertama saja.
+
+## A.45.4. Method `FindStringIndex()`
+
+Digunakan untuk mencari index string kembalian hasil dari operasi regexp.
+
+```go
+var text = "banana burger soup"
+var regex, _ = regexp.Compile(`[a-z]+`)
+
+var idx = regex.FindStringIndex(text)
+fmt.Println(idx)
+// [0, 6]
+
+var str = text[0:6]
+fmt.Println(str)
+// "banana"
+```
+
+Method ini sama dengan `FindString()` hanya saja yang dikembalikan indeks-nya.
+
+## A.45.5. Method `FindAllString()`
+
+Digunakan untuk mencari banyak string yang memenuhi kriteria regexp yang telah ditentukan.
+
+```go
+var text = "banana burger soup"
+var regex, _ = regexp.Compile(`[a-z]+`)
+
+var str1 = regex.FindAllString(text, -1)
+fmt.Println(str1)
+// ["banana", "burger", "soup"]
+
+var str2 = regex.FindAllString(text, 1)
+fmt.Println(str2)
+// ["banana"]
+```
+
+Jumlah data yang dikembalikan bisa ditentukan. Jika diisi dengan `-1`, maka akan mengembalikan semua data.
+
+## A.45.6. Method `ReplaceAllString()`
+
+Berguna untuk me-replace semua string yang memenuhi kriteri regexp, dengan string lain.
+
+```go
+var text = "banana burger soup"
+var regex, _ = regexp.Compile(`[a-z]+`)
+
+var str = regex.ReplaceAllString(text, "potato")
+fmt.Println(str)
+// "potato potato potato"
+```
+
+## A.45.7. Method `ReplaceAllStringFunc()`
+
+Digunakan untuk me-replace semua string yang memenuhi kriteri regexp, dengan kondisi yang bisa ditentukan untuk setiap substring yang akan di replace.
+
+```go
+var text = "banana burger soup"
+var regex, _ = regexp.Compile(`[a-z]+`)
+
+var str = regex.ReplaceAllStringFunc(text, func(each string) string {
+ if each == "burger" {
+ return "potato"
+ }
+ return each
+})
+fmt.Println(str)
+// "banana potato soup"
+```
+
+Pada contoh di atas, jika ada substring yang *match* dengan kata `"burger"`, maka akan diganti dengan `"potato"`.
+
+## A.45.8. Method `Split()`
+
+Digunakan untuk memisah string dengan pemisah adalah substring yang memenuhi kriteria regexp yang telah ditentukan.
+
+Jumlah karakter yang akan di split bisa ditentukan dengan mengisi parameter kedua fungsi `regex.Split()`. Jika di-isi `-1` maka semua karakter yang memenuhi kriteria regex akan menjadi *separator* dalam operasi pemisahan/split. Contoh lain, jika di-isi `2`, maka hanya 2 karakter pertama yang memenuhi kriteria regex akan menjadi *separator* dalam split tersebut.
+
+```go
+var text = "banana burger soup"
+var regex, _ = regexp.Compile(`[a-b]+`) // split dengan separator adalah karakter "a" dan/atau "b"
+
+var str = regex.Split(text, -1)
+fmt.Printf("%#v \n", str)
+// []string{"", "n", "n", " ", "urger soup"}
+```
+
+Pada contoh di atas, ekspresi regexp `[a-b]+` digunakan sebagai kriteria split. Maka karakter `a` dan/atau `b` akan menjadi separator.
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-seleksi-kondisi.md b/en/content-en/A-seleksi-kondisi.md
new file mode 100644
index 000000000..8118043c5
--- /dev/null
+++ b/en/content-en/A-seleksi-kondisi.md
@@ -0,0 +1,211 @@
+# A.13. Seleksi Kondisi
+
+Seleksi kondisi digunakan untuk mengontrol alur eksekusi flow program. Analoginya mirip seperti fungsi rambu lalu lintas di jalan raya. Kapan kendaraan diperbolehkan melaju dan kapan harus berhenti diatur oleh rambu tersebut. Seleksi kondisi pada program juga kurang lebih sama, kapan sebuah blok kode dieksekusi dikontrol.
+
+Yang dijadikan acuan oleh seleksi kondisi adalah nilai bertipe `bool`, bisa berasal dari variabel, ataupun hasil operasi perbandingan. Nilai tersebut menentukan blok kode mana yang akan dieksekusi.
+
+Go memiliki 2 macam keyword untuk seleksi kondisi, yaitu **if else** dan **switch**. Pada chapter ini kita akan mempelajari keduanya.
+
+> Go tidak mendukung seleksi kondisi menggunakan **ternary**.
+ if
- else
](images/A_seleksi_kondisi_1_if_else.png)
+
+Penulisan if else Go diawali dengan keyword `if` kemudian diikuti nilai seleksi kondisi dan blok kode ketika kondisi terpenuhi. Ketika kondisinya tidak terpenuhi akan blok kode `else` dipanggil (jika blok kode `else` tersebut ada). Ketika ada banyak kondisi, gunakan `else if`.
+
+## A.13.2. Variabel Temporary Pada `if` - `else`
+
+Variabel temporary adalah variabel yang hanya bisa digunakan pada deretan blok seleksi kondisi di mana ia ditempatkan. Penggunaan variabel ini membawa beberapa manfaat, antara lain:
+
+ - Scope atau cakupan variabel jelas, hanya bisa digunakan pada blok seleksi kondisi itu saja
+ - Kode menjadi lebih rapi
+ - Ketika nilai variabel tersebut didapat dari sebuah komputasi, perhitungan tidak perlu dilakukan di dalam blok masing-masing kondisi.
+
+```go
+var point = 8840.0
+
+if percent := point / 100; percent >= 100 {
+ fmt.Printf("%.1f%s perfect!\n", percent, "%")
+} else if percent >= 70 {
+ fmt.Printf("%.1f%s good\n", percent, "%")
+} else {
+ fmt.Printf("%.1f%s not bad\n", percent, "%")
+}
+```
+
+Variabel `percent` nilainya didapat dari hasil perhitungan, dan hanya bisa digunakan di deretan blok seleksi kondisi itu saja yang mencakup blok `if`, `else if`, dan `else`.
+
+> Deklarasi variabel temporary hanya bisa dilakukan lewat metode type inference yang menggunakan tanda `:=`. Penggunaan keyword `var` di situ tidak diperbolehkan karena menyebabkan error.
+
+## A.13.3. Seleksi Kondisi Menggunakan Keyword `switch` - `case`
+
+Switch merupakan seleksi kondisi yang sifatnya fokus pada satu variabel, lalu kemudian di-cek nilainya. Contoh sederhananya seperti penentuan apakah nilai variabel `x` adalah: `1`, `2`, `3`, atau lainnya.
+
+```go
+var point = 6
+
+switch point {
+case 8:
+ fmt.Println("perfect")
+case 7:
+ fmt.Println("awesome")
+default:
+ fmt.Println("not bad")
+}
+```
+
+Pada kode di atas, tidak ada kondisi atau `case` yang terpenuhi karena nilai variabel `point` tetap `6`. Ketika hal seperti ini terjadi, blok kondisi `default` dipanggil. Bisa dibilang bahwa `default` merupakan `else` dalam sebuah switch.
+
+Perlu diketahui, switch pada pemrograman Go memiliki perbedaan dibanding bahasa lain. Di Go, ketika sebuah case terpenuhi, tidak akan dilanjutkan ke pengecekan case selanjutnya, meskipun tidak ada keyword `break` di situ. Konsep ini berkebalikan dengan switch pada umumnya pemrograman lain (yang ketika sebuah case terpenuhi, maka akan tetap dilanjut mengecek case selanjutnya kecuali ada keyword `break`).
+
+## A.13.4. Pemanfaatan `case` Untuk Banyak Kondisi
+
+Sebuah `case` dapat menampung banyak kondisi. Cara penerapannya yaitu dengan menuliskan nilai pembanding-pembanding variabel yang di-switch setelah keyword `case` dipisah tanda koma (`,`).
+
+```go
+var point = 6
+
+switch point {
+case 8:
+ fmt.Println("perfect")
+case 7, 6, 5, 4:
+ fmt.Println("awesome")
+default:
+ fmt.Println("not bad")
+}
+```
+
+Kondisi `case 7, 6, 5, 4:` akan terpenuhi ketika nilai variabel `point` adalah 7 atau 6 atau 5 atau 4.
+
+## A.13.5. Kurung Kurawal Pada Keyword `case` & `default`
+
+Tanda kurung kurawal (`{ }`) bisa diterapkan pada keyword `case` dan `default`. Tanda ini opsional, boleh dipakai boleh tidak. Bagus jika dipakai pada blok kondisi yang di dalamnya ada banyak statement, dengannya kode akan terlihat lebih rapi.
+
+Perhatikan kode berikut, bisa dilihat pada keyword `default` terdapat kurung kurawal yang mengapit 2 statement di dalamnya.
+
+```go
+var point = 6
+
+switch point {
+case 8:
+ fmt.Println("perfect")
+case 7, 6, 5, 4:
+ fmt.Println("awesome")
+default:
+ {
+ fmt.Println("not bad")
+ fmt.Println("you can be better!")
+ }
+}
+```
+
+## A.13.6. Switch Dengan Gaya `if` - `else`
+
+Uniknya di Go, switch bisa digunakan dengan gaya ala if-else. Nilai yang akan dibandingkan tidak dituliskan setelah keyword `switch`, melainkan akan ditulis langsung dalam bentuk perbandingan dalam keyword `case`.
+
+Pada kode di bawah ini, kode program switch di atas diubah ke dalam gaya `if-else`. Variabel `point` dihilangkan dari keyword `switch`, lalu kondisi-kondisinya dituliskan di tiap `case`.
+
+```go
+var point = 6
+
+switch {
+case point == 8:
+ fmt.Println("perfect")
+case (point < 8) && (point > 3):
+ fmt.Println("awesome")
+default:
+ {
+ fmt.Println("not bad")
+ fmt.Println("you need to learn more")
+ }
+}
+```
+
+## A.13.7. Penggunaan Keyword `fallthrough` Dalam `switch`
+
+Seperti yang sudah dijelaskan sebelumnya, bahwa switch pada Go memiliki perbedaan dengan bahasa lain. Ketika sebuah `case` terpenuhi, pengecekan kondisi tidak akan diteruskan ke case-case setelahnya.
+
+Keyword `fallthrough` digunakan untuk memaksa proses pengecekan tetap diteruskan ke `case` selanjutnya dengan **tanpa menghiraukan nilai kondisinya**, efeknya adalah case di pengecekan selanjutnya selalu dianggap `true` (meskipun aslinya bisa saja kondisi tersebut tidak terpenuhi, akan tetap dianggap `true`).
+
+```go
+var point = 6
+
+switch {
+case point == 8:
+ fmt.Println("perfect")
+case (point < 8) && (point > 3):
+ fmt.Println("awesome")
+ fallthrough
+case point < 5:
+ fmt.Println("you need to learn more")
+default:
+ {
+ fmt.Println("not bad")
+ fmt.Println("you need to learn more")
+ }
+}
+```
+
+Di contoh, setelah pengecekan `case (point < 8) && (point > 3)` selesai, dilanjut ke pengecekan `case point < 5`, karena ada `fallthrough` di situ. Dan kondisi `case < 5` tersebut dianggap `true` meskipun secara logika harusnya tidak terpenuhi.
+
+![Penggunaan fallthrough
dalam switch
](images/A_seleksi_kondisi_2_fallthrough.png)
+
+Pada `case` dalam sebuah `switch`, diperbolehkan terdapat lebih dari satu `fallthrough`.
+
+## A.13.8. Seleksi Kondisi Bersarang
+
+Seleksi kondisi bersarang adalah seleksi kondisi, yang berada dalam seleksi kondisi, yang mungkin juga berada dalam seleksi kondisi, dan seterusnya. Seleksi kondisi bersarang bisa dilakukan pada `if` - `else`, `switch`, ataupun kombinasi keduanya.
+
+```go
+var point = 10
+
+if point > 7 {
+ switch point {
+ case 10:
+ fmt.Println("perfect!")
+ default:
+ fmt.Println("nice!")
+ }
+} else {
+ if point == 5 {
+ fmt.Println("not bad")
+ } else if point == 3 {
+ fmt.Println("keep trying")
+ } else {
+ fmt.Println("you can do it")
+ if point == 0 {
+ fmt.Println("try harder!")
+ }
+ }
+}
+```
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-setup-go-project-dengan-go-modules.md b/en/content-en/A-setup-go-project-dengan-go-modules.md
new file mode 100644
index 000000000..96c914088
--- /dev/null
+++ b/en/content-en/A-setup-go-project-dengan-go-modules.md
@@ -0,0 +1,54 @@
+# A.3. Go Modules
+
+Pada bagian ini kita akan belajar cara pembuatan project baru menggunakan Go Modules.
+
+## A.3.1. Penjelasan
+
+Go modules merupakan tools untuk manajemen dependensi resmi milik Go. Modules digunakan untuk menginisialisasi sebuah project, sekaligus melakukan manajemen terhadap *3rd party* atau *library* atau *dependency* yang digunakan dalam project.
+
+Modules penggunaannya adalah via CLI. Jika pembaca sudah sukses meng-*install* Go, maka otomatis bisa menggunakan operasi CLI Go Modules.
+
+> Di Go, istilah modules (atau module) maknanya adalah sama dengan project. Jadi gak perlu bingung
+
+## A.3.2. Inisialisasi Project Menggunakan Go Modules
+
+Command `go mod init` digunakan untuk menginisialisasi project baru.
+
+Mari langsung praktekan saja. Buat folder baru, bisa via CLI atau lewat browser/finder.
+
+```bash
+mkdir project-pertama
+cd project-pertama
+go mod init project-pertama
+```
+
+Bisa dilihat pada *command* di atas ada direktori `project-pertama`, dibuat. Setelah masuk ke direktori tersebut, perintah `go mod init project-pertama` dijalankan. Dengan ini maka kita telah menginisialisasi direktori/folder `project-pertama` sebagai sebuah project Go dengan nama `project-pertama`.
+
+![Init project](images/A_go_modules_1_initmodule.png)
+
+Skema penulisan command `go mod`:
+
+```
+go mod init
+ [apple, grape, banana, melon]
| semua elemen mulai indeks ke-0, hingga sebelum indeks ke-4 |
+| `fruits[0:0]` | `[]` | menghasilkan slice kosong, karena tidak ada elemen sebelum indeks ke-0 |
+| `fruits[4:4]` | `[]` | menghasilkan slice kosong, karena tidak ada elemen yang dimulai dari indeks ke-4 |
+| `fruits[4:0]` | `[]` | error, pada penulisan `fruits[a:b]` nilai `a` harus lebih kecil atau sama dengan `b` |
+| `fruits[:]` | `[apple, grape, banana, melon]` | semua elemen |
+| `fruits[2:]` | `[banana, melon]` | semua elemen mulai indeks ke-2 |
+| `fruits[:2]` | `[apple, grape]` | semua elemen hingga sebelum indeks ke-2 |
+
+## A.16.3. Slice Merupakan Tipe Data Reference
+
+Slice merupakan tipe data *reference* atau referensi. Artinya jika ada slice baru yang terbentuk dari slice lama, maka data elemen slice yang baru akan memiliki alamat memori yang sama dengan elemen slice lama. Setiap perubahan yang terjadi di elemen slice baru, akan berdampak juga pada elemen slice lama yang memiliki referensi yang sama.
+
+Program berikut merupakan pembuktian tentang teori yang baru kita bahas. Kita akan mencoba mengubah data elemen slice baru, yang terbentuk dari slice lama.
+
+```go
+var fruits = []string{"apple", "grape", "banana", "melon"}
+
+var aFruits = fruits[0:3]
+var bFruits = fruits[1:4]
+
+var aaFruits = aFruits[1:2]
+var baFruits = bFruits[0:1]
+
+fmt.Println(fruits) // [apple grape banana melon]
+fmt.Println(aFruits) // [apple grape banana]
+fmt.Println(bFruits) // [grape banana melon]
+fmt.Println(aaFruits) // [grape]
+fmt.Println(baFruits) // [grape]
+
+// Buah "grape" diubah menjadi "pinnaple"
+baFruits[0] = "pinnaple"
+
+fmt.Println(fruits) // [apple pinnaple banana melon]
+fmt.Println(aFruits) // [apple pinnaple banana]
+fmt.Println(bFruits) // [pinnaple banana melon]
+fmt.Println(aaFruits) // [pinnaple]
+fmt.Println(baFruits) // [pinnaple]
+```
+
+Sekilas bisa kita lihat bahwa setelah slice yang isi datanya adalah `grape` di-ubah menjadi `pinnaple`, semua slice pada 4 variabel lainnya juga ikut berubah.
+
+Variabel `aFruits`, `bFruits` merupakan slice baru yang terbentuk dari variabel `fruits`. Dengan menggunakan dua slice baru tersebut, diciptakan lagi slice lainnya, yaitu `aaFruits`, dan `baFruits`. Kelima slice tersebut ditampilkan nilainya.
+
+Selanjutnya, nilai dari `baFruits[0]` diubah, dan 5 slice tadi ditampilkan lagi. Hasilnya akan ada banyak slice yang elemennya ikut berubah. Yaitu elemen-elemen yang referensi-nya sama dengan referensi elemen `baFruits[0]`.
+
+![Perubahan data elemen slice berpengaruh pada slice lain](images/A_slice_2_slice_reference.png)
+
+Bisa dilihat pada output di atas, elemen yang sebelumnya bernilai `"grape"` pada variabel `fruits`, `aFruits`, `bFruits`, `aaFruits`, dan `baFruits`; Seluruhnya berubah menjadi `"pinnaple"`, karena memiliki referensi yang sama.
+
+---
+
+Pembahasan mengenai dasar slice sepertinya sudah cukup, selanjutnya kita akan membahas tentang beberapa *built in function* bawaan Go, yang bisa dimanfaatkan untuk keperluan operasi slice.
+
+## A.16.4. Fungsi `len()`
+
+Fungsi `len()` digunakan untuk menghitung jumlah elemen slice yang ada. Sebagai contoh jika sebuah variabel adalah slice dengan data 4 buah, maka fungsi ini pada variabel tersebut akan mengembalikan angka **4**.
+
+```go
+var fruits = []string{"apple", "grape", "banana", "melon"}
+fmt.Println(len(fruits)) // 4
+```
+
+## A.16.5. Fungsi `cap()`
+
+Fungsi `cap()` digunakan untuk menghitung lebar atau kapasitas maksimum slice. Nilai kembalian fungsi ini untuk slice yang baru dibuat pasti sama dengan `len`, tapi bisa berubah seiring operasi slice yang dilakukan. Agar lebih jelas, silakan pelajari kode berikut.
+
+```go
+var fruits = []string{"apple", "grape", "banana", "melon"}
+fmt.Println(len(fruits)) // len: 4
+fmt.Println(cap(fruits)) // cap: 4
+
+var aFruits = fruits[0:3]
+fmt.Println(len(aFruits)) // len: 3
+fmt.Println(cap(aFruits)) // cap: 4
+
+var bFruits = fruits[1:4]
+fmt.Println(len(bFruits)) // len: 3
+fmt.Println(cap(bFruits)) // cap: 3
+```
+
+Variabel `fruits` disiapkan di awal dengan jumlah elemen 4, fungsi `len(fruits)` dan `cap(fruits)` pasti hasinya 4.
+
+Variabel `aFruits` dan `bFruits` merupakan slice baru berisikan 3 buah elemen milik slice `fruits`. Variabel `aFruits` mengambil elemen index 0, 1, 2; sedangkan `bFruits` 1, 2, 3.
+
+Fungsi `len()` menghasilkan angka 3, karena jumlah elemen kedua slice ini adalah 3. Tetapi `cap(aFruits)` menghasilkan angka yang berbeda, yaitu 4 untuk `aFruits` dan 3 untuk `bFruits`. kenapa? jawabannya bisa dilihat pada tabel berikut.
+
+| Kode | Output | `len()` | `cap()` |
+|:-------------- |:--------------------------------- |:-------:|:-------:|
+| `fruits[0:4]` | [**`buah` `buah` `buah` `buah`**] | 4 | 4 |
+| `aFruits[0:3]` | [**`buah` `buah` `buah`** `----`] | 3 | 4 |
+| `bFruits[1:4]` | `----` [**`buah` `buah` `buah`**] | 3 | 3 |
+
+Kita analogikan slicing 2 index menggunakan **x** dan **y**.
+
+```go
+fruits[x:y]
+```
+
+**Slicing** yang dimulai dari indeks **0** hingga **y** akan mengembalikan elemen-elemen mulai indeks **0** hingga sebelum indeks **y**, dengan lebar kapasitas adalah sama dengan slice aslinya.
+
+Sedangkan slicing yang dimulai dari indeks **x**, yang mana nilai **x** adalah lebih dari **0**, membuat elemen ke-**x** slice yang diambil menjadi elemen ke-0 slice baru. Hal inilah yang membuat kapasitas slice berubah.
+
+## A.16.6. Fungsi `append()`
+
+Fungsi `append()` digunakan untuk menambahkan elemen pada slice. Elemen baru tersebut diposisikan setelah indeks paling akhir. Nilai balik fungsi ini adalah slice yang sudah ditambahkan nilai barunya. Contoh penggunaannya bisa dilihat di kode berikut.
+
+```go
+var fruits = []string{"apple", "grape", "banana"}
+var cFruits = append(fruits, "papaya")
+
+fmt.Println(fruits) // ["apple", "grape", "banana"]
+fmt.Println(cFruits) // ["apple", "grape", "banana", "papaya"]
+```
+
+Ada 3 hal yang perlu diketahui dalam penggunaan fungsi ini.
+
+ - Ketika jumlah elemen dan lebar kapasitas adalah sama (`len(fruits) == cap(fruits)`), maka elemen baru hasil `append()` merupakan referensi baru.
+ - Ketika jumlah elemen lebih kecil dibanding kapasitas (`len(fruits) < cap(fruits)`), elemen baru tersebut ditempatkan ke dalam cakupan kapasitas, menjadikan semua elemen slice lain yang referensi-nya sama akan berubah nilainya.
+
+Agar lebih jelas silakan perhatikan contoh berikut.
+
+```go
+var fruits = []string{"apple", "grape", "banana"}
+var bFruits = fruits[0:2]
+
+fmt.Println(cap(bFruits)) // 3
+fmt.Println(len(bFruits)) // 2
+
+fmt.Println(fruits) // ["apple", "grape", "banana"]
+fmt.Println(bFruits) // ["apple", "grape"]
+
+var cFruits = append(bFruits, "papaya")
+
+fmt.Println(fruits) // ["apple", "grape", "papaya"]
+fmt.Println(bFruits) // ["apple", "grape"]
+fmt.Println(cFruits) // ["apple", "grape", "papaya"]
+```
+
+Pada contoh di atas bisa dilihat, elemen indeks ke-2 slice `fruits` nilainya berubah setelah ada penggunaan keyword `append()` pada `bFruits`. Slice `bFruits` kapasitasnya adalah **3** sedang jumlah datanya hanya **2**. Karena `len(bFruits) < cap(bFruits)`, maka elemen baru yang dihasilkan, terdeteksi sebagai perubahan nilai pada referensi yang lama (referensi elemen indeks ke-2 slice `fruits`), membuat elemen yang referensinya sama, nilainya berubah.
+
+## A.16.7. Fungsi `copy()`
+
+Fungsi `copy()` digunakan untuk men-copy elements slice pada `src` (parameter ke-2), ke `dst` (parameter pertama).
+
+```go
+copy(dst, src)
+```
+
+Jumlah element yang di-copy dari `src` adalah sejumlah lebar slice `dst` (atau `len(dst)`). Jika jumlah slice pada `src` lebih kecil dari `dst`, maka akan ter-copy semua. Lebih jelasnya silakan perhatikan contoh berikut.
+
+```go
+dst := make([]string, 3)
+src := []string{"watermelon", "pinnaple", "apple", "orange"}
+n := copy(dst, src)
+
+fmt.Println(dst) // watermelon pinnaple apple
+fmt.Println(src) // watermelon pinnaple apple orange
+fmt.Println(n) // 3
+```
+
+Pada kode di atas variabel slice `dst` dipersiapkan dengan lebar adalah 3 elements. Slice `src` yang isinya 4 elements, di-copy ke `dst`. Menjadikan isi slice `dst` sekarang adalah 3 buah elements yang sama dengan 3 buah elements `src`, hasil dari operasi `copy()`.
+
+Yang ter-copy hanya 3 buah (meski `src` memiliki 4 elements) hal ini karena `copy()` hanya meng-copy elements sebanyak `len(dst)`.
+
+> Fungsi `copy()` mengembalikan informasi angka, representasi dari jumlah element yang berhasil di-copy.
+
+Pada contoh kedua berikut, `dst` merupakan slice yang sudah ada isinya, 3 buah elements. Variabel `src` yang juga merupakan slice dengan isi dua elements, di-copy ke `dst`. Karena operasi `copy()` akan meng-copy sejumlah `len(dst)`, maka semua elements `src` akan ter-copy **karena jumlahnya di bawah atau sama dengan lebar** `dst`.
+
+```go
+dst := []string{"potato", "potato", "potato"}
+src := []string{"watermelon", "pinnaple"}
+n := copy(dst, src)
+
+fmt.Println(dst) // watermelon pinnaple potato
+fmt.Println(src) // watermelon pinnaple
+fmt.Println(n) // 2
+```
+
+Jika dilihat pada kode di atas, isi `dst` masih tetap 3 elements, tapi dua elements pertama adalah sama dengan `src`. Element terakhir `dst` isinya tidak berubah, tetap `potato`, hal ini karena proses copy hanya memutasi element ke-1 dan ke-2 milik `dst`, karena memang pada `src` hanya dua itu elements-nya.
+
+## A.16.8. Pengaksesan Elemen Slice Dengan 3 Indeks
+
+**3 index** adalah teknik slicing untuk pengaksesan elemen yang sekaligus menentukan kapasitasnya. Cara penggunaannya yaitu dengan menyisipkan angka kapasitas di belakang, seperti `fruits[0:1:1]`. Angka kapasitas yang diisikan tidak boleh melebihi kapasitas slice yang akan di slicing.
+
+Berikut merupakan contoh penerapannya.
+
+```go
+var fruits = []string{"apple", "grape", "banana"}
+var aFruits = fruits[0:2]
+var bFruits = fruits[0:2:2]
+
+fmt.Println(fruits) // ["apple", "grape", "banana"]
+fmt.Println(len(fruits)) // len: 3
+fmt.Println(cap(fruits)) // cap: 3
+
+fmt.Println(aFruits) // ["apple", "grape"]
+fmt.Println(len(aFruits)) // len: 2
+fmt.Println(cap(aFruits)) // cap: 3
+
+fmt.Println(bFruits) // ["apple", "grape"]
+fmt.Println(len(bFruits)) // len: 2
+fmt.Println(cap(bFruits)) // cap: 2
+```
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-sql.md b/en/content-en/A-sql.md
new file mode 100644
index 000000000..e9ff282d4
--- /dev/null
+++ b/en/content-en/A-sql.md
@@ -0,0 +1,351 @@
+# A.56. SQL
+
+Go menyediakan package `database/sql` berisikan generic interface untuk keperluan interaksi dengan database sql. Package ini mewajibkan pengguna untuk juga menggunakan **driver** database engine yang dipilih.
+
+Ada cukup banyak sql driver yang tersedia untuk Go, detailnya bisa diakses di [https://go.dev/wiki/SQLDrivers](https://go.dev/wiki/SQLDrivers). Beberapa di antaranya:
+
+ - MySQL / MariaDB
+ - Oracle
+ - MS SQL Server
+ - Postgres
+ - dan lainnya
+
+Driver-driver tersebut merupakan project open source yang diinisiasi oleh komunitas di Github. Kita yang juga seorang developer juga bisa ikut berkontribusi di sana.
+
+Pada chapter ini kita akan belajar bagaimana membuat Go bisa berkomunikasi dengan database MySQL menggunakan driver [Go MySQL Driver](https://github.com/go-sql-driver/mysql).
+
+## A.56.1. Instalasi Driver
+
+Unduh driver mysql menggunakan `go get`.
+
+```
+cd
+ QueryRow()
](images/A_sql_3_sql_query_row.png)
+
+## A.56.5. Eksekusi Query Menggunakan `Prepare()`
+
+Teknik **prepared statement** adalah teknik penulisan query di awal dengan kelebihan bisa di re-use atau digunakan banyak kali untuk eksekusi yang berbeda-beda.
+
+Metode ini bisa digabung dengan `Query()` maupun `QueryRow()`. Berikut merupakan contoh penerapannya.
+
+```go
+func sqlPrepare() {
+ db, err := connect()
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ defer db.Close()
+
+ stmt, err := db.Prepare("select name, grade from tb_student where id = ?")
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+
+ var result1 = student{}
+ stmt.QueryRow("E001").Scan(&result1.name, &result1.grade)
+ fmt.Printf("name: %s\ngrade: %d\n", result1.name, result1.grade)
+
+ var result2 = student{}
+ stmt.QueryRow("W001").Scan(&result2.name, &result2.grade)
+ fmt.Printf("name: %s\ngrade: %d\n", result2.name, result2.grade)
+
+ var result3 = student{}
+ stmt.QueryRow("B001").Scan(&result3.name, &result3.grade)
+ fmt.Printf("name: %s\ngrade: %d\n", result3.name, result3.grade)
+}
+
+func main() {
+ sqlPrepare()
+}
+```
+
+Method `Prepare()` digunakan untuk deklarasi query, yang mengembalikan objek bertipe `sql.*Stmt`. Dari objek tersebut, dipanggil method `QueryRow()` beberapa kali dengan isi value untuk `id` berbeda-beda untuk tiap pemanggilannya.
+
+![Prepared statement](images/A_sql_4_prepared_statement.png)
+
+## A.56.6. Insert, Update, & Delete Data Menggunakan `Exec()`
+
+Untuk operasi **insert**, **update**, dan **delete**; dianjurkan untuk tidak menggunakan fungsi `sql.Query()` ataupun `sql.QueryRow()` untuk eksekusinya. Gunakan fungsi `Exec()`, contoh penerapannya bisa dilihat di bawah ini:
+
+```go
+func sqlExec() {
+ db, err := connect()
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ defer db.Close()
+
+ _, err = db.Exec("insert into tb_student values (?, ?, ?, ?)", "G001", "Galahad", 29, 2)
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ fmt.Println("insert success!")
+
+ _, err = db.Exec("update tb_student set age = ? where id = ?", 28, "G001")
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ fmt.Println("update success!")
+
+ _, err = db.Exec("delete from tb_student where id = ?", "G001")
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ fmt.Println("delete success!")
+}
+
+func main() {
+ sqlExec()
+}
+```
+
+Teknik prepared statement juga bisa digunakan pada metode ini. Berikut adalah perbandingan eksekusi `Exec()` menggunakan `Prepare()` dan cara biasa.
+
+```go
+// menggunakan metode prepared statement
+stmt, err := db.Prepare("insert into tb_student values (?, ?, ?, ?)")
+stmt.Exec("G001", "Galahad", 29, 2)
+
+// menggunakan metode biasa
+_, err := db.Exec("insert into tb_student values (?, ?, ?, ?)", "G001", "Galahad", 29, 2)
+```
+
+## A.56.7. Koneksi Dengan Engine Database Lain
+
+Karena package `database/sql` merupakan interface generic, maka cara untuk koneksi ke engine database lain (semisal Oracle, Postgres, SQL Server) adalah sama dengan cara koneksi ke MySQL. Cukup dengan meng-import driver yang digunakan, lalu mengganti nama driver pada saat pembuatan koneksi baru.
+
+```go
+sql.Open(driverName, connectionString)
+```
+
+Sebagai contoh saya menggunakan driver [pq](https://github.com/lib/pq) untuk koneksi ke server Postgres, maka connection string-nya:
+
+```go
+sql.Open("postgres", "user=postgres password=secret dbname=test sslmode=disable")
+```
+
+Selengkapya mengenai driver yang tersedia di Go silakan lihat di [https://go.dev/wiki/SQLDrivers](https://go.dev/wiki/SQLDrivers).
+
+---
+
+- [Go MySQL Driver](https://github.com/go-sql-driver/mysql), by Julien Schmidt, MPL-2.0 license
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-string-format.md b/en/content-en/A-string-format.md
new file mode 100644
index 000000000..3c66f8a09
--- /dev/null
+++ b/en/content-en/A-string-format.md
@@ -0,0 +1,279 @@
+# A.38. Layout Format String
+
+Pada pembahasan-pembahasan sebelumnya kita telah banyak memanfaatkan layout format string, contohnya seperti `%s`, `%d`, `%.2f`, dan lainnya. Layout format string tersebut digunakan untuk keperluan formatting string untuk dimunculkan ke layar ataupun untuk disimpan ke variabel.
+
+Layout format string digunakan dalam konversi data ke bentuk string. Contohnya seperti `%.3f` yang untuk konversi nilai `double` ke `string` dengan 3 digit desimal.
+
+## A.38.1. Persiapan
+
+Pada chapter ini kita akan mempelajari satu per satu layout format string yang tersedia di Golang. Kode berikut adalah sample data yang akan kita digunakan sebagai contoh.
+
+```go
+type student struct {
+ name string
+ height float64
+ age int32
+ isGraduated bool
+ hobbies []string
+}
+
+var data = student{
+ name: "wick",
+ height: 182.5,
+ age: 26,
+ isGraduated: false,
+ hobbies: []string{"eating", "sleeping"},
+}
+```
+
+## A.38.2. Layout Format `%b`
+
+Digunakan untuk memformat data numerik, menjadi bentuk string numerik berbasis 2 (biner).
+
+```go
+fmt.Printf("%b\n", data.age)
+// 11010
+```
+
+## A.38.3. Layout Format `%c`
+
+Digunakan untuk memformat data numerik yang merupakan kode unicode, menjadi bentuk string karakter unicode-nya.
+
+```go
+fmt.Printf("%c\n", 1400)
+// ո
+
+fmt.Printf("%c\n", 1235)
+// ӓ
+```
+
+## A.38.4. Layout Format `%d`
+
+Digunakan untuk memformat data numerik, menjadi bentuk string numerik berbasis 10 (basis bilangan yang kita gunakan).
+
+```go
+fmt.Printf("%d\n", data.age)
+// 26
+```
+
+## A.38.5. Layout Format `%e` atau `%E`
+
+Digunakan untuk memformat data numerik desimal ke dalam bentuk notasi numerik standar [Scientific notation](https://en.wikipedia.org/wiki/Scientific_notation).
+
+```go
+fmt.Printf("%e\n", data.height)
+// 1.825000e+02
+
+fmt.Printf("%E\n", data.height)
+// 1.825000E+02
+```
+
+**1.825000E+02** maksudnya adalah **1.825 x 10^2**, dan hasil operasi tersebut adalah sesuai dengan data asli = **182.5**.
+
+Perbedaan antara `%e` dan `%E` hanya pada bagian huruf besar kecil karakter `e` pada hasil.
+
+## A.38.6. Layout Format `%f` atau `%F`
+
+`%F` adalah alias dari `%f`. Keduanya memiliki fungsi yang sama.
+
+Berfungsi untuk memformat data numerik desimal, dengan lebar desimal bisa ditentukan. Secara default lebar digit desimal adalah 6 digit.
+
+```go
+fmt.Printf("%f\n", data.height)
+// 182.500000
+
+fmt.Printf("%.9f\n", data.height)
+// 182.500000000
+
+fmt.Printf("%.2f\n", data.height)
+// 182.50
+
+fmt.Printf("%.f\n", data.height)
+// 182
+```
+
+## A.38.7. Layout Format `%g` atau `%G`
+
+`%G` adalah alias dari `%g`. Keduanya memiliki fungsi yang sama.
+
+Berfungsi untuk memformat data numerik desimal, dengan lebar desimal bisa ditentukan. Lebar kapasitasnya sangat besar, pas digunakan untuk data yang jumlah digit desimalnya cukup banyak.
+
+Bisa dilihat pada kode berikut perbandingan antara `%e`, `%f`, dan `%g`.
+
+```go
+fmt.Printf("%e\n", 0.123123123123)
+// 1.231231e-01
+
+fmt.Printf("%f\n", 0.123123123123)
+// 0.123123
+
+fmt.Printf("%g\n", 0.123123123123)
+// 0.123123123123
+```
+
+Perbedaan lainnya adalah pada `%g`, lebar digit desimal adalah sesuai dengan datanya, tidak bisa dicustom seperti pada `%f`.
+
+```go
+fmt.Printf("%g\n", 0.12)
+// 0.12
+
+fmt.Printf("%.5g\n", 0.12)
+// 0.12
+```
+
+## A.38.8. Layout Format `%o`
+
+Digunakan untuk memformat data numerik, menjadi bentuk string numerik berbasis 8 (oktal).
+
+```go
+fmt.Printf("%o\n", data.age)
+// 32
+```
+
+## A.38.9. Layout Format `%p`
+
+Digunakan untuk memformat data pointer, mengembalikan alamat pointer referensi variabel-nya.
+
+Alamat pointer dituliskan dalam bentuk numerik berbasis 16 dengan prefix `0x`.
+
+```go
+fmt.Printf("%p\n", &data.name)
+// 0x2081be0c0
+```
+
+## A.38.10. Layout Format `%q`
+
+Digunakan untuk **escape** string. Meskipun string yang dipakai menggunakan literal
+ \
akan tetap di-escape.
+
+```go
+fmt.Printf("%q\n", `" name \ height "`)
+// "\" name \\ height \""
+```
+
+## A.38.11. Layout Format `%s`
+
+Digunakan untuk memformat data string.
+
+```go
+fmt.Printf("%s\n", data.name)
+// wick
+```
+
+## A.38.12. Layout Format `%t`
+
+Digunakan untuk memformat data boolean, menampilkan nilai `bool`-nya.
+
+```go
+fmt.Printf("%t\n", data.isGraduated)
+// false
+```
+
+## A.38.13. Layout Format `%T`
+
+Berfungsi untuk mengambil tipe variabel yang akan diformat.
+
+```go
+fmt.Printf("%T\n", data.name)
+// string
+
+fmt.Printf("%T\n", data.height)
+// float64
+
+fmt.Printf("%T\n", data.age)
+// int32
+
+fmt.Printf("%T\n", data.isGraduated)
+// bool
+
+fmt.Printf("%T\n", data.hobbies)
+// []string
+```
+
+## A.38.14. Layout Format `%v`
+
+Digunakan untuk memformat data apa saja (termasuk data bertipe `interface{}`). Hasil kembaliannya adalah string nilai data aslinya.
+
+Jika data adalah objek cetakan `struct`, maka akan ditampilkan semua secara property berurutan.
+
+```go
+fmt.Printf("%v\n", data)
+// {wick 182.5 26 false [eating sleeping]}
+```
+
+## A.38.15. Layout Format `%+v`
+
+Digunakan untuk memformat struct, mengembalikan nama tiap property dan nilainya berurutan sesuai dengan struktur struct.
+
+```go
+fmt.Printf("%+v\n", data)
+// {name:wick height:182.5 age:26 isGraduated:false hobbies:[eating sleeping]}
+```
+
+## A.38.16. Layout Format `%#v`
+
+Digunakan untuk memformat struct, mengembalikan nama dan nilai tiap property sesuai dengan struktur struct dan juga bagaimana objek tersebut dideklarasikan.
+
+```go
+fmt.Printf("%#v\n", data)
+// main.student{name:"wick", height:182.5, age:26, isGraduated:false, hobbies:[]string{"eating", "sleeping"}}
+```
+
+Ketika menampilkan objek yang deklarasinya adalah menggunakan teknik *anonymous struct*, maka akan muncul juga struktur anonymous struct nya.
+
+```go
+var data = struct {
+ name string
+ height float64
+}{
+ name: "wick",
+ height: 182.5,
+}
+
+fmt.Printf("%#v\n", data)
+// struct { name string; height float64 }{name:"wick", height:182.5}
+```
+
+Format ini juga bisa digunakan untuk menampilkan tipe data lain, dan akan dimunculkan strukturnya juga.
+
+## A.38.17. Layout Format `%x` atau `%X`
+
+Digunakan untuk memformat data numerik, menjadi bentuk string numerik berbasis 16 (heksadesimal).
+
+```go
+fmt.Printf("%x\n", data.age)
+// 1a
+```
+
+Jika digunakan pada tipe data string, maka akan mengembalikan kode heksadesimal tiap karakter.
+
+```go
+var d = data.name
+
+fmt.Printf("%x%x%x%x\n", d[0], d[1], d[2], d[3])
+// 7769636b
+
+fmt.Printf("%x\n", d)
+// 7769636b
+```
+
+`%x` dan `%X` memiliki fungsi yang sama. Perbedaannya adalah `%X` akan mengembalikan string dalam bentuk *uppercase* atau huruf kapital.
+
+## A.38.18. Layout Format `%%`
+
+Cara untuk menulis karakter `%` pada string format.
+
+```go
+fmt.Printf("%%\n")
+// %
+```
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-strings.md b/en/content-en/A-strings.md
new file mode 100644
index 000000000..90145ea99
--- /dev/null
+++ b/en/content-en/A-strings.md
@@ -0,0 +1,167 @@
+# A.44. Fungsi String
+
+Go menyediakan package `strings`, isinya banyak fungsi untuk keperluan pengolahan data string. Chapter ini berisi pembahasan mengenai penggunaan fungsi yang ada di dalam package tersebut.
+
+## A.44.1. Fungsi `strings.Contains()`
+
+Dipakai untuk deteksi apakah string (parameter kedua) merupakan bagian dari string lain (parameter pertama). Nilai kembaliannya berupa `bool`.
+
+```go
+package main
+
+import "fmt"
+import "strings"
+
+func main() {
+ var isExists = strings.Contains("john wick", "wick")
+ fmt.Println(isExists)
+}
+```
+
+Variabel `isExists` akan bernilai `true`, karena string `"wick"` merupakan bagian dari `"john wick"`.
+
+## A.44.2. Fungsi `strings.HasPrefix()`
+
+Digunakan untuk deteksi apakah sebuah string (parameter pertama) diawali string tertentu (parameter kedua).
+
+```go
+var isPrefix1 = strings.HasPrefix("john wick", "jo")
+fmt.Println(isPrefix1) // true
+
+var isPrefix2 = strings.HasPrefix("john wick", "wi")
+fmt.Println(isPrefix2) // false
+```
+
+## A.44.3. Fungsi `strings.HasSuffix()`
+
+Digunakan untuk deteksi apakah sebuah string (parameter pertama) diakhiri string tertentu (parameter kedua).
+
+```go
+var isSuffix1 = strings.HasSuffix("john wick", "ic")
+fmt.Println(isSuffix1) // false
+
+var isSuffix2 = strings.HasSuffix("john wick", "ck")
+fmt.Println(isSuffix2) // true
+```
+
+## A.44.4. Fungsi `strings.Count()`
+
+Memiliki kegunaan untuk menghitung jumlah karakter tertentu (parameter kedua) dari sebuah string (parameter pertama). Nilai kembalian fungsi ini adalah jumlah karakternya.
+
+```go
+var howMany = strings.Count("ethan hunt", "t")
+fmt.Println(howMany) // 2
+```
+
+Nilai yang dikembalikan `2`, karena pada string `"ethan hunt"` terdapat dua buah karakter `"t"`.
+
+## A.44.5. Fungsi `strings.Index()`
+
+Digunakan untuk mencari posisi indeks sebuah string (parameter kedua) dalam string (parameter pertama).
+
+```go
+var index1 = strings.Index("ethan hunt", "ha")
+fmt.Println(index1) // 2
+```
+
+String `"ha"` berada pada posisi ke `2` dalam string `"ethan hunt"` (indeks dimulai dari 0). Jika diketemukan dua substring, maka yang diambil adalah yang pertama, contoh:
+
+```go
+var index2 = strings.Index("ethan hunt", "n")
+fmt.Println(index2) // 4
+```
+
+String `"n"` berada pada indeks `4` dan `8`. Yang dikembalikan adalah yang paling kiri (paling kecil), yaitu `4`.
+
+## A.44.6. Fungsi `strings.Replace()`
+
+Fungsi ini digunakan untuk replace atau mengganti bagian dari string dengan string tertentu. Jumlah substring yang di-replace bisa ditentukan, apakah hanya 1 string pertama, 2 string, atau seluruhnya.
+
+```go
+var text = "banana"
+var find = "a"
+var replaceWith = "o"
+
+var newText1 = strings.Replace(text, find, replaceWith, 1)
+fmt.Println(newText1) // "bonana"
+
+var newText2 = strings.Replace(text, find, replaceWith, 2)
+fmt.Println(newText2) // "bonona"
+
+var newText3 = strings.Replace(text, find, replaceWith, -1)
+fmt.Println(newText3) // "bonono"
+```
+
+Penjelasan:
+
+ 1. Pada contoh di atas, substring `"a"` pada string `"banana"` akan di-replace dengan string `"o"`.
+ 2. Pada `newText1`, hanya 1 huruf `o` saja yang tereplace karena maksimal substring yang ingin di-replace ditentukan 1.
+ 3. Angka `-1` akan menjadikan proses replace berlaku pada semua substring. Contoh bisa dilihat pada `newText3`.
+
+## A.44.7. Fungsi `strings.Repeat()`
+
+Digunakan untuk mengulang string (parameter pertama) sebanyak data yang ditentukan (parameter kedua).
+
+```go
+var str = strings.Repeat("na", 4)
+fmt.Println(str) // "nananana"
+```
+
+Pada contoh di atas, string `"na"` diulang sebanyak 4 kali. Hasilnya adalah: `"nananana"`
+
+## A.44.8. Fungsi `strings.Split()`
+
+Digunakan untuk memisah string (parameter pertama) dengan tanda pemisah bisa ditentukan sendiri (parameter kedua). Hasilnya berupa slice string.
+
+```go
+var string1 = strings.Split("the dark knight", " ")
+fmt.Println(string1) // output: ["the", "dark", "knight"]
+
+var string2 = strings.Split("batman", "")
+fmt.Println(string2) // output: ["b", "a", "t", "m", "a", "n"]
+```
+
+String `"the dark knight"` dipisah oleh karakter spasi `" "`, hasilnya kemudian ditampung oleh `string1`.
+
+Untuk memisah string menjadi slice tiap 1 string, gunakan pemisah string kosong `""`. Bisa dilihat contohnya pada variabel `string2`.
+
+## A.44.9. Fungsi `strings.Join()`
+
+Memiliki kegunaan berkebalikan dengan `strings.Split()`. Digunakan untuk menggabungkan slice string (parameter pertama) menjadi sebuah string dengan pemisah tertentu (parameter kedua.
+
+```go
+var data = []string{"banana", "papaya", "tomato"}
+var str = strings.Join(data, "-")
+fmt.Println(str) // "banana-papaya-tomato"
+```
+
+Slice `data` digabungkan menjadi satu dengan pemisah tanda *dash* (`-`).
+
+## A.44.10. Fungsi `strings.ToLower()`
+
+Mengubah huruf-huruf string menjadi huruf kecil.
+
+```go
+var str = strings.ToLower("aLAy")
+fmt.Println(str) // "alay"
+```
+
+## A.44.11. Fungsi `strings.ToUpper()`
+
+Mengubah huruf-huruf string menjadi huruf besar.
+
+```go
+var str = strings.ToUpper("eat!")
+fmt.Println(str) // "EAT!"
+```
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-struct.md b/en/content-en/A-struct.md
new file mode 100644
index 000000000..7d1daf2f0
--- /dev/null
+++ b/en/content-en/A-struct.md
@@ -0,0 +1,417 @@
+# A.24. Struct
+
+Go tidak mengadopsi konsep class seperti pada beberapa bahasa pemrograman OOP lainnya. Namun Go memiliki tipe data struktur Struct.
+
+Struct adalah kumpulan definisi variabel (atau property) dan atau fungsi (atau method), yang dibungkus sebagai tipe data baru dengan nama tertentu. Property dalam struct, tipe datanya bisa bervariasi. Mirip seperti `map`, hanya saja key-nya sudah didefinisikan di awal, dan tipe data tiap itemnya bisa berbeda.
+
+Dari sebuah struct, kita bisa buat variabel baru, yang memiliki atribut sesuai skema struct tersebut. Kita sepakati dalam buku ini, variabel tersebut dipanggil dengan istilah **object** atau **variabel object**.
+
+> Konsep struct di golang mirip dengan konsep **class** pada OOP, meski sebenarnya memiliki perbedaan. Di sini penulis menggunakan konsep OOP sebagai analogi, untuk mempermudah pembaca untuk memahami pembelajaran di chapter ini.
+
+Dengan memanfaatkan struct, penyimpanan data yang sifatnya kolektif menjadi lebih mudah, lebih rapi, dan mudah untuk dikelola.
+
+## A.24.1. Deklarasi Struct
+
+Kombinasi keyword `type` dan `struct` digunakan untuk deklarasi struct. Di bawah ini merupakan contoh cara penerapannya.
+
+```go
+type student struct {
+ name string
+ grade int
+}
+```
+
+Struct `student` dideklarasikan memiliki 2 property, yaitu `name` dan `grade`. Property adalah istilah untuk variabel yang menempel ke struct.
+
+## A.24.2. Penerapan Struct Untuk Membuat Object
+
+Struct `student` yang sudah disiapkan di atas kita gunakan untuk membuat variabel objek. Variabel tersebut tipe datanya adalah `student`. Kemudian dari variabel object, kita bisa mengakses isi property variabel. Contoh:
+
+```go
+func main() {
+ var s1 student
+ s1.name = "john wick"
+ s1.grade = 2
+
+ fmt.Println("name :", s1.name)
+ fmt.Println("grade :", s1.grade)
+}
+```
+
+Cara membuat variabel objek sama seperti pembuatan variabel biasa. Tinggal tulis saja nama variabel diikuti nama struct, contoh: `var s1 student`.
+
+Semua property variabel objek pada awalnya memiliki zero value sesuai tipe datanya. Misalnya, 0 untuk tipe `int`, dan string kosong `""` untuk string.
+
+Property variabel objek bisa diakses nilainya menggunakan notasi titik, contohnya `s1.name`. Nilai property-nya juga bisa diubah, contohnya `s1.grade = 2`.
+
+![Pengaksesan property variabel objek](images/A_struct_1_struct.png)
+
+## A.24.3. Inisialisasi Object Struct
+
+Cara inisialisasi variabel objek adalah dengan menuliskan nama struct yang telah dibuat diikuti dengan kurung kurawal. Nilai masing-masing property bisa diisi pada saat inisialisasi.
+
+Pada contoh berikut, terdapat 3 buah variabel objek yang dideklarasikan dengan cara berbeda.
+
+```go
+var s1 = student{}
+s1.name = "wick"
+s1.grade = 2
+
+var s2 = student{"ethan", 2}
+
+var s3 = student{name: "jason"}
+
+fmt.Println("student 1 :", s1.name)
+fmt.Println("student 2 :", s2.name)
+fmt.Println("student 3 :", s3.name)
+```
+
+Pada kode di atas, variabel `s1` menampung objek cetakan `student`. Variabel tersebut kemudian di-set nilai property-nya.
+
+Variabel objek `s2` dideklarasikan dengan metode yang sama dengan `s1`, pembedanya di `s2` nilai propertinya di isi langsung ketika deklarasi. Nilai pertama akan menjadi nilai property pertama (yaitu `name`), dan selanjutnya berurutan.
+
+Pada deklarasi `s3`, dilakukan juga pengisian property ketika pencetakan objek. Hanya saja, yang diisi hanya `name` saja. Cara ini cukup efektif jika digunakan untuk membuat objek baru yang nilai property-nya tidak semua harus disiapkan di awal. Keistimewaan lain menggunakan cara ini adalah penentuan nilai property bisa dilakukan dengan tidak berurutan. Contohnya:
+
+```go
+var s4 = student{name: "wayne", grade: 2}
+var s5 = student{grade: 2, name: "bruce"}
+```
+
+## A.24.4. Variabel Objek Pointer
+
+Objek yang dibuat dari tipe struct bisa diambil nilai pointer-nya, dan bisa disimpan pada variabel objek yang bertipe struct pointer. Contoh penerapannya:
+
+```go
+var s1 = student{name: "wick", grade: 2}
+
+var s2 *student = &s1
+fmt.Println("student 1, name :", s1.name)
+fmt.Println("student 4, name :", s2.name)
+
+s2.name = "ethan"
+fmt.Println("student 1, name :", s1.name)
+fmt.Println("student 4, name :", s2.name)
+```
+
+`s2` adalah variabel pointer hasil cetakan struct `student`. `s2` menampung nilai referensi `s1`, menjadikan setiap perubahan pada property variabel tersebut, akan juga berpengaruh pada variabel objek `s1`.
+
+Meskipun `s2` bukan variabel asli, property nya tetap bisa diakses seperti biasa. Inilah keistimewaan property dalam objek pointer, tanpa perlu di-dereferensi nilai asli property tetap bisa diakses. Pengisian nilai pada property tersebut juga bisa langsung menggunakan nilai asli, contohnya seperti `s2.name = "ethan"`.
+
+![Variabel objek pointer](images/A_struct_2_pointer_object.png)
+
+## A.24.5. Embedded Struct
+
+**Embedded** struct adalah mekanisme untuk menempelkan sebuah struct sebagai properti struct lain. Agar lebih mudah dipahami, mari kita bahas kode berikut.
+
+```go
+package main
+
+import "fmt"
+
+type person struct {
+ name string
+ age int
+}
+
+type student struct {
+ grade int
+ person
+}
+
+func main() {
+ var s1 = student{}
+ s1.name = "wick"
+ s1.age = 21
+ s1.grade = 2
+
+ fmt.Println("name :", s1.name)
+ fmt.Println("age :", s1.age)
+ fmt.Println("age :", s1.person.age)
+ fmt.Println("grade :", s1.grade)
+}
+
+```
+
+Pada kode di atas, disiapkan struct `person` dengan properti yang tersedia adalah `name` dan `age`. Disiapkan juga struct `student` dengan property `grade`. Struct `person` di-embed ke dalam struct `student`. Caranya cukup mudah, yaitu dengan menuliskan nama struct yang ingin di-embed ke dalam body `struct` target.
+
+Embedded struct adalah **mutable**, nilai property-nya nya bisa diubah.
+
+Khusus untuk properti yang bukan merupakan properti asli (melainkan properti turunan dari struct lain), pengaksesannya dilakukan dengan cara mengakses struct *parent*-nya terlebih dahulu, contohnya `s1.person.age`. Nilai yang dikembalikan memiliki referensi yang sama dengan `s1.age`.
+
+## A.24.6. Embedded Struct Dengan Nama Property Yang Sama
+
+Jika salah satu nama properti sebuah struct memiliki kesamaan dengan properti milik struct lain yang di-embed, maka pengaksesan property-nya harus dilakukan secara eksplisit atau jelas. Silakan lihat kode berikut agar lebih jelas.
+
+```go
+package main
+
+import "fmt"
+
+type person struct {
+ name string
+ age int
+}
+
+type student struct {
+ person
+ age int
+ grade int
+}
+
+func main() {
+ var s1 = student{}
+ s1.name = "wick"
+ s1.age = 21 // age of student
+ s1.person.age = 22 // age of person
+
+ fmt.Println(s1.name)
+ fmt.Println(s1.age)
+ fmt.Println(s1.person.age)
+}
+```
+
+Struct `person` di-embed ke dalam struct `student`, dan kedua struct tersebut kebetulan salah satu nama property-nya ada yang sama, yaitu `age`. Cara mengakses property `age` milik struct `person` lewat objek struct `student`, adalah dengan menuliskan nama struct yang di-embed kemudian nama property-nya, contohnya: `s1.person.age = 22`.
+
+## A.24.7. Pengisian Nilai Sub-Struct
+
+Pengisian nilai property sub-struct bisa dilakukan dengan langsung memasukkan variabel objek yang tercetak dari struct yang sama.
+
+```go
+var p1 = person{name: "wick", age: 21}
+var s1 = student{person: p1, grade: 2}
+
+fmt.Println("name :", s1.name)
+fmt.Println("age :", s1.age)
+fmt.Println("grade :", s1.grade)
+```
+
+Pada deklarasi `s1`, property `person` diisi variabel objek `p1`.
+
+## A.24.8. Anonymous Struct
+
+Anonymous struct adalah struct yang tidak dideklarasikan di awal sebagai tipe data baru, melainkan langsung ketika pembuatan objek. Teknik ini cukup efisien digunakan pada *use case* pembuatan variabel objek yang struct-nya hanya dipakai sekali.
+
+```go
+package main
+
+import "fmt"
+
+type person struct {
+ name string
+ age int
+}
+
+func main() {
+ var s1 = struct {
+ person
+ grade int
+ }{}
+ s1.person = person{"wick", 21}
+ s1.grade = 2
+
+ fmt.Println("name :", s1.person.name)
+ fmt.Println("age :", s1.person.age)
+ fmt.Println("grade :", s1.grade)
+}
+```
+
+Pada kode di atas, variabel `s1` langsung diisi objek anonymous struct yang memiliki property `grade`, dan property `person` yang merupakan embedded struct.
+
+Salah satu aturan yang perlu diingat dalam pembuatan anonymous struct adalah, deklarasi harus diikuti dengan inisialisasi. Bisa dilihat pada `s1` setelah deklarasi struktur struct, terdapat kurung kurawal untuk inisialisasi objek. Meskipun nilai tidak diisikan di awal, kurung kurawal tetap harus ditulis.
+
+```go
+// anonymous struct tanpa pengisian property
+var s1 = struct {
+ person
+ grade int
+}{}
+
+// anonymous struct dengan pengisian property
+var s2 = struct {
+ person
+ grade int
+}{
+ person: person{"wick", 21},
+ grade: 2,
+}
+```
+
+## A.24.9. Kombinasi Slice & Struct
+
+Slice dan `struct` bisa dikombinasikan seperti pada slice dan `map`, caranya penggunaannya-pun mirip, cukup tambahkan tanda `[]` sebelum tipe data pada saat deklarasi.
+
+```go
+type person struct {
+ name string
+ age int
+}
+
+var allStudents = []person{
+ {name: "Wick", age: 23},
+ {name: "Ethan", age: 23},
+ {name: "Bourne", age: 22},
+}
+
+for _, student := range allStudents {
+ fmt.Println(student.name, "age is", student.age)
+}
+```
+
+## A.24.10. Inisialisasi Slice Anonymous Struct
+
+Anonymous struct bisa dijadikan sebagai tipe sebuah slice. Dan nilai awalnya juga bisa diinisialisasi langsung pada saat deklarasi. Berikut adalah contohnya:
+
+```go
+var allStudents = []struct {
+ person
+ grade int
+}{
+ {person: person{"wick", 21}, grade: 2},
+ {person: person{"ethan", 22}, grade: 3},
+ {person: person{"bond", 21}, grade: 3},
+}
+
+for _, student := range allStudents {
+ fmt.Println(student)
+}
+```
+
+## A.24.11. Deklarasi Anonymous Struct Menggunakan Keyword **var**
+
+Cara lain untuk deklarasi anonymous struct adalah dengan menggunakan keyword `var`.
+
+```go
+var student struct {
+ person
+ grade int
+}
+
+student.person = person{"wick", 21}
+student.grade = 2
+```
+
+Statement `type student struct` adalah contoh cara deklarasi struct. Maknanya akan berbeda ketika keyword `type` diganti `var`, seperti pada contoh di atas `var student struct`, yang artinya dicetak sebuah objek dari anonymous struct kemudian disimpan pada variabel bernama `student`.
+
+Deklarasi anonymous struct menggunakan metode ini juga bisa dilakukan dengan disertai inisialisasi data.
+
+```go
+// hanya deklarasi
+var student struct {
+ grade int
+}
+
+// deklarasi sekaligus inisialisasi
+var student = struct {
+ grade int
+} {
+ 12,
+}
+```
+
+## A.24.12. Nested struct
+
+Nested struct adalah anonymous struct yang di-embed ke sebuah struct. Deklarasinya langsung di dalam struct peng-embed. Contoh:
+
+```go
+type student struct {
+ person struct {
+ name string
+ age int
+ }
+ grade int
+ hobbies []string
+}
+```
+
+Teknik ini biasa digunakan ketika decoding data **JSON** yang struktur datanya cukup kompleks dengan proses decode hanya sekali.
+
+## A.24.13. Deklarasi Dan Inisialisasi Struct Secara Horizontal
+
+Deklarasi struct bisa dituliskan secara horizontal, caranya bisa dilihat pada kode berikut:
+
+```go
+type person struct { name string; age int; hobbies []string }
+```
+
+Tanda semi-colon (`;`) digunakan sebagai pembatas deklarasi poperty yang dituliskan secara horizontal. Inisialisasi nilai juga bisa dituliskan dengan metode ini. Contohnya:
+
+```go
+var p1 = struct { name string; age int } { age: 22, name: "wick" }
+var p2 = struct { name string; age int } { "ethan", 23 }
+```
+
+## A.24.14. Tag property dalam struct
+
+Tag merupakan informasi opsional yang bisa ditambahkan pada property struct.
+
+```go
+type person struct {
+ name string `tag1`
+ age int `tag2`
+}
+```
+
+Tag biasa dimanfaatkan untuk keperluan encode/decode data. Informasi tag juga bisa diakses lewat reflect. Nantinya akan ada pembahasan yang lebih detail mengenai pemanfaatan tag dalam struct, terutama ketika sudah masuk chapter JSON.
+
+## A.24.15. Type Alias
+
+Sebuah tipe data, seperti struct, bisa dibuatkan alias baru, caranya dengan `type NamaAlias = TargetStruct`. Contoh:
+
+```go
+type Person struct {
+ name string
+ age int
+}
+type People = Person
+
+var p1 = Person{"wick", 21}
+fmt.Println(p1)
+
+var p2 = People{"wick", 21}
+fmt.Println(p2)
+```
+
+Pada kode di atas, sebuah alias bernama `People` dibuat untuk struct `Person`.
+
+Casting dari objek (yang dicetak lewat struct tertentu) ke tipe yang merupakan alias dari struct pencetak, hasilnya selalu valid. Berlaku juga sebaliknya.
+
+```go
+people := People{"wick", 21}
+fmt.Println(Person(people))
+
+person := Person{"wick", 21}
+fmt.Println(People(person))
+```
+
+Pembuatan struct baru juga bisa dilakukan lewat teknik type alias. Silakan perhatikan kode berikut.
+
+```go
+type People1 struct {
+ name string
+ age int
+}
+type People2 = struct {
+ name string
+ age int
+}
+```
+
+Struct `People1` dideklarasikan, kemudian struct alias `People2` juga dideklarasikan. Struct `People2` merupakan alias dari anonymous struct. Penggunaan teknik type alias untuk anonymous struct menghasilkan output yang ekuivalen dengan pendeklarasian struct.
+
+Teknik type alias ini tidak dirancang hanya untuk pembuatan alias pada tipe struct saja, semua jenis tipe data bisa dibuatkan alias. Contohnya seperti pada kode berikut, ada tipe data baru bernama `Number` yang merupakan alias dari tipe data `int`.
+
+```go
+type Number = int
+var num Number = 12
+```
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-time-duration.md b/en/content-en/A-time-duration.md
new file mode 100644
index 000000000..1111b131b
--- /dev/null
+++ b/en/content-en/A-time-duration.md
@@ -0,0 +1,130 @@
+# A.42. Time Duration
+
+Pada chapter ini kita akan belajar tentang tipe data untuk pengolahan durasi waktu yaitu `time.Duration`.
+
+Tipe `time.Duration` ini merepresentasikan durasi, contohnya seperti 1 menit, 2 jam 5 detik, dst. Data dengan tipe ini bisa dihasilkan dari operasi pencarian delta atau selisih dari dua buah objek `time.Time`, atau bisa juga kita buat sendiri.
+
+Tipe ini sangat berguna untuk banyak hal, salah satunya untuk *benchmarking* ataupun operasi-operasi lainnya yang membutuhkan informasi durasi waktu.
+
+## A.42.1. Praktek
+
+Mari kita bahas sambil praktek. Silakan tulis kode berikut lalu jalankan.
+
+```go
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+func main() {
+ start := time.Now()
+
+ time.Sleep(5 * time.Second)
+
+ duration := time.Since(start)
+
+ fmt.Println("time elapsed in seconds:", duration.Seconds())
+ fmt.Println("time elapsed in minutes:", duration.Minutes())
+ fmt.Println("time elapsed in hours:", duration.Hours())
+}
+```
+
+Pada kode di atas, sebuah objek waktu bernama `start` dibuat. Tepat setelah baris tersebut, ada statement `time.Sleep()` yang digunakan untuk menghentikan proses selama X, yang durasinya di-set lewat parameter fungsi tersebut. Bisa dilihat durasi yang dipilih adalah `5 * time.Second`.
+
+Tipe data durasi adalah `time.Duration`, yang sebenarnya tipe ini merupakan tipe buatan baru dari `int64`.
+
+Ada beberapa *predefined* konstanta durasi yang perlu kita ketahui:
+
+- `time.Nanosecond` yang nilainya adalah `1`
+- `time.Microsecond` yang nilainya adalah `1000`, atau `1000` x `time.Nanosecond`
+- `time.Millisecond` yang nilainya adalah `1000000`, atau `1000` x `time.Microsecond`
+- `time.Second` yang nilainya adalah `1000000000`, atau `1000` x `time.Millisecond`
+- `time.Minute` yang nilainya adalah `60000000000`, atau `60` x `time.Second`
+- `time.Hour` yang nilainya adalah `3600000000000`, atau `60` x `time.Minute`
+
+Dari list di atas bisa dicontohkan bahwa sebuah data dengan tipe `time.Duration` yang nilainya `1`, maka artinya durasi adalah **1 nanosecond**.
+
+Kembali ke pembahasan fungsi `time.Sleep()`, fungsi ini membutuhkan argumen/parameter durasi dalam bentuk `time.Duration`. Misalnya saya tulis `time.Sleep(1)` maka yang terjadi adalah, waktu statement tersebut hanya akan menghentikan proses selama **1 nanosecond** saja. Jika ingin menghentikan selama 1 detik, maka harus ditulis `time.Sleep(1000000000)`. Nah daripada menulis angka sepanjang itu, cukup saja tulis dengan `1 * time.Second`, artinya adalah 1 detik. Cukup mudah bukan.
+
+Di atas, kita gunakan `5 * time.Second` sebagai argumen `time.Sleep()`, maka dengan itu proses akan diberhentikan selama 5 detik.
+
+Sekarang jalankan program yang sudah dibuat.
+
+![Time Duration](images/A_time_duration_1.png)
+
+Bisa dilihat, hasilnya adalah semua statement di bawah `time.Sleep()` dieksekusi setelah 5 detik berlalu. Ini merupakan contoh penggunaan tipe data durasi pada fungsi `time.Sleep()`.
+
+## A.42.2. Hitung Durasi Menggunakan `time.Since()`.
+
+Pada kode di atas, variabel `duration` berisi durasi atau lama waktu antara kapan variabel `start` di-inisialisasi hingga kapan variabel `duration` ini statement-nya dieksekusi.
+
+Cara menghitung durasi bisa menggunakan `time.Since()`. Isi argumen fungsi tersebut dengan variabel bertipe waktu, maka durasi antara waktu pada argument vs ketika statement `time.Since()` akan dihitung.
+
+Pada contoh di atas, karena ada statement `time.Sleep(5 * time.Second)` maka idealnya `time.Since(start)` isinya adalah 5 detik (mungkin lebih sedikit, sekian mili/micro/nano-second, karena eksekusi statement juga butuh waktu).
+
+## A.42.3. Method milik tipe `time.Duration`
+
+Tipe `time.Duration` memiliki beberapa method yang sangat-sangat berguna untuk keperluan mengambil nilai durasinya dalam unit tertentu. Misalnya, objek durasi tersebut ingin di-ambil nilainya dalam satuan unit detik, maka gunakan `.Seconds()`. Jika ingin dalam bentuk menit, maka gunakan `.Minutes()`, dan lainnya.
+
+Pada contoh di atas, kita mengambil nilai durasi waktu dalam tiga bentuk, yaitu detik, menit, dan jam. Caranya cukup akses saja method-nya, maka kita akan langsung dapat nilainya, tanpa perlu memikirkan operasi aritmatik konversinya. Cukup mudah bukan.
+
+## A.42.4. Kalkulasi Durasi Antara 2 Objek Waktu
+
+Di atas kita sudah membahas cara hitung durasi menggunakan `time.Since()` antara sebuah objek waktu vs kapan statement di-eksekusi. Pada bagian ini, masih mirip, perbedannya adalah hitung durasi dilakukan pada 2 objek waktu.
+
+Silakan perhatikan contoh berikut. Kode berikut esensinya adalah sama dengan kode di atas.
+
+```go
+t1 := time.Now()
+time.Sleep(5 * time.Second)
+t2 := time.Now()
+
+duration := t2.Sub(t1)
+
+fmt.Println("time elapsed in seconds:", duration.Seconds())
+fmt.Println("time elapsed in minutes:", duration.Minutes())
+fmt.Println("time elapsed in hours:", duration.Hours())
+```
+
+Method `.Sub()` milik objek `time.Time` digunakan untuk mencari selisih waktu. Pada contoh di atas, durasi antara waktu `t1` dan waktu `t2` dihitung. Method `.Sub()` ini menghasilkan nilai balik bertipe `time.Duration`.
+
+## A.42.5. Konversi Angka ke `time.Duration`
+
+Kita bisa mengalikan angka literal dengan konstanta `time.Duration` untuk menciptakan variabel/objek bertipe durasi. Contohnya seperti yang sudah kita terapkan sebelumnya, yaitu `5 * time.Second` yang menghasilkan data durasi 5 detik. Contoh lainnya:
+
+```go
+12 * time.Minute // 12 menit
+65 * time.Hour // 65 jam
+150000 * time.Milisecond // 150k milidetik atau 150 detik
+45 * time.Microsecond // 45 microdetik
+233 * time.Nanosecond // 233 nano detik
+```
+
+Mengulas kembali pembahasan dasar di awal-awal chapter, operasi aritmatika di golang hanya bisa dilakukan ketika data adalah 1 tipe. Selebihnya harus ada casting atau konversi tipe data agar bisa dioperasikan.
+
+Tipe `time.Duration` diciptakan menggunakan tipe `ìnt64`. Jadi jika ingin mengalikan `time.Duration` dengan suatu angka, maka pastikan tipe-nya juga sama yaitu `time.Duration`. Jika angka tersebut tidak ditampung dalam variabel terlebih dahulu (contohnya seperti di atas) maka bisa langsung kalikan saja. Jika ditampung ke variabel terlebih dahulu, maka pastikan tipe variabelnya adalah `time.Duration`. Contoh:
+
+```go
+var n time.Duration = 5
+duration := n * time.Second
+```
+
+Atau bisa manfaatkan casting untuk mengkonversi data numerik ke tipe `time.Duration`. Contoh:
+
+```go
+n := 5
+duration := time.Duration(n) * time.Second
+```
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-time-parsing-format.md b/en/content-en/A-time-parsing-format.md
new file mode 100644
index 000000000..0bf5a5747
--- /dev/null
+++ b/en/content-en/A-time-parsing-format.md
@@ -0,0 +1,223 @@
+# A.40. Time, Parsing Time, & Format Time
+
+Pada chapter ini kita akan belajar tentang pemanfaatan data bertipe datetime serta method-method-nya, juga tentang **format** & **parsing** data `string` ke tipe `time.Time` dan sebaliknya.
+
+Go menyediakan package `time` yang berisikan banyak sekali komponen yang bisa digunakan untuk keperluan pemanfaatan date dan time. Salah satunya adalah `time.Time`, yang merupakan tipe untuk data tanggal dan waktu di Go.
+
+> Meskipun nama package-nya adalah `time`, yang dicakup adalah **date** dan **time**, jadi bukan hanya waktu saja.
+
+## A.40.1. Penggunaan `time.Time`
+
+Tipe `time.Time` merupakan representasi untuk objek date-time. Ada 2 cara yang bisa dipilih untuk membuat data bertipe ini.
+
+ 1. Menjadikan informasi waktu sekarang sebagai objek `time.Time`, menggunakan `time.Now()`.
+ 2. Atau, membuat objek baru bertipe `time.Time` dengan informasi ditentukan sendiri, menggunakan `time.Date()`.
+
+Berikut merupakan contoh penggunannya.
+
+```go
+package main
+
+import "fmt"
+import "time"
+
+func main() {
+ var time1 = time.Now()
+ fmt.Printf("time1 %v\n", time1)
+ // time1 2015-09-01 17:59:31.73600891 +0700 WIB
+
+ var time2 = time.Date(2011, 12, 24, 10, 20, 0, 0, time.UTC)
+ fmt.Printf("time2 %v\n", time2)
+ // time2 2011-12-24 10:20:00 +0000 UTC
+}
+```
+
+Fungsi `time.Now()` mengembalikan objek `time.Time` dengan nilai adalah informasi date-time tepat ketika statement tersebut dijalankan. Bisa dilihat pada saat di-print muncul informasi date-time sesuai dengan tanggal program tersebut dieksekusi.
+
+![Penggunaan time](images/A_time_parsing_format_1_time_instance.png)
+
+Fungsi `time.Date()` digunakan untuk membuat objek `time.Time` baru yang informasi date-time-nya kita tentukan sendiri. Fungsi ini memiliki 8 buah parameter *mandatory* dengan skema bisa dilihat di kode berikut:
+
+```go
+time.Date(tahun, bulan, tanggal, jam, menit, detik, nanodetik, timezone)
+```
+
+Objek cetakan fungsi `time.Now()` memiliki timezone yang relatif terhadap lokasi kita. Karena kebetulan penulis berlokasi di Jawa Timur, maka akan terdeteksi masuk dalam **GMT+7** atau **WIB**. Berbeda dengan variabel `time2` yang lokasinya sudah kita tentukan secara eksplisit yaitu **UTC**.
+
+Selain menggunakan `time.UTC` untuk penentuan lokasi, tersedia juga `time.Local` yang nilainya adalah relatif terhadap date-time lokal kita.
+
+## A.40.2. Method Milik `time.Time`
+
+Tipe data `time.Time` merupakan struct, memiliki beberapa method yang bisa dipakai.
+
+```go
+var now = time.Now()
+fmt.Println("year:", now.Year(), "month:", now.Month())
+// year: 2015 month: 8
+```
+
+Kode di atas adalah contoh penggunaan beberapa method milik objek bertipe `time.Time`. Method `Year()` mengembalikan informasi tahun, dan `Month()` mengembalikan informasi angka bulan.
+
+Selain kedua method di atas, ada banyak lagi yang bisa dimanfaatkan. Tabel berikut merupakan list method yang berhubungan dengan *date*, *time*, dan *location* yang dimiliki tipe `time.Time`.
+
+| Method | Return Type | Penjelasan |
+|:------------------ |:----------------- |:----------------------------------------------------------------------------------------------------------------------------------- |
+| `now.Year()` | `int` | Tahun |
+| `now.YearDay()` | `int` | Hari ke-? di mulai awal tahun |
+| `now.Month()` | `int` | Bulan |
+| `now.Weekday()` | `string` | Nama hari. Bisa menggunakan `now.Weekday().String()` untuk mengambil bentuk string-nya |
+| `now.ISOWeek()` | (`int`, `int`) | Tahun dan minggu ke-? mulai awal tahun |
+| `now.Day()` | `int` | Tanggal |
+| `now.Hour()` | `int` | Jam |
+| `now.Minute()` | `int` | Menit |
+| `now.Second()` | `int` | Detik |
+| `now.Nanosecond()` | `int` | Nano detik |
+| `now.Local()` | `time.Time` | Date-time dalam timezone lokal |
+| `now.Location()` | `*time.Location` | Mengambil informasi lokasi, apakah *local* atau *utc*. Bisa menggunakan `now.Location().String()` untuk mengambil bentuk string-nya |
+| `now.Zone()` | (`string`, `int`) | Mengembalikan informasi *timezone offset* dalam string dan numerik. Sebagai contoh `WIB, 25200` |
+| `now.IsZero()` | `bool` | Deteksi apakah nilai object `now` adalah `01 Januari tahun 1, 00:00:00 UTC`. Jika iya maka bernilai `true` |
+| `now.UTC()` | `time.Time` | Date-time dalam timezone `UTC` |
+| `now.Unix()` | `int64` | Date-time dalam format *unix time* |
+| `now.UnixNano()` | `int64` | Date-time dalam format *unix time*. Infomasi nano detik juga dimasukkan |
+| `now.String()` | `string` | Date-time dalam string |
+
+## A.40.3. Parsing dari `string` ke `time.Time`
+
+Data `string` bisa dikonversi menjadi `time.Time` dengan memanfaatkan `time.Parse`. Fungsi ini membutuhkan 2 parameter:
+
+ - Parameter ke-1 adalah layout format dari data waktu yang akan diparsing.
+ - Parameter ke-2 adalah data string yang ingin diparsing.
+
+Contoh penerapannya bisa dilihat di kode berikut.
+
+```go
+var layoutFormat, value string
+var date time.Time
+
+layoutFormat = "2006-01-02 15:04:05"
+value = "2015-09-02 08:04:00"
+date, _ = time.Parse(layoutFormat, value)
+fmt.Println(value, "\t->", date.String())
+// 2015-09-02 08:04:00 +0000 UTC
+
+layoutFormat = "02/01/2006 MST"
+value = "02/09/2015 WIB"
+date, _ = time.Parse(layoutFormat, value)
+fmt.Println(value, "\t\t->", date.String())
+// 2015-09-02 00:00:00 +0700 WIB
+```
+
+![Contoh penggunaan **time.Parse**](images/A_time_parsing_format_2_time_parse.png)
+
+Layout format date-time di Go berbeda dibanding bahasa lain. Umumnya layout format yang digunakan adalah seperti `"DD/MM/YYYY"`, di Go tidak.
+
+Go memiliki standar layout format yang cukup unik, contohnya seperti pada kode di atas `"2006-01-02 15:04:05"`. Go menggunakan `2006` untuk parsing tahun, bukan `YYYY`; `01` untuk parsing bulan; `02` untuk parsing hari; dan seterusnya. Detailnya bisa dilihat di tabel berikut.
+
+| Layout Format | Penjelasan | Contoh Data |
+|:------------------ |:---------------------------------------------------------------------------- |:------------------------------- |
+| `2006` | Tahun 4 digit | `2015` |
+| `006` | Tahun 3 digit | `015` |
+| `06` | Tahun 2 digit | `15` |
+| `01` | Bulan 2 digit | `05` |
+| `1` | Bulan 1 digit jika di bawah bulan 10, selainnya 2 digit | `5`, `12` |
+| `January` | Nama bulan dalam bahasa inggris | `September`, `August` |
+| `Jan` | Nama bulan dalam bahasa inggris, 3 huruf | `Sep`, `Aug` |
+| `02` | Tanggal 2 digit | `02` |
+| `2` | Tanggal 1 digit jika di bawah bulan 10, selainnya 2 digit | `8`, `31` |
+| `Monday` | Nama hari dalam bahasa inggris | `Saturday`, `Friday` |
+| `Mon` | Nama hari dalam bahasa inggris, 3 huruf | `Sat`, `Fri` |
+| `15` | Jam dengan format **24 jam** | `18` |
+| `03` | Jam dengan format **12 jam** 2 digit | `05`, `11` |
+| `3` | Jam dengan format **12 jam** 1 digit jika di bawah jam 11, selainnya 2 digit | `5`, `11` |
+| `PM` | AM/PM, biasa digunakan dengan format jam **12 jam** | `PM`, `AM` |
+| `04` | Menit 2 digit | `08` |
+| `4` | Menit 1 digit jika di bawah menit 10, selainnya 2 digit | `8`, `24` |
+| `05` | Detik 2 digit | `06` |
+| `5` | Detik 1 digit jika di bawah detik 10, selainnya 2 digit | `6`, `36` |
+| `999999` | Nano detik | `124006` |
+| `MST` | Lokasi timezone | `UTC`, `WIB`, `EST` |
+| `Z0700` | Offset timezone | `Z`, `+0700`, `-0200` |
+
+## A.40.4. Predefined Layout Format Untuk Keperluan Parsing Time
+
+Go juga menyediakan beberapa predefined layout format umum yang bisa dimanfaatkan. Jadi tidak perlu menuliskan kombinasi komponen-komponen layout format.
+
+Salah satu predefined layout yang bisa digunakan adalah `time.RFC822`, ekuivalen dengan layout format `02 Jan 06 15:04 MST`. Berikut adalah contoh penerapannya.
+
+```go
+var date, _ = time.Parse(time.RFC822, "02 Sep 15 08:00 WIB")
+fmt.Println(date.String())
+// 2015-09-02 08:00:00 +0700 WIB
+```
+
+Ada beberapa layout format lain yang tersedia, silakan lihat tabel berikut.
+
+| Predefined Layout Format | Layout Format |
+|:------------------------ |:----------------------------------- |
+| `time.ANSIC` | Mon Jan _2 15:04:05 2006 |
+| `time.UnixDate` | Mon Jan _2 15:04:05 MST 2006 |
+| `time.RubyDate` | Mon Jan 02 15:04:05 -0700 2006 |
+| `time.RFC822` | 02 Jan 06 15:04 MST |
+| `time.RFC822Z` | 02 Jan 06 15:04 -0700 |
+| `time.RFC850` | Monday, 02-Jan-06 15:04:05 MST |
+| `time.RFC1123` | Mon, 02 Jan 2006 15:04:05 MST |
+| `time.RFC1123Z` | Mon, 02 Jan 2006 15:04:05 -0700 |
+| `time.RFC3339` | 2006-01-02T15:04:05Z07:00 |
+| `time.RFC3339Nano` | 2006-01-02T15:04:05.999999999Z07:00 |
+| `time.Kitchen` | 3:04PM |
+| `time.Stamp` | Jan _2 15:04:05 |
+| `time.StampMilli` | Jan _2 15:04:05.000 |
+| `time.StampMicro` | Jan _2 15:04:05.000000 |
+| `time.StampNano` | Jan _2 15:04:05.000000000 |
+
+## A.40.5. Format dari `time.Time` ke `string`
+
+Setelah sebelumnya kita belajar tentang cara konversi data dengan tipe `string` ke `time.Time`. Kali ini kita akan belajar kebalikannya, konversi `time.Time` ke `string`.
+
+Method `Format()` milik tipe `time.Time` digunakan untuk membentuk output `string` sesuai dengan layout format yang diinginkan. Contoh bisa dilihat pada kode berikut.
+
+```go
+var date, _ = time.Parse(time.RFC822, "02 Sep 15 08:00 WIB")
+
+var dateS1 = date.Format("Monday 02, January 2006 15:04 MST")
+fmt.Println("dateS1", dateS1)
+// Wednesday 02, September 2015 08:00 WIB
+
+var dateS2 = date.Format(time.RFC3339)
+fmt.Println("dateS2", dateS2)
+// 2015-09-02T08:00:00+07:00
+```
+
+Variabel `date` di atas berisikan hasil parsing data dengan format `time.RFC822`. Data tersebut kemudian diformat sebagai string 2 kali dengan layout format berbeda.
+
+![Contoh penggunaan method
+ Format()
](images/A_time_parsing_format_3_time_format.png)
+
+## A.40.6. Handle Error Parsing `time.Time`
+
+Parsing `string` ke `time.Time` memungkinkan terjadinya error, misalnya karena struktur data yang akan di-parse tidak sesuai layout format yang digunakan. Error-tidaknya parsing bisa diketahui lewat nilai kembalian ke-2 fungsi `time.Parse()`. Contoh:
+
+```go
+var date, err = time.Parse("06 Jan 15", "02 Sep 15 08:00 WIB")
+
+if err != nil {
+ fmt.Println("error", err.Error())
+ return
+}
+
+fmt.Println(date)
+```
+
+Kode di atas menghasilkan error karena format tidak sesuai dengan skema data yang akan diparsing. Layout format yang seharusnya digunakan adalah `06 Jan 15 03:04 MST`.
+
+![Error parsing time.Time](images/A_time_parsing_format_4_error_parse.png)
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-timer-ticker-scheduler.md b/en/content-en/A-timer-ticker-scheduler.md
new file mode 100644
index 000000000..0ce1e5a1b
--- /dev/null
+++ b/en/content-en/A-timer-ticker-scheduler.md
@@ -0,0 +1,206 @@
+# A.41. Timer, Ticker, & Scheduler
+
+Ada beberapa fungsi dalam package `time` yang bisa dimanfaatkan untuk operasi penundaan eksekusi, countdown timer, dan pengaturan jadwal eksekusi sebuah proses.
+
+## A.41.1. Fungsi `time.Sleep()`
+
+Fungsi ini digunakan untuk menghentikan program sejenak. `time.Sleep()` bersifat **blocking**, statement di bawahnya tidak akan dieksekusi sampai pemberhentian usai. Contoh sederhana penerapan bisa dilihat pada kode berikut.
+
+```go
+package main
+
+import "fmt"
+import "time"
+
+func main () {
+ fmt.Println("start")
+ time.Sleep(time.Second * 4)
+ fmt.Println("after 4 seconds")
+}
+```
+
+Hasilnya, tulisan `"start"` muncul, lalu 4 detik kemudian tulisan `"after 4 seconds"` muncul.
+
+## A.41.2. Scheduler Menggunakan `time.Sleep()`
+
+Selain untuk blocking proses, fungsi `time.Sleep()` ini bisa dimanfaatkan untuk membuat scheduler sederhana, contohnya seperti berikut, scheduler untuk menampilkan pesan halo setiap 1 detik.
+
+```go
+for true {
+ fmt.Println("Hello !!")
+ time.Sleep(1 * time.Second)
+}
+```
+
+## A.41.3. Fungsi `time.NewTimer()`
+
+Fungsi ini sedikit berbeda dengan `time.Sleep()`. Fungsi `time.NewTimer()` mengembalikan objek bertipe `*time.Timer` yang memiliki property `C` yang bertipe channel.
+
+Cara kerja fungsi ini: setelah jeda waktu yang ditentukan sebuah data akan dikirimkan lewat channel `C`. Penggunaan fungsi ini harus diikuti dengan statement untuk penerimaan data dari channel `C`.
+
+Untuk lebih jelasnya silakan perhatikan kode berikut.
+
+```go
+var timer = time.NewTimer(4 * time.Second)
+fmt.Println("start")
+<-timer.C
+fmt.Println("finish")
+```
+
+Statement `var timer = time.NewTimer(4 * time.Second)` mengindikasikan bahwa nantinya akan ada data yang dikirimkan ke channel `timer.C` setelah 4 detik berlalu. Baris kode `<-timer.C` menandakan penerimaan data dari channel `timer.C`. Karena penerimaan channel sendiri sifatnya adalah blocking, maka statement `fmt.Println("finish")` baru akan dieksekusi setelah **4 detik**.
+
+Hasil program di atas adalah tulisan `"start"` muncul, lalu setelah 4 detik tulisan `"finish"` muncul.
+
+## A.41.4. Fungsi `time.AfterFunc()`
+
+Fungsi `time.AfterFunc()` memiliki 2 parameter. Parameter pertama adalah durasi timer, dan parameter kedua adalah *callback* nya. Callback tersebut akan dieksekusi jika waktu sudah memenuhi durasi timer.
+
+```go
+var ch = make(chan bool)
+
+time.AfterFunc(4*time.Second, func() {
+ fmt.Println("expired")
+ ch <- true
+})
+
+fmt.Println("start")
+<-ch
+fmt.Println("finish")
+```
+
+Hasil dari kode di atas, tulisan `"start"` muncul kemudian setelah 4 detik berlalu, tulisan `"expired"` muncul.
+
+Di dalam callback terdapat proses transfer data lewat channel, menjadikan tulisan `"finish"` akan muncul tepat setelah tulisan `"expired"` muncul.
+
+Beberapa hal yang perlu diketahui ketika menggunakan fungsi ini:
+
+ - Jika tidak ada serah terima data lewat channel, maka eksekusi `time.AfterFunc()` adalah asynchronous (tidak blocking).
+ - Jika ada serah terima data lewat channel, maka fungsi akan tetap berjalan asynchronous hingga baris kode di mana penerimaan data channel dilakukan. Proses blocking nya berada pada baris kode penerimaan channel.
+
+## A.41.5. Fungsi `time.After()`
+
+Kegunaan fungsi ini mirip seperti `time.Sleep()`. Perbedaannya adalah, fungsi `timer.After()` akan mengembalikan data channel, sehingga perlu menggunakan tanda `<-` dalam penerapannya.
+
+```go
+<-time.After(4 * time.Second)
+fmt.Println("expired")
+```
+
+Tulisan `"expired"` akan muncul setelah 4 detik.
+
+## A.41.6. Scheduler Menggunakan Ticker
+
+Selain fungsi-fungsi untuk keperluan timer, Go juga menyediakan fungsi scheduler (yang di sini kita sebut sebagai ticker).
+
+Cara penggunaan ticker cukup mudah, buat objek ticker baru menggunakan `time.NewTicker()` isi argument dengan durasi yang diinginkan. Dari objek tersebut kita bisa akses properti `.C` yang merupakan channel. Setiap durasi yang sudah ditentukan, objek ticker akan mengirimkan informasi date-time via channel tersebut.
+
+```go
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+func main() {
+ done := make(chan bool)
+ ticker := time.NewTicker(time.Second)
+
+ go func() {
+ time.Sleep(10 * time.Second) // wait for 10 seconds
+ done <- true
+ }()
+
+ for {
+ select {
+ case <-done:
+ ticker.Stop()
+ return
+ case t := <-ticker.C:
+ fmt.Println("Hello !!", t)
+ }
+ }
+}
+
+```
+
+Pada contoh di atas bisa dilihat, selain ticker disiapkan juga variabel channel `done`. Variabel ini kita gunakan untuk mengontrol kapan ticker harus di stop.
+
+Cara kerja program di atas: teknik `for` - `select` pada channel digunakan untuk mengecek penerimaan data dari channel `done` dan `ticker.C`. By default, channel `ticker.C` akan menerima kiriman data setiap `X` duration yang mana pada kode di atas adalah 1 detik (lihat argumen inisialisasi objek ticker).
+
+Data yang dikirimkan via channel `ticker.C` adalah data date-time kapan event itu terjadi. Pada kode di atas, setiap ada kiriman data via channel tersebut kita tampilkan.
+
+Sebelum blok kode perulangan `for`, bisa kita lihat ada goroutine baru di-dispatch, isinya adalah mengirim data ke channel `done` setelah 10 detik. Data tersebut nantinya akan diterima oleh blok kode `for` - `select`, dan ketika itu terjadi, method `.Stop()` milik objek ticker dipanggil untuk menonaktifkan scheduler pada ticker tersebut.
+
+Jadi, selama 10 detik, di setiap detiknya akan muncul pesan halo.
+
+## A.41.7. Kombinasi Timer & Goroutine
+
+Berikut merupakan contoh penerapan timer dan goroutine. Program di bawah ini adalah program tanya-jawab sederhana. Sebuah pertanyaan muncul dan user harus menginputkan jawaban dalam waktu tidak lebih dari 5 detik. Jika 5 detik berlalu dan belum ada jawaban, maka akan muncul pesan *time out*.
+
+OK langsung saja, mari kita buat programnya, pertama, import package yang diperlukan.
+
+```go
+package main
+
+import "fmt"
+import "os"
+import "time"
+```
+
+Buat fungsi `timer()`, nantinya fungsi ini dieksekusi sebagai goroutine. Di dalam fungsi `timer()` terdapat blok kode jika waktu sudah mencapai `timeout`, maka sebuah data dikirimkan lewat channel `ch`.
+
+```go
+func timer(timeout int, ch chan<- bool) {
+ time.AfterFunc(time.Duration(timeout)*time.Second, func() {
+ ch <- true
+ })
+}
+```
+
+Siapkan juga fungsi `watcher()`. Fungsi ini juga akan dieksekusi sebagai goroutine. Tugasnya cukup sederhana, yaitu menerima data dari channel `ch` (jika ada penerimaan data, berarti sudah masuk waktu timeout), lalu menampilkan pesan bahwa waktu telah habis.
+
+```go
+func watcher(timeout int, ch <-chan bool) {
+ <-ch
+ fmt.Println("\ntime out! no answer more than", timeout, "seconds")
+ os.Exit(0)
+}
+```
+
+Terakhir, buat implementasi di fungsi `main()`.
+
+```go
+func main() {
+ var timeout = 5
+ var ch = make(chan bool)
+
+ go timer(timeout, ch)
+ go watcher(timeout, ch)
+
+ var input string
+ fmt.Print("what is 725/25 ? ")
+ fmt.Scan(&input)
+
+ if input == "29" {
+ fmt.Println("the answer is right!")
+ } else {
+ fmt.Println("the answer is wrong!")
+ }
+}
+```
+
+Ketika user tidak menginputkan apa-apa dalam kurun waktu 5 detik, pesan timeout muncul lalu program berhenti.
+
+![Penerapan timer dalam goroutine](images/A_timer_ticker_scheduler_1_timer.png)
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-tipe-data.md b/en/content-en/A-tipe-data.md
new file mode 100644
index 000000000..4c044ca83
--- /dev/null
+++ b/en/content-en/A-tipe-data.md
@@ -0,0 +1,130 @@
+# A.10. Tipe Data
+
+Go mengenal beberapa jenis tipe data, di antaranya adalah tipe data numerik (desimal & non-desimal), string, dan boolean.
+
+Pada pembahasan-pembahasan sebelumnya secara tak sadar kita sudah mengaplikasikan beberapa tipe data, diantaranya ada `string` dan tipe numerik `int`.
+
+Pada chapter ini akan dijelaskan beberapa macam tipe data standar yang disediakan oleh Go, disertai juga contohnya.
+
+## A.10.1. Tipe Data Numerik Non-Desimal
+
+Tipe data numerik non-desimal atau **non floating point** di Go ada beberapa jenis. Secara umum ada 2 tipe data kategori ini yang perlu diketahui.
+
+ - `uint`, tipe data untuk bilangan cacah (bilangan positif).
+ - `int`, tipe data untuk bilangan bulat (bilangan negatif dan positif).
+
+Kedua tipe data di atas kemudian dibagi lagi menjadi beberapa jenis, dengan pembagian berdasarkan lebar cakupan nilainya, detailnya bisa dilihat di tabel berikut.
+
+| Tipe data | Cakupan bilangan |
+|:---------:|:----------------------------------------------------- |
+| `uint8` | 0 ↔ 255 |
+| `uint16` | 0 ↔ 65535 |
+| `uint32` | 0 ↔ 4294967295 |
+| `uint64` | 0 ↔ 18446744073709551615 |
+| `uint` | sama dengan `uint32` atau `uint64` (tergantung nilai) |
+| `byte` | sama dengan `uint8` |
+| `int8` | -128 ↔ 127 |
+| `int16` | -32768 ↔ 32767 |
+| `int32` | -2147483648 ↔ 2147483647 |
+| `int64` | -9223372036854775808 ↔ 9223372036854775807 |
+| `int` | sama dengan `int32` atau `int64` (tergantung nilai) |
+| `rune` | sama dengan `int32` |
+
+Dianjurkan untuk tidak sembarangan dalam menentukan tipe data variabel, sebisa mungkin tipe yang dipilih harus disesuaikan dengan nilainya, karena efeknya adalah ke alokasi memori variabel. Pemilihan tipe data yang tepat akan membuat pemakaian memori lebih optimal, tidak berlebihan.
+
+```go
+var positiveNumber uint8 = 89
+var negativeNumber = -1243423644
+
+fmt.Printf("bilangan positif: %d\n", positiveNumber)
+fmt.Printf("bilangan negatif: %d\n", negativeNumber)
+```
+
+Variabel `positiveNumber` bertipe `uint8` dengan nilai awal `89`. Sedangkan variabel `negativeNumber` dideklarasikan dengan nilai awal `-1243423644`. Compiler secara cerdas akan menentukan tipe data variabel tersebut sebagai `int32` (karena angka tersebut masuk ke cakupan tipe data `int32`).
+
+String format `%d` pada `fmt.Printf()` digunakan untuk memformat data numerik non-desimal.
+
+## A.10.2. Tipe Data Numerik Desimal
+
+Tipe data numerik desimal yang perlu diketahui ada 2, `float32` dan `float64`. Perbedaan kedua tipe data tersebut berada di lebar cakupan nilai desimal yang bisa ditampung. Untuk lebih jelasnya bisa merujuk ke spesifikasi [IEEE-754 32-bit floating-point numbers](http://www.h-schmidt.net/FloatConverter/IEEE754.html).
+
+```go
+var decimalNumber = 2.62
+
+fmt.Printf("bilangan desimal: %f\n", decimalNumber)
+fmt.Printf("bilangan desimal: %.3f\n", decimalNumber)
+```
+
+Pada kode di atas, variabel `decimalNumber` akan memiliki tipe data `float32`, karena nilainya berada di cakupan tipe data tersebut.
+
+![Tipe data numerik desimal](images/A_tipe_data_1_decimal_data_type.png)
+
+String format `%f` digunakan untuk memformat data numerik desimal menjadi string. Digit desimal yang akan dihasilkan adalah **6 digit**. Pada contoh di atas, hasil format variabel `decimalNumber` adalah `2.620000`. Jumlah digit yang muncul bisa dikontrol menggunakan `%.nf`, tinggal ganti `n` dengan angka yang diinginkan. Contoh: `%.3f` maka akan menghasilkan 3 digit desimal, `%.10f` maka akan menghasilkan 10 digit desimal.
+
+## A.10.3. Tipe Data `bool` (Boolean)
+
+Tipe data `bool` berisikan hanya 2 variansi nilai, `true` dan `false`. Tipe data ini biasa dimanfaatkan dalam seleksi kondisi dan perulangan (yang nantinya akan kita bahas pada [A.13. Seleksi Kondisi](/A-seleksi-kondisi.html) dan [A.14. Perulangan](/A-perulangan.html)).
+
+```go
+var exist bool = true
+fmt.Printf("exist? %t \n", exist)
+```
+
+Gunakan `%t` untuk memformat data `bool` menggunakan fungsi `fmt.Printf()`.
+
+## A.10.4. Tipe Data `string`
+
+Ciri khas dari tipe data string adalah nilainya di apit oleh tanda *quote* atau petik dua (`"`). Contoh penerapannya:
+
+```go
+var message string = "Halo"
+fmt.Printf("message: %s \n", message)
+```
+Selain menggunakan tanda quote, deklarasi string juga bisa dengan tanda *grave accent/backticks* (
+ `
), tanda ini terletak di sebelah kiri tombol 1. Keistimewaan string yang dideklarasikan menggunakan backtics adalah membuat semua karakter di dalamnya **tidak di escape**, termasuk`\n`, tanda petik dua dan tanda petik satu, baris baru, dan lainnya. Semua akan terdeteksi sebagai string.
+
+```go
+var message = `Nama saya "John Wick".
+Salam kenal.
+Mari belajar "Golang".`
+
+fmt.Println(message)
+```
+
+Ketika dijalankan, output akan muncul sama persis sesuai nilai variabel `message` di atas. Tanda petik dua akan muncul, baris baru juga muncul, sama persis.
+
+![String menggunakan grave accent](images/A_tipe_data_2_unescaped_string.png)
+
+## A.10.5. Nilai `nil` & Zero Value
+
+`nil` bukan merupakan tipe data, melainkan sebuah nilai. Variabel yang isi nilainya `nil` berarti memiliki nilai kosong.
+
+Semua tipe data yang sudah dibahas di atas memiliki zero value (nilai default tipe data). Artinya meskipun variabel dideklarasikan dengan tanpa nilai awal, tetap akan ada nilai default-nya.
+
+ - Zero value dari `string` adalah `""` (string kosong).
+ - Zero value dari `bool` adalah `false`.
+ - Zero value dari tipe numerik non-desimal adalah `0`.
+ - Zero value dari tipe numerik desimal adalah `0.0`.
+
+Selain tipe data yang disebutkan di atas, ada juga tipe data lain yang zero value-nya adalah `nil`. `Nil` merepresentasikan nilai kosong, benar-benar kosong. `nil` tidak bisa digunakan pada tipe data yang sudah dibahas di atas.
+
+Beberapa tipe data yang bisa di-set nilainya dengan `nil`, di antaranya:
+
+- pointer
+- tipe data fungsi
+- slice
+- `map`
+- `channel`
+- interface kosong atau `any` (yang merupakan alias dari `interface{}`)
+
+Nantinya kita akan sering bertemu dengan nilai `nil` setelah masuk pada pembahasan-pembahasan tersebut.
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-unit-test.md b/en/content-en/A-unit-test.md
new file mode 100644
index 000000000..215adb70a
--- /dev/null
+++ b/en/content-en/A-unit-test.md
@@ -0,0 +1,196 @@
+# A.58. Unit Test
+
+Go menyediakan package `testing` yang isinya banyak sekali API untuk keperluan pembuatan test file.
+
+Pada chapter ini kita akan belajar mengenai testing, benchmark, dan juga testing menggunakan *3rd party* [testify](https://github.com/stretchr/testify).
+
+## A.58.1. Persiapan
+
+Langsung saja kita praktek. Pertama siapkan terlebih dahulu sebuah struct `Kubus`. Variabel object hasil struct ini nantinya kita gunakan sebagai bahan testing.
+
+```go
+package main
+
+import "math"
+
+type Kubus struct {
+ Sisi float64
+}
+
+func (k Kubus) Volume() float64 {
+ return math.Pow(k.Sisi, 3)
+}
+
+func (k Kubus) Luas() float64 {
+ return math.Pow(k.Sisi, 2) * 6
+}
+
+func (k Kubus) Keliling() float64 {
+ return k.Sisi * 12
+}
+```
+
+Simpan kode di atas dengan nama `bab55.go`.
+
+## A.58.2. File Testing
+
+Di Go, file untuk keperluan testing dipisah dengan file utama. Nama file testing harus berakhiran `_test.go`, dan harus ditempatkan di package yang sama seperti source code yang akan di-test. Pada chapter ini, file utama adalah `bab55.go`, maka file testing harus bernama `bab55_test.go`.
+
+Unit test di Go dituliskan dalam bentuk fungsi, yang memiliki parameter yang bertipe `*testing.T`, dengan nama fungsi harus diawali kata **Test** (pastikan sudah meng-import package `testing` sebelumnya). Lewat parameter tersebut, kita bisa mengakses method-method untuk keperluan testing.
+
+Pada contoh di bawah ini disiapkan 3 buah fungsi test, yang masing-masing digunakan untuk mengecek apakah hasil kalkulasi volume, luas, dan keliling kubus adalah benar.
+
+```go
+package main
+
+import "testing"
+
+var (
+ kubus Kubus = Kubus{4}
+ volumeSeharusnya float64 = 64
+ luasSeharusnya float64 = 96
+ kelilingSeharusnya float64 = 48
+)
+
+func TestHitungVolume(t *testing.T) {
+ t.Logf("Volume : %.2f", kubus.Volume())
+
+ if kubus.Volume() != volumeSeharusnya {
+ t.Errorf("SALAH! harusnya %.2f", volumeSeharusnya)
+ }
+}
+
+func TestHitungLuas(t *testing.T) {
+ t.Logf("Luas : %.2f", kubus.Luas())
+
+ if kubus.Luas() != luasSeharusnya {
+ t.Errorf("SALAH! harusnya %.2f", luasSeharusnya)
+ }
+}
+
+func TestHitungKeliling(t *testing.T) {
+ t.Logf("Keliling : %.2f", kubus.Keliling())
+
+ if kubus.Keliling() != kelilingSeharusnya {
+ t.Errorf("SALAH! harusnya %.2f", kelilingSeharusnya)
+ }
+}
+```
+
+Method `t.Logf()` digunakan untuk memunculkan log. Method ini equivalen dengan `fmt.Printf()`.
+
+Method `Errorf()` digunakan untuk memunculkan log dengan diikuti keterangan bahwa terjadi **fail** pada saat testing.
+
+Cara eksekusi testing adalah menggunakan command `go test`. Karena struct yang diuji berada dalam file `bab55.go`, maka pada saat eksekusi test menggunakan `go test`, nama file `bab55_test.go` dan `bab55.go` perlu dituliskan sebagai argument.
+
+Argument `-v` atau verbose digunakan menampilkan semua output log pada saat pengujian.
+
+Jalankan aplikasi seperti gambar di bawah ini, terlihat bahwa tidak ada test yang fail.
+
+![Testing](images/A_unit_test_1_test.png)
+
+OK, selanjutnya coba ubah rumus kalkulasi method `Keliling()`. Tujuan dari pengubahan ini adalah untuk mengetahui bagaimana penanda fail muncul ketika ada test yang gagal.
+
+```go
+func (k Kubus) Keliling() float64 {
+ return k.Sisi * 15
+}
+```
+
+Setelah itu jalankan lagi test.
+
+![Test fail](images/A_unit_test_2_test_fail.png)
+
+## A.58.3. Method Test
+
+Table berikut berisikan method standar testing yang bisa digunakan di Go.
+
+| Method | Kegunaan |
+|:------------ |:---------------------------------------------------------------------------- |
+| `Log()` | Menampilkan log |
+| `Logf()` | Menampilkan log menggunakan format |
+| `Fail()` | Menandakan terjadi `Fail()` dan proses testing fungsi tetap diteruskan |
+| `FailNow()` | Menandakan terjadi `Fail()` dan proses testing fungsi dihentikan |
+| `Failed()` | Menampilkan laporan fail |
+| `Error()` | `Log()` diikuti dengan `Fail()` |
+| `Errorf()` | `Logf()` diikuti dengan `Fail()` |
+| `Fatal()` | `Log()` diikuti dengan `failNow()` |
+| `Fatalf()` | `Logf()` diikuti dengan `failNow()` |
+| `Skip()` | `Log()` diikuti dengan `SkipNow()` |
+| `Skipf()` | `Logf()` diikuti dengan `SkipNow()` |
+| `SkipNow()` | Menghentikan proses testing fungsi, dilanjutkan ke testing fungsi setelahnya |
+| `Skiped()` | Menampilkan laporan skip |
+| `Parallel()` | Menge-set bahwa eksekusi testing adalah parallel |
+
+## A.58.4. Benchmark
+
+Package `testing` selain berisikan tools untuk testing juga berisikan tools untuk benchmarking. Cara pembuatan benchmark sendiri cukup mudah yaitu dengan membuat fungsi yang namanya diawali dengan **Benchmark** dan parameternya bertipe `*testing.B`.
+
+Sebagai contoh, kita akan mengetes performa perhitungan luas kubus. Siapkan fungsi dengan nama `BenchmarkHitungLuas()` dengan isi adalah kode berikut.
+
+```go
+func BenchmarkHitungLuas(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ kubus.Luas()
+ }
+}
+```
+
+Jalankan test menggunakan argument `-bench=.`, argumen ini digunakan untuk menandai bahwa selain testing terdapat juga benchmark yang perlu diuji.
+
+![Benchmark](images/A_unit_test_3_benchmark.png)
+
+Arti dari `30000000 51.1 ns/op` adalah, fungsi di atas di-test sebanyak **30 juta** kali, hasilnya membutuhkan waktu rata-rata **51 nano detik** untuk run satu fungsi.
+
+## A.58.5. Testing Menggunakan testify
+
+Package **testify** merupakan salah satu dari sekian banyak *3rd party* yang tersedia untuk keperluan testing di Go. Testify bisa di-download di [github.com/stretchr/testify](https://github.com/stretchr/testify) menggunakan perintah `go get`.
+
+Dalam testify terdapat 5 package yang masing-masing memiliki kegunaan berbeda-beda satu sama lain. Detailnya bisa dilihat pada tabel berikut.
+
+| Package | Kegunaan |
+|:--------- |:--------------------------------------------------------------------------------------------------- |
+| `assert` | Berisikan tools standar untuk testing |
+| `http` | Berisikan tools untuk keperluan testing http |
+| `mock` | Berisikan tools untuk mocking object |
+| `require` | Sama seperti assert, hanya saja jika terjadi fail pada saat test akan menghentikan eksekusi program |
+| `suite` | Berisikan tools testing yang berhubungan dengan struct dan method |
+
+Pada chapter ini akan kita contohkan bagaimana penggunaan package `assert`. Silakan perhatikan contoh berikut.
+
+```go
+import "github.com/stretchr/testify/assert"
+
+...
+
+func TestHitungVolume(t *testing.T) {
+ assert.Equal(t, kubus.Volume(), volumeSeharusnya, "perhitungan volume salah")
+}
+
+func TestHitungLuas(t *testing.T) {
+ assert.Equal(t, kubus.Luas(), luasSeharusnya, "perhitungan luas salah")
+}
+
+func TestHitungKeliling(t *testing.T) {
+ assert.Equal(t, kubus.Keliling(), kelilingSeharusnya, "perhitungan keliling salah")
+}
+```
+
+Fungsi `assert.Equal()` digunakan untuk uji perbandingan. Parameter ke-2 dibandingkan nilainya dengan parameter ke-3. Jika tidak sama, maka pesan parameter ke-3 akan dimunculkan.
+
+![Testing menggunakan testify](images/A_unit_test_4_testify.png)
+
+---
+
+- [Testify](https://github.com/stretchr/testify), by "Stretchr, Inc"
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-url-parsing.md b/en/content-en/A-url-parsing.md
new file mode 100644
index 000000000..0bdc2ede6
--- /dev/null
+++ b/en/content-en/A-url-parsing.md
@@ -0,0 +1,48 @@
+# A.52. URL Parsing
+
+Data string berisi informasi URL bisa dikonversi ke tipe data `url.URL`. Dengan menggunakan tipe `url.URL`, akan ada banyak informasi yang bisa kita dapatkan dengan mudah, di antaranya seperti jenis protokol yang digunakan, path yang diakses, query, dan lainnya.
+
+Berikut adalah contoh sederhana konversi string ke `url.URL`.
+
+```go
+package main
+
+import "fmt"
+import "net/url"
+
+func main() {
+ var urlString = "http://kalipare.com:80/hello?name=john wick&age=27"
+ var u, e = url.Parse(urlString)
+ if e != nil {
+ fmt.Println(e.Error())
+ return
+ }
+
+ fmt.Printf("url: %s\n", urlString)
+
+ fmt.Printf("protocol: %s\n", u.Scheme) // http
+ fmt.Printf("host: %s\n", u.Host) // kalipare.com:80
+ fmt.Printf("path: %s\n", u.Path) // /hello
+
+ var name = u.Query()["name"][0] // john wick
+ var age = u.Query()["age"][0] // 27
+ fmt.Printf("name: %s, age: %s\n", name, age)
+}
+```
+
+Fungsi `url.Parse()` digunakan untuk parsing string ke bentuk url. Fungsi ini mengembalikan 2 data, variabel objek bertipe `url.URL` dan error (jika ada). Lewat variabel objek tersebut pengaksesan informasi url akan menjadi lebih mudah, contohnya seperti nama host bisa didapatkan lewat `u.Host`, protokol lewat `u.Scheme`, dan lainnya.
+
+Selain itu, query yang ada pada url akan otomatis diparsing juga, menjadi bentuk `map[string][]string`, dengan key adalah nama elemen query, dan value array string yang berisikan value elemen query.
+
+![Pengaksesan elemen URL](images/A_url_parsing_1_parse_url.png)
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/A-variabel.md b/en/content-en/A-variabel.md
new file mode 100644
index 000000000..bf421eb4c
--- /dev/null
+++ b/en/content-en/A-variabel.md
@@ -0,0 +1,192 @@
+# A.9. Variabel
+
+Go mengadopsi dua jenis penulisan variabel, yaitu yang dituliskan tipe data-nya dan yang tidak. Kedua cara tersebut valid dan tujuannya sama yaitu untuk deklarasi variabel, pembedanya hanya pada cara penulisannya saja.
+
+Pada chapter ini akan dikupas tuntas tentang macam-macam cara deklarasi variabel.
+
+## A.9.1. Deklarasi Variabel Beserta Tipe Data
+
+Go memiliki aturan cukup ketat dalam hal penulisan variabel. Ketika deklarasi, tipe data yg digunakan harus dituliskan juga. Istilah dari metode deklarasi variabel ini adalah **manifest typing**.
+
+Berikut adalah contoh cara pembuatan variabel yang tipe datanya harus ditulis. Silakan tulis pada project baru atau pada project yang sudah ada, bebas. Pastikan pada setiap pembuatan project baru untuk tidak lupa menginisialisasi project menggunakan command `go mod init
+ sync.WaitGroup
](images/A_waitgroup_1_waitgroup.png)
+
+> `sync.WaitGroup` merupakan salah satu tipe yang *thread safe*. Kita tidak perlu khawatir terhadap potensi *race condition* karena variabel bertipe ini aman untuk digunakan di banyak goroutine secara paralel.
+
+## A.59.2. Perbedaan WaitGroup Dengan Channel
+
+Bukan sebuah perbandingan yang *fair*, tapi jika dilihat perbedaan antara channel dan `sync.WaitGroup` kurang lebih ada di bagian ini:
+
+ - Channel tergantung kepada goroutine tertentu dalam penggunaannya, tidak seperti `sync.WaitGroup` yang dia tidak perlu tahu goroutine mana saja yang dijalankan, cukup tahu jumlah goroutine yang harus selesai
+ - Penerapan `sync.WaitGroup` lebih mudah dibanding channel
+ - Kegunaan utama channel adalah untuk komunikasi data antar goroutine. Sifatnya yang blocking bisa kita manfaatkan untuk manage goroutine; sedangkan WaitGroup khusus digunakan untuk sinkronisasi goroutine
+ - Performa `sync.WaitGroup` lebih baik dibanding channel, sumber: https://groups.google.com/forum/#!topic/golang-nuts/whpCEk9yLhc
+
+Kombinasi yang tepat antara `sync.WaitGroup` dan channel sangat penting, keduanya diperlukan dalam concurrent programming program performansinya bisa maksimal.
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/A-web-server.md b/en/content-en/A-web-server.md
new file mode 100644
index 000000000..3a35e279a
--- /dev/null
+++ b/en/content-en/A-web-server.md
@@ -0,0 +1,134 @@
+# A.51. Web Server
+
+Go menyediakan package `net/http`, berisi berbagai macam fitur untuk keperluan pembuatan aplikasi berbasis web. Termasuk di dalamnya mencakup web server, routing, templating, dan lainnya.
+
+Go memiliki web server sendiri, tersedia dalam package Go yang bisa kita import dengan mudah. Jadi berbeda dibanding beberapa bahasa lain yang servernya terpisah yang perlu diinstal sendiri (seperti PHP yang memerlukan Apache, .NET yang memerlukan IIS).
+
+Pada chapter ini kita akan belajar cara pembuatan aplikasi web sederhana dan pemanfaatan template untuk mendesain view.
+
+## A.51.1. Membuat Aplikasi Web Sederhana
+
+Package `net/http` memiliki banyak sekali fungsi yang bisa dimanfaatkan. Di bagian ini kita akan mempelajari beberapa fungsi penting seperti *routing* dan *start server*.
+
+Program di bawah ini merupakan contoh sederhana untuk memunculkan text di web ketika url tertentu diakses.
+
+```go
+package main
+
+import "fmt"
+import "net/http"
+
+func index(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, "apa kabar!")
+}
+
+func main() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, "halo!")
+ })
+
+ http.HandleFunc("/index", index)
+
+ fmt.Println("starting web server at http://localhost:8080/")
+ http.ListenAndServe(":8080", nil)
+}
+```
+
+Jalankan program tersebut.
+
+![Eksekusi program](images/A_web_server_1_start_server.png)
+
+Jika muncul dialog **Do you want the application “bab48” to accept incoming network connections?** atau sejenis, pilih allow. Setelah itu, buka url [http://localhost/](http://localhost/) dan [http://localhost/index](http://localhost/index) di browser.
+
+![Contoh penerapan net/http](images/A_web_server_2_web_server.png)
+
+Fungsi `http.HandleFunc()` digunakan untuk routing aplikasi web. Maksud dari routing adalah penentuan aksi ketika url tertentu diakses oleh user.
+
+Pada kode di atas 2 rute didaftarkan, yaitu `/` dan `/index`. Aksi dari rute `/` adalah menampilkan text `"halo"` di halaman website. Sedangkan `/index` menampilkan text `"apa kabar!"`.
+
+Fungsi `http.HandleFunc()` memiliki 2 buah parameter yang harus diisi. Parameter pertama adalah rute yang diinginkan. Parameter kedua adalah *callback* atau aksi ketika rute tersebut diakses. Callback tersebut bertipe fungsi `func(w http.ResponseWriter, r *http.Request)`.
+
+Pada pendaftaran rute `/index`, callback-nya adalah fungsi `index()`, hal seperti ini diperbolehkan asalkan tipe dari fungsi tersebut sesuai.
+
+Fungsi `http.listenAndServe()` digunakan untuk menghidupkan server sekaligus menjalankan aplikasi menggunakan server tersebut. Di Go, 1 web aplikasi adalah 1 buah server berbeda.
+
+Pada contoh di atas, server dijalankan pada port `8080`.
+
+Perlu diingat, setiap ada perubahan pada file `.go`, `go run` harus dipanggil lagi.
+
+Untuk menghentikan web server, tekan **CTRL+C** pada terminal atau CMD, di mana pengeksekusian aplikasi berlangsung.
+
+## A.51.2. Penggunaan Template Web
+
+Template engine memberikan kemudahan dalam mendesain tampilan view aplikasi website. Dan kabar baiknya Go menyediakan engine template sendiri, dengan banyak fitur yang tersedia di dalamnya.
+
+Di sini kita akan belajar contoh sederhana penggunaan template untuk menampilkan data. Pertama siapkan dahulu template-nya. Buat file `template.html` lalu isi dengan kode berikut.
+
+```html
+
+
+
+ Hello {{.Name}} !
+{{.Message}}
+ + +``` + +Kode `{{.Name}}` artinya memunculkan isi data property `Name` yang dikirim dari router. Kode tersebut nantinya di-replace dengan isi variabel `Name`. + +Selanjutnya ubah isi file `.go` dengan kode berikut. + +```go +package main + +import "fmt" +import "html/template" +import "net/http" + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + var data = map[string]string{ + "Name": "john wick", + "Message": "have a nice day", + } + + var t, err = template.ParseFiles("template.html") + if err != nil { + fmt.Println(err.Error()) + return + } + + t.Execute(w, data) + }) + + fmt.Println("starting web server at http://localhost:8080/") + http.ListenAndServe(":8080", nil) +} +``` + +Jalankan, lalu buka [http://localhost:8080](http://localhost:8080), maka data `Nama` dan `Message` akan muncul di view. + +![Penggunaan template](images/A_web_server_3_template.png) + +Fungsi `template.ParseFiles()` digunakan untuk parsing template, mengembalikan 2 data yaitu instance template-nya dan error (jika ada). Pemanggilan method `Execute()` akan membuat hasil parsing template ditampilkan ke layar web browser. + +Pada kode di atas, variabel `data` disisipkan sebagai parameter ke-2 method `Execute()`. Isi dari variabel tersebut bisa diakses di-view dengan menggunakan notasi `{{.NAMA_PROPERTY}}` (nama variabel sendiri tidak perlu dituliskan, langsung nama property di dalamnya). + +Pada contoh di atas, statement di view `{{.Name}}` akan menampilkan isi dari `data.Name`. + +## A.51.3. Advanced Web Programming + +Sampai chapter ini yang kita pelajari adalah yang sifatnya fundamental atau dasar di pemrograman Go. Nantinya di chapter [B.1. Golang Web App: Hello World](/B-golang-web-hello-world.html) hingga seterusnya akan lebih banyak membahas mengenai pemrograman web. Jadi untuk sekarang sabar dulu ya. Mari kita selesaikan pembelajaran fundamental secara runtun, sebelum masuk ke bagian pengembangan web. + +--- + + + +--- + + diff --git a/en/content-en/A-web-service-api.md b/en/content-en/A-web-service-api.md new file mode 100644 index 000000000..709652368 --- /dev/null +++ b/en/content-en/A-web-service-api.md @@ -0,0 +1,152 @@ +# A.54. Web Service API Server + +Pada chapter ini kita akan mencoba mengkombinasikan hasl pembelajaran di 2 chapter sebelumnya (yaitu web programming dan JSON), untuk membuat sebuah web service API yang memilikki endpoint dengan reponse data mengadopsi format JSON. + +> Web Service API adalah sebuah web yang menerima request dari client dan menghasilkan response, biasa berupa JSON/XML atau format lainnya. + +## A.54.1. Pembuatan Web API + +Pertama siapkan terlebih dahulu struct dan beberapa data sample. + +```go +package main + +import "encoding/json" +import "net/http" +import "fmt" + +type student struct { + ID string + Name string + Grade int +} + +var data = []student{ + student{"E001", "ethan", 21}, + student{"W001", "wick", 22}, + student{"B001", "bourne", 23}, + student{"B002", "bond", 23}, +} +``` + +Struct `student` di atas digunakan sebagai tipe elemen slice sample data, ditampung variabel `data`. + +Selanjutnya buat fungsi `users()` untuk handle endpoint `/users`. Di dalam fungsi tersebut ada proses deteksi jenis request lewat property `r.Method()`, untuk mencari tahu apakah jenis request adalah **POST** atau **GET** atau lainnya. + +```go +func users(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + if r.Method == "GET" { + var result, err = json.Marshal(data) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Write(result) + return + } + + http.Error(w, "", http.StatusBadRequest) +} +``` + +Jika request adalah GET (mengambil data), maka data yang di-encode ke JSON dijadikan sebagai response. + +Statement `w.Header().Set("Content-Type", "application/json")` digunakan untuk menentukan tipe response, yaitu sebagai JSON. Sedangkan `r.Write()` digunakan untuk mendaftarkan data sebagai response. + +Selebihnya, jika request tidak valid, response di set sebagai error menggunakan fungsi `http.Error()`. + +Siapkan juga handler untuk endpoint `/user`. Perbedaan endpoint ini dengan `/users` di atas adalah: + + - Endpoint `/users` mengembalikan semua sample data yang ada (array). + - Endpoint `/user` mengembalikan satu buah data saja, diambel dari data sample berdasarkan `ID`-nya. Pada endpoint ini, client harus mengirimkan juga informasi `ID` data yang dicari. + +```go +func user(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + if r.Method == "GET" { + var id = r.FormValue("id") + var result []byte + var err error + + for _, each := range data { + if each.ID == id { + result, err = json.Marshal(each) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Write(result) + return + } + } + + http.Error(w, "User not found", http.StatusNotFound) + return + } + + http.Error(w, "", http.StatusBadRequest) +} +``` + +Method `r.FormValue()` digunakan untuk mengambil data form yang dikirim dari client, pada konteks ini data yang dimaksud adalah `ID`. + +Dengan menggunakan `ID` tersebut dicarilah data yang relevan. Jika ada, maka dikembalikan sebagai response. Jika tidak ada maka error **400, Bad Request** dikembalikan dengan pesan **User Not Found**. + +Terakhir, implementasikan kedua handler di atas. + +```go +func main() { + http.HandleFunc("/users", users) + http.HandleFunc("/user", user) + + fmt.Println("starting web server at http://localhost:8080/") + http.ListenAndServe(":8080", nil) +} +``` + +Jalankan program, sekarang web server sudah live dan bisa dikonsumsi datanya. + +![Web API Server dijalankan](images/A_web_service_1_server.png) + +## A.54.2. Test Web Service API via Postman + +Setelah web server sudah berjalan, web service yang telah dibuat perlu untuk di-tes. Di sini saya menggunakan Google Chrome plugin bernama [Postman](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en) untuk mengetes API yang sudah dibuat. + + - Test endpoint `/users`, apakah data yang dikembalikan sudah benar. + + ![Test +/users
](images/A_web_service_2_test_api_users.png)
+
+ - Test endpoint `/user`, isi form data `id` dengan nilai `E001`.
+
+ ![Test /user
](images/A_web_service_3_test_api_user.png)
+
+## A.54.3. Test Web Service API via `cURL`
+
+Testing bisa juga dilakukan via cURL. Pastikan untuk menginstall cURL terlebih dahulu agar bisa menggunakan command berikut.
+
+```
+curl -X GET http://localhost:8080/users
+curl -X GET http://localhost:8080/user?id=B002
+```
+
+![cURL test](images/A_web_service_4.png)
+
+Data user ID pada endpoint `/user` ditulis dalam format query parameters, yaitu `?id=B002`.
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/B-ajax-json-payload.md b/en/content-en/B-ajax-json-payload.md
new file mode 100644
index 000000000..f27c4d11a
--- /dev/null
+++ b/en/content-en/B-ajax-json-payload.md
@@ -0,0 +1,204 @@
+# B.14. AJAX JSON Payload
+
+Sebelumnya kita telah mempelajari cara submit data dari front-end ke back-end dengan menggunakan payload **Form Data**. Kali ini kita akan belajar tentang cara request menggunakan payload **JSON**.
+
+> **Form Data** merupakan tipe payload default HTTP request via tag ``
+
+Pada chapter ini, kita tidak akan menggunakan tag `` untuk submit data, melainkan dengan memanfaatkan teknik AJAX (Asynchronous JavaScript And XML) dengan payload JSON.
+
+Sebenarnya [perbedaan](http://stackoverflow.com/a/23152367/1467988) antara kedua jenis request tersebut ada di dua hal, yaitu isi header `Content-Type` dan struktur informasi dikirimkan. Request lewat `` secara default memiliki content type `application/x-www-form-urlencoded`, efeknya data dikirimkan dalam bentuk query string (key-value) seperti `id=n001&nama=bruce`.
+
+> Pengiriman data via tag `` sebenarnya bisa menggunakan content-type selain `application/x-www-form-urlencoded`, yaitu `multipart/form-data`.
+
+Untuk payload JSON, `Content-Type` yang digunakan adalah `application/json`. Dengannya, data disisipkan di dalam `Body` request dalam bentuk **JSON** string.
+
+## B.14.1. Struktur Folder Proyek
+
+OK, mari praktek. Pertama siapkan proyek dengan struktur seperti gambar berikut.
+
+![Struktur proyek](images/B_ajax_json_payload_1_structure.png)
+
+> Silakan unduh file JS jQuery dari situs official-nya.
+
+## B.14.2. Front End - HTML
+
+Layout dari view perlu disiapkan terlebih dahulu, tulis kode berikut pada file `view.html`.
+
+```html
+
+
+
+
+ + | + + | +
+ | + + | +
+ | + + | +
+ + | +
false
, maka cookie yang disimpan ketika web diakses menggunakan protocol http://
, tetap bisa diakses lewat https://
, dan berlaku juga untuk kebalikannya.true
, pada saat pengaksesan lewat protokol https://
, maka data cookie akan di-enkripsi. Sedangkan pada pengaksesan lewat protokol http://
cookie disimpan seperti biasa (tanpa dienkripsi). Jika dalam satu web server, dua protokol tersebut bisa diakses, https://
dan https://
, maka aturan di atas tetap berlaku untuk masing-masing protokol, dengan catatan data yang disimpan lewat https://
hanya bisa diakses lewat protokol tersebut.false
, maka cookie bisa dibuat lewat back end (Go), maupun lewat front end (javascript)true
, maka cookie hanya bisa diciptakan dari back end{{.message}}
+ + +{{end}} +``` + +## B.12.2. Back-End + +Buat file `main.go`. Dalam file ini 2 buah route handler diregistrasikan. + + - Route `/` adalah landing page, menampilkan form input. + - Route `/process` sebagai action dari form input, menampilkan text. + +```go +package main + +import "net/http" +import "fmt" +import "html/template" + +func main() { + http.HandleFunc("/", routeIndexGet) + http.HandleFunc("/process", routeSubmitPost) + + fmt.Println("server started at localhost:9000") + http.ListenAndServe(":9000", nil) +} +``` + +Handler route `/` dibungkus dalam fungsi bernama `routeIndexGet()`. Di dalamnya, template `form` dalam file template `view.html` akan di-render ke view. Request dalam handler ini hanya dibatasi untuk method GET saja, request dengan method lain akan menghasilkan response `400 Bad Request`. + +```go +func routeIndexGet(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + var tmpl = template.Must(template.New("form").ParseFiles("view.html")) + var err = tmpl.Execute(w, nil) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + return + } + + http.Error(w, "", http.StatusBadRequest) +} +``` + +Fungsi `routeSubmitPost()` yang merupakan handler route `/process`, berisikan proses yang mirip seperti handler route `/`, yaitu parsing `view.html` untuk di ambil template `result`-nya. Selain itu, pada handler ini ada proses pengambilan data yang dikirim dari form submit, untuk kemudian disisipkan ke template view. + +```go +func routeSubmitPost(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + var tmpl = template.Must(template.New("result").ParseFiles("view.html")) + + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var name = r.FormValue("name") + var message = r.Form.Get("message") + + var data = map[string]string{"name": name, "message": message} + + if err := tmpl.Execute(w, data); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + return + } + + http.Error(w, "", http.StatusBadRequest) +} +``` + +Ketika user submit ke `/process`, maka data-data yang ada di form input dikirim. Method `ParseForm()` pada statement `r.ParseForm()` berguna untuk parsing form data yang dikirim dari view, sebelum akhirnya bisa diambil datanya. Method tersebut mengembalikan `error` jika proses parsing gagal (kemungkinan karena data yang dikirim ada yang tidak valid). + +Pengambilan data dilakukan lewat method `FormValue()`. Contohnya seperti pada kode di atas, `r.FormValue("name")`, akan mengembalikan data inputan `name` (data dari inputan ``). + +Selain lewat method `FormValue()`, pengaksesan data juga bisa dilakukan dengan cara mengakses property `Form` terlebih dahulu, kemudian mengakses method `Get()`. Contohnya seperti `r.Form.Get("message")`, yang akan menghasilkan data inputan `message`. Hasil dari kedua cara di atas adalah sama. + +Setelah data dari form sudah ditangkap oleh back-end, data ditampung dalam variabel `data` yang bertipe `map[string]string`. Variabel `data` tersebut kemudian disisipkan ke view, lewat statement `tmpl.Execute(w, data)`. + +## B.12.3. Testing + +OK, sekarang coba jalankan program yang telah kita buat, dan cek hasilnya. + +![Form Value](images/B_form_value_1_form.png) + +--- + + + +--- + + diff --git a/en/content-en/B-golang-web-hello-world.md b/en/content-en/B-golang-web-hello-world.md new file mode 100644 index 000000000..4a800540c --- /dev/null +++ b/en/content-en/B-golang-web-hello-world.md @@ -0,0 +1,162 @@ +# B.1. Golang Web App: Hello World + +Pada serial chapter B ini, fokus pembelajaran masih tetap tentang topik-topik fundamental atau dasar, tapi lebih spesifik ke area yang berhubungan dengan web development atau pengembangan web. + +Pembahasan diawali dengan pembuatan aplikasi web "Hello World" sederhana menggunakan Go. + +## B.1.1. Pembuatan Aplikasi + +Mari belajar sambil praktik. Pertama buat folder project baru dengan isi `main.go`, tentukan package-nya sebagai `main`, lalu import package `fmt` dan `net/http`. + +```go +package main + +import "fmt" +import "net/http" +``` + +Setelah itu, siapkan dua buah fungsi, masing-masing memiliki skema parameter yang sama: + + - Parameter ke-1 bertipe `http.ResponseWrite` + - Parameter ke-2 bertipe `*http.Request` + +Fungsi dengan struktur di atas diperlukan oleh `http.HandleFunc` sebagai handler untuk keperluan penanganan request ke rute yang ditentukan. Berikut adalah dua fungsi yang dimaksud: + +```go +func handlerIndex(w http.ResponseWriter, r *http.Request) { + var message = "Welcome" + w.Write([]byte(message)) +} + +func handlerHello(w http.ResponseWriter, r *http.Request) { + var message = "Hello world!" + w.Write([]byte(message)) +} +``` + +Method `Write()` milik parameter pertama (yang bertipe `http.ResponseWrite`), digunakan untuk meng-output-kan data ke HTTP response. Argumen method adalah data yang ingin dijadikan output, dituliskan dalam bentuk `[]byte`. + +Pada contoh ini, data yang akan kita tampilkan bertipe string, maka perlu dilakukan casting dari `string` ke `[]byte`. Praktiknya bisa dilihat seperta pada kode di atas, di bagian `w.Write([]byte(message))`. + +Selanjutnya, siapkan fungsi `main()` dengan isi di dalamnya adalah beberapa rute (atau *route*), dengan aksi adalah kedua fungsi yang sudah disiapkan di atas. Tak lupa siapkan juga kode untuk start web server. + +```go +func main() { + http.HandleFunc("/", handlerIndex) + http.HandleFunc("/index", handlerIndex) + http.HandleFunc("/hello", handlerHello) + + var address = "localhost:9000" + fmt.Printf("server started at %s\n", address) + err := http.ListenAndServe(address, nil) + if err != nil { + fmt.Println(err.Error()) + } +} +``` + +Fungsi `http.HandleFunc()` digunakan untuk keperluan routing. Parameter pertama adalah rute dan parameter ke-2 adalah handler-nya. + +Fungsi `http.ListenAndServe()` digunakan membuat sekaligus start server baru, dengan parameter pertama adalah alamat web server yang diiginkan (bisa diisi host, host & port, atau port saja). Parameter kedua merupakan object mux atau multiplexer. + +> Dalam chapter ini kita menggunakan *default* mux yang sudah disediakan oleh Go, jadi untuk parameter ke-2 cukup isi dengan `nil`. + +Ok, sekarang program sudah siap, jalankan menggunakan `go run`. + +![Jalankan program](images/B_golang_web_hello_world_1_start_server.png) + +Cek pada browser rute yang sudah dibuat, output akan muncul. + +![Mengakses aplikasi web](images/B_golang_web_hello_world_2_browse.png) + +Berikut merupakan penjelasan detail per-bagian program yang telah kita buat dari contoh di atas. + +#### ◉ Penggunaan `http.HandleFunc()` + +Fungsi ini digunakan untuk **routing**, menentukan aksi dari pengaksesan URL/rute tertentu. Rute dituliskan dalam tipe data `string` sebagai parameter pertama, dan aksi-nya sendiri dibungkus dalam fungsi (bisa berupa closure) pada parameter kedua (biasanya disebut sebagai handler). + +Pada kode di atas, tiga buah rute didaftarkan: + + - Rute `/` dengan aksi adalah fungsi `handlerIndex()` + - Rute `/index` dengan aksi adalah sama dengan `/`, yaitu fungsi `handlerIndex()` + - Rute `/hello` dengan aksi fungsi `handlerHello()` + +Ketika rute-rute di atas diakses lewat browser, outpunya adalah isi-handler rute yang bersangkutan. Kebetulan pada chapter ini, ketiga rute tersebut outputnya adalah sama, yaitu berupa string. + +> Pada contoh di atas, ketika rute yang tidak terdaftar diakses, maka secara otomatis handler rute `/` yang dipanggil. + +#### ◉ Penjelasan Mengenai **Handler** + +Route handler atau handler atau parameter kedua fungsi `http.HandleFunc()`, adalah sebuah fungsi dengan ber-skema `func (ResponseWriter, *Request)`. + + - Parameter ke-1 merupakan objek untuk keperluan http response. + - Sedang parameter ke-2 yang bertipe `*request` ini, berisikan informasi-informasi yang berhubungan dengan http request untuk rute yang bersangkutan. + +Contoh penulisan handler bisa dilihat pada fungsi `handlerIndex()` berikut. + +```go +func handlerIndex(w http.ResponseWriter, r *http.Request) { + var message = "Welcome" + w.Write([]byte(message)) +} +``` + +Output dari rute dituliskan di dalam handler menggunakan method `Write()` milik objek `ResponseWriter` (parameter pertama). Output bisa berupa apapun, untuk output text tinggal lakukan casting dari tipe `string` ke `[]byte`, aturan ini juga berlaku untuk banyak jenis output lainnya seperti HTML, XML, JSON, dan lainnya (dengan catatan response header `Content-Type`-nya juga perlu disesuaikan). + +Pada contoh program yang telah kita buat, handler `Index()` memunculkan text `"Welcome"`, dan handler `Hello()` memunculkan text `"Hello world!"`. + +Sebuah handler bisa dipergunakan pada banyak rute, bisa dilihat pada di atas handler `Index()` digunakan pada rute `/` dan `/index`. + +#### ◉ Penggunaan `http.ListenAndServe()` + +Fungsi ini digunakan untuk membuat web server baru. Pada contoh yang telah dibuat, web server di-*start* pada port `9000` (bisa dituliskan dalam bentuk `localhost:9000`, `0.0.0.0:9000`, atau cukup `:9000` saja). + +```go +var address = ":9000" +fmt.Printf("server started at %s\n", address) +err := http.ListenAndServe(address, nil) +``` + +Fungsi `http.ListenAndServe()` bersifat blocking, menjadikan semua statement setelahnya tidak akan dieksekusi, sebelum di-stop. + +Fungsi ini mengembalikan nilai balik ber-tipe `error`. Jika proses pembuatan web server baru gagal, maka kita bisa mengetahui root-cause nya apa. + +## B.1.2. Web Server Menggunakan `http.Server` + +Selain menggunakan `http.ListenAndServe()`, ada cara lain yang bisa diterapkan untuk start web server, yaitu dengan memanfaatkan struct `http.Server`. + +Kode di bagian start server yang sudah kita buat, jika diubah ke cara ini, kurang lebih menjadi seperti berikut. + +```go +var address = ":9000" +fmt.Printf("server started at %s\n", address) + +server := new(http.Server) +server.Addr = address +err := server.ListenAndServe() +if err != nil { + fmt.Println(err.Error()) +} +``` + +Informasi host/port perlu dimasukan dalam property `.Addr` milik objek server. Lalu dari objek tersebut panggil method `.ListenAndServe()` untuk start web server. + +Kelebihan menggunakan `http.Server` salah satunya adalah kemampuan untuk mengubah beberapa konfigurasi default web server Go. Contohnya bisa dilihat pada kode berikut, timeout untuk read request dan write request di ubah menjadi 10 detik. + +```go +server.ReadTimeout = time.Second * 10 +server.WriteTimeout = time.Second * 10 +``` + +Struct `http.Server` memiliki cukup banyak property lainnya, yang pastinya akan dibahas pada pembahasan-pembahasan selanjutnya. + +--- + + + + +--- + + diff --git a/en/content-en/B-http-basic-auth.md b/en/content-en/B-http-basic-auth.md new file mode 100644 index 000000000..401274dad --- /dev/null +++ b/en/content-en/B-http-basic-auth.md @@ -0,0 +1,242 @@ +# B.18. HTTP Basic Authentication + +HTTP Basic Auth adalah salah satu spesifikasi yang mengatur otentikasi pada HTTP request. Metode ini mewajibkan client request untuk menyertakan username dan password dalam header request. Dengan menerapkan basic auth maka kita tidak perlu menggunakan token untuk mendapatkan session. + +> Lebih jelasnya mengenai spesifikasi Basic Auth bisa di lihat di[RFC-7617](https://tools.ietf.org/html/rfc7617) + +Informasi username dan password harus di-encode terlebih dahulu ke format yg sudah ditentukan sesuai spesifikasi, kemudian dijadikan value dari header `Authentication`. + +Berikut adalah contoh format penulisan basic auth: + +```js +// Request header +Authorization: Basic c29tZXVzZXJuYW1lOnNvbWVwYXNzd29yZA== +``` + +Informasi disisipkan dalam request header dengan key `Authorization`, dan value adalah `Basic` diikut karakter spasi dan hasil encode terhadap data username dan password. Data username dan password digabung dengan separator tanda titik dua (`:`) lalu di-encode dalam format encoding Base64. + +```js +// Username password encryption +base64encode("someusername:somepassword") +// Hasilnya adalah c29tZXVzZXJuYW1lOnNvbWVwYXNzd29yZA== +``` + +Go menyediakan fasilitas untuk mengambil informasi basic auth dari suatu HTTP request dengan mudah, tanpa perlu untuk memparsing header request terlebih dahulu secara manual. + +## B.18.1. Struktur Folder Proyek dan Endpoint + +Pada chapter ini kita akan membuat sebuah web service sederhana, isinya hanya satu buah endpoint. Endpoint ini didesain untuk bisa menerima query parameter atau tanpa query parameter. + + - Endpoint `/student` menghasilkan response berisi semua data siswa + - Endpoint `/student?id=s001` menghasilkan response berisi data siswa sesuai dengan id yang di minta + +Data siswa sendiri merupakan slice object yang disimpan di variabel global. + +OK, langsung saja kita praktekan. Siapkan 3 buah file berikut, tempatkan dalam satu folder proyek. + +![Project structure](images/B_http_basic_auth_1_structure.png) + +## B.18.2. Routing + +Buka `main.go`, isi dengan kode berikut. + +```go +package main + +import "net/http" +import "fmt" +import "encoding/json" + +func main() { + http.HandleFunc("/student", ActionStudent) + + server := new(http.Server) + server.Addr = ":9000" + + fmt.Println("server started at localhost:9000") + server.ListenAndServe() +} +``` + +Lalu siapkan handler untuk rute `/student`. + +```go +func ActionStudent(w http.ResponseWriter, r *http.Request) { + if !Auth(w, r) { + return + } + if !AllowOnlyGET(w, r) { + return + } + + if id := r.URL.Query().Get("id"); id != "" { + OutputJSON(w, SelectStudent(id)) + return + } + + OutputJSON(w, GetStudents()) +} +``` + +Di dalam rute `/student` terdapat beberapa validasi. + + - Validasi `!Auth(w, r)`; Nantinya kita siapkan fungsi `Auth()` yang gunanya adalah untuk mengecek apakah request merupakan valid basic auth request atau tidak. + - Validasi `!AllowOnlyGET(w, r)`; Akan dibuat juga fungsi `AllowOnlyGET()`, tugasnya memastikan hanya request dengan method `GET` yang diperbolehkan masuk. + +Setelah request lolos dari 2 validasi di atas, lanjut ke pengecekan berikutnya yaitu mendeteksi apakah request memiliki parameter student id. + + - Ketika tidak ada parameter student id, maka endpoint ini mengembalikan semua data user yang ada. Fungsi `GetStudents()` dieksekusi. + - Sedangkan jika ada parameter student id, maka hanya user dengan id yg diinginkan yg dijadikan nilai balik. Fungsi `SelectStudent(id)` dieksekusi. + +Selanjutnya tambahkan satu fungsi lagi di `main()` yaitu `OutputJSON()`. Fungsi ini digunakan konversi data ke bentuk JSON string. + +```go +func OutputJSON(w http.ResponseWriter, o interface{}) { + res, err := json.Marshal(o) + if err != nil { + w.Write([]byte(err.Error())) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(res) +} +``` + +Konversi dari objek atau slice ke JSON string dilakukan via `json.Marshal()`. Lebih jelasnya mengenai fungsi tersebut di bahas di chapter [A.53. JSON Data](/A-json.html). + +## B.18.3. Data `Student` + +Buka file `student.go`, siapkan struct `Student` dan variabel untuk menampung data yang bertipe `[]Student`. Data inilah yang nantinya dijadikan nilai balik endpoint `/student`. + +```go +package main + +var students = []*Student{} + +type Student struct { + Id string + Name string + Grade int32 +} +``` + +Buat fungsi `GetStudents()`, fungsi ini mengembalikan semua data student. Buat juga fungsi `SelectStudent(id)`, fungsi ini mengembalikan data student sesuai dengan id terpilih. + +```go +func GetStudents() []*Student { + return students +} + +func SelectStudent(id string) *Student { + for _, each := range students { + if each.Id == id { + return each + } + } + + return nil +} +``` + +*Last but not least*, implementasikan fungsi `init()` yang didalamnya berisi pembuatan beberapa dummy data untuk ditampung variabel `students`. + +> Fungsi `init()` adalah fungsi yang secara otomatis dipanggil ketika package tersebut di import atau di run. + +```go +func init() { + students = append(students, &Student{Id: "s001", Name: "bourne", Grade: 2}) + students = append(students, &Student{Id: "s002", Name: "ethan", Grade: 2}) + students = append(students, &Student{Id: "s003", Name: "wick", Grade: 3}) +} +``` + +## B.18.4. Fungsi `Auth()` dan `AllowOnlyGET()` + +Selanjutnya, ada dua fungsi lainnya yang perlu dipersiapkan yaitu `Auth()` dan `AllowOnlyGET()`. + +#### ◉ Fungsi `Auth()` + +Buka `middleware.go`, siapkan fungsi `Auth()`. + +```go +package main + +import "net/http" + +const USERNAME = "batman" +const PASSWORD = "secret" + +func Auth(w http.ResponseWriter, r *http.Request) bool { + username, password, ok := r.BasicAuth() + if !ok { + w.Write([]byte(`something went wrong`)) + return false + } + + isValid := (username == USERNAME) && (password == PASSWORD) + if !isValid { + w.Write([]byte(`wrong username/password`)) + return false + } + + return true +} +``` + +Tugas fungsi `Auth()` adalah memvalidasi apakah request merupakan valid basic auth request, dan juga apakah credentials yang dikirim cocok dengan data pada aplikasi kita. Informasi acuan credentials sendiri di hardcode pada konstanta `USERNAME` dan `PASSWORD`. + +Fungsi `r.BasicAuth()` mengembalikan 3 informasi: + + 1. Username + 2. Password + 3. Nilai balik ke-3 ini adalah representasi valid tidak nya basic auth request yang sedang berlangsung + +Error dimunculkan ketika basic auth terdeteksi adalah tidak valid. Sedangkan jika ternyata valid, maka dilanjutkan ke proses otentikasi, mengecek apakah username dan password yang dikirim cocok dengan username dan password yang sudah di-*hardcode*. + +#### ◉ Fungsi `AllowOnlyGET()` + +Fungsi ini bertugas memastikan bahwa request yang diperbolehkan hanya yang ber-method `GET`. Selainnya, maka dianggap invalid request. + +```go +func AllowOnlyGET(w http.ResponseWriter, r *http.Request) bool { + if r.Method != "GET" { + w.Write([]byte("Only GET is allowed")) + return false + } + + return true +} +``` + +## B.18.5. Testing + +Semuanya sudah siap, sekarang jalankan aplikasi. + +```bash +go run *.go +``` + +Jangan menggunakan `go run main.go`, dikarenakan dalam package `main` terdapat beberapa file lain yang harus diikutsertakan pada saat runtime. + +![Run the server](images/B_http_basic_auth_2_run_server.png) + +Test web service yang telah dibuat menggunakan command `curl`. + +```bash +$ curl -X GET --user batman:secret http://localhost:9000/student +$ curl -X GET --user batman:secret http://localhost:9000/student?id=s001 +``` + +![Consume API](images/B_http_basic_auth_3_test_api.png) + +--- + + + + +--- + + diff --git a/en/content-en/B-http-method-basic.md b/en/content-en/B-http-method-basic.md new file mode 100644 index 000000000..69a272585 --- /dev/null +++ b/en/content-en/B-http-method-basic.md @@ -0,0 +1,66 @@ +# B.11. HTTP Method: POST & GET + +Sampai chapter ini, terhitung kita telah mempelajari banyak hal yang berhubungan dengan template view. Kali ini topik yang akan dibahas sedikit berbeda, yaitu mengenai penanganan HTTP request di back-end. + +Sebuah route handler pada dasarnya bisa menerima segala jenis request, apapun jenis HTTP method-nya maka akan tetap masuk ke satu handler (seperti **POST**, **GET**, dan atau lainnya). Pengategorian request berdasarkan HTTP method bisa dilakukan menggunakan seleksi kondisi. + +> Pada chapter lain kita akan belajar teknik routing yg lebih advance dengan bantuan *3rd party* routing library. + +## B.11.1. Praktek + +Mari coba praktekan. Disiapkan sebuah handler untuk rute `/` yang didalamnya ada pengecekan seleksi kondisi berdasarkan HTTP method. + +```go +package main + +import "net/http" +import "fmt" + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "POST": + w.Write([]byte("post")) + case "GET": + w.Write([]byte("get")) + default: + http.Error(w, "", http.StatusBadRequest) + } + }) + + fmt.Println("server started at localhost:9000") + http.ListenAndServe(":9000", nil) +} +``` + +Struct `*http.Request` memiliki property bernama `Method`, isinya informasi HTTP method dari request. + +- Jika HTTP method adalah `POST`, maka text `post` dijadikan nilai response +- Jika HTTP method adalah `GET`, maka text `get` dijadikan nilai response + +## B.11.2. Testing + +Gunakan [Postman](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en), atau tools sejenisnya untuk mempermudah testing. + +Berikut adalah contoh request dengan method GET. + +![Request GET](images/B_http_method_basic_1_get.png) + +Dan di bawah ini adalah contoh request dengan method POST. + +![Request POST](images/B_http_method_basic_2_post.png) + +Jika method yang digunakan adalah selain POST dan GET, maka web server menghasilkan response **400 Bad Request**. Di bawah ini adalah contoh request dengan method **PUT**. + +![400 Bad Request](images/B_http_method_basic_3_bad_request.png) + +--- + + + + +--- + + diff --git a/en/content-en/B-middleware-using-http-handler.md b/en/content-en/B-middleware-using-http-handler.md new file mode 100644 index 000000000..1fd6f6427 --- /dev/null +++ b/en/content-en/B-middleware-using-http-handler.md @@ -0,0 +1,189 @@ +# B.19. Middleware `http.Handler` + +Pada chapter ini, topik yang dibahas adalah penerapan interface `http.Handler` untuk implementasi custom middleware. Kita gunakan sample proyek pada chapter sebelumnya [B.18. HTTP Basic Auth](/B-http-basic-auth.html) sebagai dasar bahan pembahasan chapter ini. + +> Apa itu middleware? +> +> Istilah middleware berbeda-beda di tiap bahasa/framework. Di NodeJS dan Rails ada istilah middleware. Pada pemrograman Java Enterprise, istilah filters digunakan. Pada C# middleware disebut dengan delegate handlers. Definisi sederhana middleware adalah sebuah blok kode yang dipanggil sebelum ataupun sesudah http request di proses. + +Pada chapter sebelumnya, terdapat beberapa proses yang dijalankan dalam handler rute `/student`, yaitu pengecekan otentikasi dan pengecekan HTTP method. Misalnya terdapat rute lagi, maka dua validasi tersebut juga harus dipanggil lagi dalam handlernya. + +```go +func ActionStudent(w http.ResponseWriter, r *http.Request) { + if !Auth(w, r) { + return + } + if !AllowOnlyGET(w, r) { + return + } + + // ... +} +``` + +Jika ada banyak rute, apa yang harus kita lakukan? salah satu solusi adalah dengan memanggil fungsi `Auth()` dan `AllowOnlyGet()` di setiap handler rute yang ada. Namun jelasnya ini bukan best practice karena mengharuskan penulisan kode yang berulang-ulang. Selain itu, bisa jadi ada jenis validasi lainnya yang harus diterapkan, misalnya misalnya pengecekan csrf, authorization, dan lainnya. Maka perlu ada desain penataan kode yang lebih efisien tanpa harus menuliskan validasi yang banyak tersebut berulang-ulang. + +Solusi yang pas adalah dengan membuat middleware baru untuk keperluan validasi. + +## B.19.1. Interface `http.Handler` + +Interface `http.Handler` merupakan tipe data paling populer di Go untuk keperluan manajemen middleware. Struct yang mengimplementasikan interface ini diwajibkan untuk memilik method dengan skema `ServeHTTP(ResponseWriter, *Request)`. + +> Di Go, objek utama untuk keperluan routing web server adalah `mux` (kependekan dari multiplexer), dan `mux` ini mengimplementasikan interface `http.Handler`. + +Kita akan buat beberapa middleware baru dengan memanfaatkan interface `http.Handler` untuk keperluan pengecekan otentikasi dan pengecekan HTTP method. + +## B.19.2. Persiapan + +OK, mari masuk ke bagian *coding*. Pertama duplikat folder project sebelumnya sebagai folder proyek baru. Lalu pada `main.go`, ubah isi fungsi `ActionStudent()` dan `main()`. + + - Fungsi`ActionStudent()` + + ```go + func ActionStudent(w http.ResponseWriter, r *http.Request) { + if id := r.URL.Query().Get("id"); id != "" { + OutputJSON(w, SelectStudent(id)) + return + } + + OutputJSON(w, GetStudents()) + } + ``` + + - Fungsi `main()` + + ```go + func main() { + mux := http.DefaultServeMux + + mux.HandleFunc("/student", ActionStudent) + + var handler http.Handler = mux + handler = MiddlewareAuth(handler) + handler = MiddlewareAllowOnlyGet(handler) + + server := new(http.Server) + server.Addr = ":9000" + server.Handler = handler + + fmt.Println("server started at localhost:9000") + server.ListenAndServe() + } + ``` + + +Perubahan pada kode `ActionStudent()` adalah penghapusan kode untuk pengecekan basic auth dan HTTP method. Selain itu, di fungsi `main()` juga terdapat cukup banyak perubahan, yang detailnya akan dijelaskan sebentar lagi. + +## B.19.3. Mux / Multiplexer + +Di Go, mux (kependekan dari multiplexer) adalah router. Semua routing pasti dilakukan lewat objek mux. + +Apa benar? Routing `http.HandleFunc()` sepertinya tidak menggunakan mux? Begini, sebenarnya routing tersebut juga menggunakan mux. Go memiliki default objek mux yaitu `http.DefaultServeMux`. Routing yang langsung dilakukan dari fungsi `HandleFunc()` milik package `net/http` sebenarnya mengarah ke method default mux `http.DefaultServeMux.HandleFunc()`. + +Agar lebih jelas perbedaannya, silakan perhatikan dua kode berikut. + +```go +http.HandleFunc("/student", ActionStudent) + +// vs + +mux := http.DefaultServeMux +mux.HandleFunc("/student", ActionStudent) +``` + +Dua kode di atas melakukan prosees yang ekuivalen. + +Mux sendiri adalah bentuk nyata struct yang mengimplementasikan interface `http.Handler`. Di kode setelah routing, bisa dilihat objek `mux` ditampung ke variabel baru bertipe `http.Handler`. Seperti ini adalah valid karena memang struct multiplexer memenuhi kriteria interface `http.Handler`, yaitu memiliki method `ServeHTTP()`. + +> Untuk lebih jelasnya silakan baca dokumentasi package net/http di [https://golang.org/pkg/net/http/#Handle](https://golang.org/pkg/net/http/#Handle) + +Lalu dari objek `handler` tersebut, ke-dua middleware dipanggil dengan argument parameter diisi objek `handler` itu sendiri, dan nilai baliknya ditampung pada objek yang sama. + +```go +var handler http.Handler = mux +handler = MiddlewareAuth(handler) +handler = MiddlewareAllowOnlyGet(handler) +``` + +Fungsi `MiddlewareAuth()` dan `MiddlewareAllowOnlyGet()` adalah middleware yang akan kita buat sebentar lagi. Cara registrasi middleware yang paling populer adalah dengan memanggilnya secara sekuensial atau berurutan, seperti pada kode di atas. + + - `MiddlewareAuth()` bertugas melakukan pengencekan credentials, basic auth. + - `MiddlewareAllowOnlyGet()` bertugas melakukan pengecekan method. + +> Silakan lihat source code beberapa library middleware yang sudah terkenal seperti gorilla, gin-contrib, echo middleware, dan lainnya; Semua metode implementasi middleware-nya adalah sama, atau minimal mirip. Point plus nya, beberapa di antara library tersebut mudah diintegrasikan dan *compatible* satu sama lain. + +Kedua middleware yang akan kita buat tersebut mengembalikan fungsi bertipe `http.Handler`. Eksekusi middleware sendiri terjadi pada saat ada http request masuk. + +Setelah semua middleware diregistrasi. Masukan objek `handler` ke property `.Handler` milik server. + +```go +server := new(http.Server) +server.Addr = ":9000" +server.Handler = handler +``` + +## B.19.3. Pembuatan Middleware + +Di dalam `middleware.go` ubah fungsi `Auth()` (hasil salinan project pada chapter sebelumnya) menjadi fungsi `MiddlewareAuth()`. Parameternya objek bertipe `http.Handler`, dan nilai baliknya juga sama. + +```go +func MiddlewareAuth(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + username, password, ok := r.BasicAuth() + if !ok { + w.Write([]byte(`something went wrong`)) + return + } + + isValid := (username == USERNAME) && (password == PASSWORD) + if !isValid { + w.Write([]byte(`wrong username/password`)) + return + } + + next.ServeHTTP(w, r) + }) +} +``` + +Idealnya fungsi middleware harus mengembalikan struct yang implements `http.Handler`. Beruntungnya, Go sudah menyiapkan fungsi ajaib untuk mempersingkat pembuatan struct yang implement `http.Handler`, yaitu fungsi `http.HandlerFunc()`. Cukup bungkus callback `func(http.ResponseWriter,*http.Request)` sebagai tipe `http.HandlerFunc()` maka semuanya beres. + +Isi dari `MiddlewareAuth()` sendiri adalah pengecekan basic auth (sama seperti pada chapter sebelumnya). + +Tak lupa, ubah juga `AllowOnlyGet()` menjadi `MiddlewareAllowOnlyGet()`. + +```go +func MiddlewareAllowOnlyGet(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + w.Write([]byte("Only GET is allowed")) + return + } + + next.ServeHTTP(w, r) + }) +} +``` + +## B.19.4. Testing + +Jalankan aplikasi. + +![Run the server](images/B_http_basic_auth_2_run_server.png) + +Lalu test menggunakan `curl`, hasilnya adalah sama dengan pada chapter sebelumnya. + +![Consume API](images/B_http_basic_auth_3_test_api.png) + +Dibanding metode pada chapter sebelumnya, dengan teknik ini kita lebih mudah mengontrol lalu lintas routing aplikasi, karena semua rute pasti melewati layer middleware terlebih dahulu sebelum sampai ke handler tujuan. Cukup maksimalkan saja penerapan middleware tanpa perlu menambahkan validasi di masing-masing handler. + +--- + + + + +--- + + diff --git a/en/content-en/B-render-html-string.md b/en/content-en/B-render-html-string.md new file mode 100644 index 000000000..0ab8b4c1e --- /dev/null +++ b/en/content-en/B-render-html-string.md @@ -0,0 +1,67 @@ +# B.10. Template: Render HTML String + +Output HTML yang muncul, selain bersumber dari template view bisa juga bersumber dari sebuah string. Dengan menggunakan method `Parse()` milik `*template.Template` kita bisa menjadikan HTML string sebagai output di web. + +## B.10.1. Praktek + +Langsung saja kita praktekkan, siapkan folder project baru beserta file `main.go`, isi dengan kode berikut. + +```go +package main + +import "net/http" +import "fmt" +import "html/template" + +const view string = ` + + +/
dan /test
](images/B_render_specific_html_template_1_preview.png)
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/B-routing-http-handlefunc.md b/en/content-en/B-routing-http-handlefunc.md
new file mode 100644
index 000000000..3ac8e616b
--- /dev/null
+++ b/en/content-en/B-routing-http-handlefunc.md
@@ -0,0 +1,82 @@
+# B.2. Routing `http.HandleFunc`
+
+Routing di Go bisa dilakukan dengan beberapa cara, di antaranya:
+
+ 1. Dengan memanfaatkan fungsi `http.HandleFunc()`
+ 2. Mengimplementasikan interface `http.Handler` pada suatu struct, untuk kemudian digunakan pada fungsi `http.Handle()`
+ 3. Membuat multiplexer sendiri dengan memanfaatkan struct `http.ServeMux`
+
+Pada buku ini, semua cara tersebut akan dibahas, namun khusus di chapter ini hanya `http.HandleFunc()` yang kita pelajari.
+
+> Metode routing cara pertama dan cara kedua memiliki kesamaan yaitu sama-sama menggunakan `DefaultServeMux` sebagai router. Mengenai apa itu `DefaultServeMux` akan kita bahas lebih mendetail pada chapter lain.
+
+## B.2.1. Penggunaan `http.HandleFunc()`
+
+Seperti yang sudah dijelaskan sekilas pada chapter sebelumnya, fungsi `http.HandleFunc()` digunakan untuk registrasi rute/endpoint beserta handler-nya. Penggunaan fungsi ini cukup mudah, panggil saja fungsi lalu isi dua parameternya.
+
+ 1. Parameter ke-1, adalah rute (atau endpoint). Sebagai contoh: `/`, `/index`, `/about`.
+ 2. Parameter ke-2, berisikan handler untuk rute bersangkutan. Sebagai contoh handler untuk rute `/` bertugas untuk menampilkan output berupa html `
+ hello
`. + +Agar lebih mudah dipahami mari langsung praktek. Siapkan file `main.go` dengan package adalah `main`, dan import package `net/http` di dalamnya. + +```go +package main + +import "fmt" +import "net/http" +``` + +Buat fungsi `main()`, di dalamnya siapkan sebuah closure `handlerIndex`, lalu gunakan closure tersebut sebagai handler dari dua rute baru yang sebentar lagi disiapkan, yaitu rute `/` dan `/index`. + +```go +func main() { + handlerIndex := func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("hello")) + } + + http.HandleFunc("/", handlerIndex) + http.HandleFunc("/index", handlerIndex) +} +``` + +Selanjutnya, masih dalam fungsi `main()`, tambahkan rute `/data` dengan handler adalah anonymous function. + +```go +func main() { + // ... + + http.HandleFunc("/data", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("hello again")) + }) +} +``` + +Terakhir, jalankan web server. + +```go +func main() { + // ... + + fmt.Println("server started at localhost:9000") + http.ListenAndServe(":9000", nil) +} +``` + +## B.2.2. Run & Test + +Tes dan lihat hasilnya. + +![Rute/data
mengembalikan data json](images/B_routing_http_handlefunc_1_routing.png)
+
+Handler bisa berupa fungsi, closure, ataupun anonymous function, intinya bebas, yang terpenting adalah skema fungsi-nya harus sesuai dengan `func (http.ResponseWriter, *http.Request)`.
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/B-routing-static-assets.md b/en/content-en/B-routing-static-assets.md
new file mode 100644
index 000000000..ceb7bea80
--- /dev/null
+++ b/en/content-en/B-routing-static-assets.md
@@ -0,0 +1,97 @@
+# B.3. Routing Static Assets
+
+Pada bagian ini kita akan mempelajari cara routing static assets / static contents. Static assets yang dimaksud adalah seperti file statis css, js, gambar, dan lainnya.
+
+Ok, mari belajar sambil praktek.
+
+## B.3.1. Struktur Aplikasi
+
+Buat project baru, siapkan file dan folder dengan struktur sesuai dengan gambar berikut.
+
+![Structure](images/B_routing_static_assets_1_structure.png)
+
+Dalam folder `assets`, isi dengan file apapun bebas, bisa gambar atau file js. Selanjutnya masuk ke bagian routing static assets.
+
+## B.3.2. Routing
+
+Berbeda dengan routing menggunakan `http.HandleFunc()`, routing static assets lebih mudah. Silakan tulis kode berikut dalam `main.go`, setelahnya kita akan bahas secara mendetail.
+
+```go
+package main
+
+import "fmt"
+import "net/http"
+
+func main() {
+ http.Handle("/static/",
+ http.StripPrefix("/static/",
+ http.FileServer(http.Dir("assets"))))
+
+ fmt.Println("server started at localhost:9000")
+ http.ListenAndServe(":9000", nil)
+}
+```
+
+Syarat yang dibutuhkan untuk routing static assets masih sama dengan routing handler, yaitu perlu didefiniskan rute beserta handler-nya. Hanya saja pembedanya di sini adalah dalam routing static assets yang digunakan adalah `http.Handle()`, bukan `http.HandleFunc()`.
+
+ 1. Rute terpilih adalah `/static/`, maka nantinya semua request yang di awali dengan `/static/` akan diarahkan ke sini. Registrasi rute menggunakan `http.Handle()` adalah berbeda dengan routing menggunakan `http.HandleFunc()`, lebih jelasnya akan ada sedikit penjelasan pada chapter lain.
+
+ 2. Sedang untuk handler-nya bisa di-lihat, ada pada parameter ke-2 yang isinya statement `http.StripPrefix()`. Sebenarnya actual handler nya berada pada `http.FileServer()`. Fungsi `http.StripPrefix()` hanya digunakan untuk membungkus actual handler.
+
+Fungsi `http.FileServer()` mengembalikan objek ber-tipe `http.Handler`. Fungsi ini berguna untuk merespon http request dengan konten yang ada di dalam folder `assets` sesuai permintaan.
+
+Jalankan `main.go`, lalu test hasilnya di browser `http://localhost:9000/static/`.
+
+![Structure](images/B_routing_static_assets_2_preview.png)
+
+## B.3.3. Penjelasan
+
+Penjelasan akan lebih mudah dipahami jika disajikan juga contoh praktek, maka sejenak kita coba bahas menggunakan contoh sederhana berikut.
+
+```go
+http.Handle("/", http.FileServer(http.Dir("assets")))
+```
+
+Jika dilihat pada struktur folder yang sudah di-buat, di dalam folder `assets` terdapat file bernama `site.css`. Maka dengan bentuk routing pada contoh sederhana di atas, request ke `/site.css` akan diarahkan ke path `./site.css` (relatif dari folder `assets`). Permisalan contoh lainnya:
+
+ * Request ke `/site.css` mengarah path `./site.css` relatif dari folder `assets`
+ * Request ke `/script.js` mengarah path `./script.js` relatif dari folder `assets`
+ * Request ke `/some/folder/test.png` mengarah path `./some/folder/test.png` relatif dari folder `assets`
+ * ... dan seterusnya
+
+> Fungsi `http.Dir()` berguna untuk *adjustment path parameter*. Separator dari path yang di-definisikan otomatis di-konversi ke path separator sesuai sistem operasi.
+
+Sekarang coba perhatikan kode berikut.
+
+```go
+http.Handle("/static", http.FileServer(http.Dir("assets")))
+```
+
+Dengan skema routing di atas, maka:
+
+ * Request ke `/static/site.css` mengarah ke `./static/site.css` relatif dari folder `assets`
+ * Request ke `/static/script.js` mengarah ke `./static/script.js` relatif dari folder `assets`
+ * Request ke `/static/some/folder/test.png` mengarah ke `./static/some/folder/test.png` relatif dari folder `assets`
+ * ... dan seterusnya
+
+Bisa dilihat bahwa rute yang didaftarkan juga akan digabung dengan path destinasi file yang dicari, dan ini menjadikan path tidak valid. File `site.css` berada pada path `assets/site.css`, sedangkan dari routing di atas pencarian file mengarah ke path `assets/static/site.css`. Di sinilah kegunaan dari fungsi `http.StripPrefix()`.
+
+Fungsi `http.StripPrefix()` berguna untuk menghapus prefix dari endpoint yang diakses. Request ke URL yang di awali dengan `/static/` akan diambil informasi endpoint-nya tanpa prefix `/static/`.
+
+ * Request ke `/static/site.css` mengarah ke `site.css`
+ * Request ke `/static/script.js` mengarah ke `script.js`
+ * Request ke `/static/some/folder/test.png` mengarah ke `some/folder/test.png`
+ * ... dan seterusnya
+
+Dengan penerapan `http.StripPrefix()` maka routing static assets menjadi valid, karena file yang di-request akan cocok dengan path folder yang telah dibuat.
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/B-server-handler-http-request-cancellation.md b/en/content-en/B-server-handler-http-request-cancellation.md
new file mode 100644
index 000000000..0475f2453
--- /dev/null
+++ b/en/content-en/B-server-handler-http-request-cancellation.md
@@ -0,0 +1,129 @@
+# B.23. Server Handler HTTP Request Cancellation
+
+Dalam konteks web application, kadang kala sebuah HTTP request butuh waktu cukup lama untuk selesai, bisa jadi karena kode yang kurang dioptimasi atau prosesnya memang lama, atau mungkin ada faktor lainnya. Dari sisi client, biasanya ada handler untuk cancel request ketika request melebihi batas timeout yang sudah ditentukan.
+
+Berbeda dengan handler di back end-nya, by default request yang sudah di-cancel oleh client tidak mempengaruhi yang terjadi di back-end, proses di back end akan tetap lanjut hingga selesai. Umumnya hal ini bukan merupakan masalah, tapi untuk beberapa *case* ada baiknyakita perlu men-*treat* *cancelled request* dengan baik. Dan pada chapter ini kita akan belajar caranya.
+
+> Chapter ini fokus terhadap cancellation pada client http request di sisi back-end. Untuk topik cancellation pada proses konkuren silakan pembahasannya ada di chapter [A.64. Concurrency Pattern: Context Cancellation Pipeline](/A-pipeline-context-cancellation.html).
+
+## B.32.1. Praktek
+
+Dari objek `*http.Request` informasi objek context bisa diakses lewat method `.Context()`, dan dari context tersebut kita bisa mendeteksi apakah sebuah request di-cancel atau tidak oleh client.
+
+> Pada chapter ini kita tidak membahas secara rinci apa itu context karena sudah ada pembahasan terpisah mengenai topik tersebut di chapter [A.64. Concurrency Pattern: Context Cancellation Pipeline](/A-pipeline-context-cancellation.html).
+
+Object context memiliki method `.Done()` yang nilai baliknya berupa channel. Dari channel tersebut kita bisa deteksi apakah request di-cancel atau tidak oleh client, jika ada data yang diterima via channel tersebut dan error yang didapat ada keterangan `"cancelled"` maka bisa diasumsikan request tersebut dibatalkan oleh client.
+
+Mari kita praktekan langsung. Silakan mulai dengan menulis kode berikut.
+
+```go
+package main
+
+import (
+ "log"
+ "net/http"
+ "strings"
+ "time"
+ "log"
+)
+
+func handleIndex(w http.ResponseWriter, r *http.Request) {
+ // do something here
+}
+
+func main() {
+ http.HandleFunc("/", handleIndex)
+ http.ListenAndServe(":8080", nil)
+}
+```
+
+Di dalam `handleIndex()` disimulasikan sebuah proses membutuhkan waktu lama untuk selesai (kita gunakan `time.Sleep()` untuk ini). Umumnya kode dituliskan langsung dalam handler tersebut, tapi pada kasus ini tidak. Untuk bisa mendeteksi sebuah request di-cancel atau tidak kita akan manfaatkan goroutine baru.
+
+Dalam penerapannya ada dua pilihan opsi:
+
+- Cara ke-1: Dengan menaruh proses utama di dalam gorutine tersebut, dan menaruh kode untuk deteksi di luar (di dalam handler-nya).
+- Cara ke-2: Atau sebaliknya. Menaruh proses utama di dalam handler, dan menempatkan deteksi cancelled request dalam goroutine baru.
+
+Pada contoh berikut, kita gunakan cara pertama. Tulis kode berikut dalam handler.
+
+```go
+done := make(chan bool)
+go func() {
+ // do the process here
+ // simulate a long-time request by putting 10 seconds sleep
+ time.Sleep(10 * time.Second)
+
+ done <- true
+}()
+
+select {
+case <-r.Context().Done():
+ if err := r.Context().Err(); err != nil {
+ if strings.Contains(strings.ToLower(err.Error()), "canceled") {
+ log.Println("request canceled")
+ } else {
+ log.Println("unknown error occured.", err.Error())
+ }
+ }
+case <-done:
+ log.Println("done")
+}
+```
+
+Pada kode di atas terlihat, proses utama dibungkus dalam goroutine. Ketika selesai, maka back-end akan menerima data via channel `done`.
+
+Keyword `select` di situ disiapkan untuk pendeteksian dua kondisi berikut:
+
+- Channel `r.Context().Done()`. Jika channel ini menerima data maka diasumsikan request selesai. Selanjutnya lakukan pengecekan pada objek error milik context untuk deteksi apakah selesai-nya request ini karena memang selesai, atau di-cancel oleh client, atau faktor lainnya.
+- Channel `<-done`. Jika channel ini menerima data, maka proses utama adalah selesai.
+
+Sekarang coba jalankan kode lalu test hasilnya.
+
+```bash
+curl -X GET http://localhost:8080/
+```
+
+![Cancelled client http request](images/B_server_handler_http_request_cancellation_1_cancelled_request_get.png)
+
+Pada gambar di atas terdapat dua request, yg pertama sukses dan yang kedua adalah cancelled. Pesan `request cancelled` muncul ketika client http request dibatalkan.
+
+> Di CMD/terminal bisa cukup dengan `ctrl + c` untuk cancel request
+
+## B.32.2. Handle Cancelled Request yang ada Payload-nya
+
+Khusus untuk request dengan HTTP method yang memiliki request body (payload), maka channel `r.Context().Done()` tidak akan menerima data hingga terjadi proses read pada body payload.
+
+Silakan coba saja, misalnya dengan menambahkan kode berikut.
+
+```go
+go func() {
+ // do the process here
+ // simulate a long-time request by putting 10 seconds sleep
+
+ body, err := io.ReadAll(r.Body)
+ // ...
+
+ time.Sleep(10 * time.Second)
+
+ done <- true
+}()
+```
+
+Hasilnya:
+
+```go
+curl -X POST http://localhost:8080/ -H 'Content-Type: application/json' -d '{}'
+```
+
+!\[Cancelled client http request\](images/B_server_handler_http_request_cancellation_2_cancelled request_with_payload.png)
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/B-simple-configuration.md b/en/content-en/B-simple-configuration.md
new file mode 100644
index 000000000..f7303e1ee
--- /dev/null
+++ b/en/content-en/B-simple-configuration.md
@@ -0,0 +1,240 @@
+# B.22. Simple Configuration
+
+Dalam development, pastinya kita programmer akan berurusan dengan banyak sekali variabel dan konstanta untuk keperluan konfigurasi. Misalnya, variabel berisi informasi port web server, timeout, variabel global, dan lainnya.
+
+Pada chapter ini, kita akan belajar dasar pengelolahan variabel konfigurasi dengan memanfaatkan file JSON.
+
+## B.22.1. Struktur Aplikasi
+
+Pertama-tama, buat project baru dengan struktur seperti gambar berikut.
+
+![Structure](images/B_simple_configuration_1_structure.png)
+
+Folder `conf` berisi 2 file.
+
+ 1. File `config.json`. Semua konfigurasi nantinya harus disimpan di file ini dalam struktur JSON.
+ 2. File `config.go`. Berisikan beberapa fungsi dan operasi untuk mempermudah pengaksesan konfigurasi dari file `config.json`.
+
+## B.22.2. File Konfigurasi JSON `config.json`
+
+Semua konfigurasi dituliskan dalam file ini. Desain struktur JSON-nya untuk bisa mudah dipahami, contoh:
+
+```json
+{
+ "server": {
+ "port": 9000,
+ "read_timeout": 5,
+ "write_timeout": 5
+ },
+
+ "log": {
+ "verbose": true
+ }
+}
+```
+
+Data JSON di atas berisi 4 buah data konfigurasi.
+
+ 1. Property `server.port`. Port yang digunakan saat start web server.
+ 2. Property `server.read_timeout`. Dijadikan sebagai timeout read.
+ 3. Property `server.write_timeout`. Dijadikan sebagai timeout write.
+ 4. Property `log.verbose`. Penentu apakah log di print atau tidak.
+
+## B.22.3. Pemrosesan Konfigurasi
+
+Pada file `config.go` kita akan siapkan sebuah fungsi yang isinya mengembalikan objek cetakan struct didapat dari konten file `config.json`.
+
+Siapkan struct nya terlebih dahulu.
+
+```go
+package conf
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "time"
+)
+
+var shared *_Configuration
+
+type _Configuration struct {
+ Server struct {
+ Port int `json:"port"`
+ ReadTimeout time.Duration `json:"read_timeout"`
+ WriteTimeout time.Duration `json:"write_timeout"`
+ } `json:"server"`
+
+ Log struct {
+ Verbose bool `json:"verbose"`
+ } `json:"log"`
+}
+```
+
+Bisa dilihat pada kode di atas, struct bernama `_Configuration` dibuat. Struct ini berisikan banyak property yang strukturnya sama persis dengan isi file `config.json`. Dengan skema seperti itu akan cukup mempermudah developer dalam pengaksesan data konfigurasi.
+
+Dari struct tersebut disiapkan objek bernama `shared`. Variabel ini berisi informasi konfigurasi hasil baca `config.json`, dan nantinya isinya bisa diakses via fungsi fungsi yang sebentar lagi akan dibuat.
+
+Selanjutnya, siapkan fungsi `init()` dengan isi operasi baca file `config.json` serta operasi decode data JSON dari isi file tersebut ke variabel `shared`.
+
+Dengan adanya fungsi `init()` maka pada saat package `conf` ini di-import ke package lain otomatis file `config.json` dibaca dan di-parse untuk disimpan di variabel `shared`. Tambahkan juga validasi untuk memastikan kode hanya di-parse sekali saja.
+
+```go
+func init() {
+ if shared != nil {
+ return
+ }
+
+ basePath, err := os.Getwd()
+ if err != nil {
+ panic(err)
+ return
+ }
+
+ bts, err := os.ReadFile(filepath.Join(basePath, "conf", "config.json"))
+ if err != nil {
+ panic(err)
+ return
+ }
+
+ shared = new(_Configuration)
+ err = json.Unmarshal(bts, &shared)
+ if err != nil {
+ panic(err)
+ return
+ }
+}
+```
+
+Kemudian buat fungsi `Configuration()` yang isinya menjembatani pengaksesan object `shared`.
+
+```go
+func Configuration() _Configuration {
+ return *shared
+}
+```
+
+## B.22.4. Routing & Server
+
+Masuk ke bagian implementasi, buka `main.go`, lalu buat custom mux.
+
+```go
+package main
+
+import (
+ "chapter-B.22/conf"
+ "fmt"
+ "log"
+ "net/http"
+ "time"
+)
+
+type CustomMux struct {
+ http.ServeMux
+}
+
+func (c CustomMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if conf.Configuration().Log.Verbose {
+ log.Println("Incoming request from", r.Host, "accessing", r.URL.String())
+ }
+
+ c.ServeMux.ServeHTTP(w, r)
+}
+```
+
+Bisa dilihat dalam method `ServeHTTP()` di atas, ada pengecekan salah satu konfigurasi, yaitu `Log.Verbose`. Cara pengaksesannya cukup mudah, yaitu lewat fungsi `Configuration()` milik package `conf` yang telah di-import.
+
+OK, kembali lagi ke contoh, dari mux di atas dibuatkan object baru bernama `router`, lalu beberapa rute didaftarkan ke object mux tersebut.
+
+```go
+func main() {
+ router := new(CustomMux)
+ router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("Hello World!"))
+ })
+ router.HandleFunc("/howareyou", func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("How are you?"))
+ })
+
+ // ...
+}
+```
+
+Selanjutnya, siapkan kode untuk start web server. Tulis kode berikut di dalam fungsi `main()` tepat setelah kode deklarasi route handler.
+
+```go
+server := new(http.Server)
+server.Handler = router
+server.ReadTimeout = conf.Configuration().Server.ReadTimeout * time.Second
+server.WriteTimeout = conf.Configuration().Server.WriteTimeout * time.Second
+server.Addr = fmt.Sprintf(":%d", conf.Configuration().Server.Port)
+
+if conf.Configuration().Log.Verbose {
+ log.Printf("Starting server at %s \n", server.Addr)
+}
+
+err := server.ListenAndServe()
+if err != nil {
+ panic(err)
+}
+```
+
+Di atas, ada objek baru dibuat dari struct `http.Server`, yaitu `server`. Untuk start server, panggil method `ListenAndServe()` milik objek tersebut.
+
+Dengan memanfaatkan struct ini, kita bisa meng-custom beberapa konfigurasi default pada Go web server. Di antaranya seperti `ReadTimeout` dan `WriteTimeout`.
+
+Bisa dilihat di contoh ada 4 buah properti milik `server` yang diisi nilainya dengan data konfigurasi.
+
+ - `server.Handler`. Properti ini wajib di isi dengan custom mux yang dibuat.
+ - `server.ReadTimeout`. Adalah timeout ketika memproses sebuah request. Kita isi dengan nilai dari configurasi.
+ - `server.WriteTimeout`. Adalah timeout ketika memproses response.
+ - `server.Addr`. Port yang digunakan web server pada saat start.
+
+Ok. Sekarang jalankan aplikasi, akses dua buah endpoint yang sudah dibuat, kemudian cek di console.
+
+![Structure](images/B_simple_configuration_2_log.png)
+
+Coba ubah konfigurasi pada `config.json` nilai `log.verbose` menjadi `false`. Lalu restart aplikasi, maka log tidak muncul.
+
+## B.22.5. Kekurangan Konfigurasi File
+
+Ok, kita telah selesai belajar tentang cara membuat file konfigurasi yang terpusat dan mudah dibaca. Metode konfigurasi seperti ini umum digunakan, tapi dalam penerapannya memiliki beberapa *cons* yang mungkin akan mulai terasa ketika aplikasi arsitektur aplikasi berkembang dan arsitektur sistemnya menjadi kompleks. *Cons* yang dimaksud diantaranya adalah:
+
+#### ◉ Tidak mendukung komentar
+
+Komentar sangat penting karena untuk aplikasi besar yang konfigurasi item-nya sangat banyak, konfigurasi seperti pada contoh ini akan cukup susah untuk dikelola. Sebenarnya masalah ini bisa diselesaikan dengan mudah dengan cara mengadopsi file format lainnya, misalnya `YAML`, `.env`, atau lainnya.
+
+#### ◉ Nilai konfigurasi harus diketahui di awal
+
+Kita harus tau semua value tiap-tiap konfigurasi terlebih dahulu sebelum dituliskan ke file, dan sebelum aplikasi di-up. Dari sini akan sangat susah jika misal ada beberapa konfigurasi yang kita tidak tau nilainya tapi tau cara pengambilannya.
+
+Contohnya seperti ini, di beberapa kasus, misalnya di AWS, database server yang di-setup secara automated akan meng-generate connection string yang host-nya bisa berganti-ganti tiap start-up, dan tidak hanya itu saja, bisa saja username, password dan lainnya juga tidak statis.
+
+Dengan ini akan cukup merepotkan jika kita harus cari terlebih dahulu value konfigurasi tersebut untuk kemudian dituliskan ke file secara manual.
+
+#### ◉ Tidak terpusat
+
+Dalam pengembangan aplikasi, banyak konfigurasi yang nilai-nya akan didapat lewat jalan lain, seperti *environment variables* atau *command arguments*. Menyimpan konfigurasi file itu sudah cukup bagus, cuman untuk *case* dimana terdapat banyak sekali services, agak merepotkan pengelolahannya.
+
+Ketika ada perubahan konfigurasi, semua services harus direstart.
+
+#### ◉ Statis (tidak dinamis)
+
+Konfigurasi umumnya dibaca hanya jika diperlukan. Penulisan konfigurasi dalam file membuat proses pembacaan file harus dilakukan di awal, haru kemudian kita bisa ambil nilai konfigurasi dari data yang sudah ada di memori.
+
+Hal tersebut memiliki beberapa konsekuensi, untuk aplikasi yang di-manage secara automated, sangat mungkin adanya perubahan nilai konfigurasi. Dari sini berarti pembacaan konfigurasi file tidak boleh hanya dilakukan di awal saja. Tapi juga tidak boleh dilakukan di setiap waktu, karena membaca file itu ada *cost*-nya dari prespektif I/O.
+
+#### ◉ Solusi
+
+Kita akan membahas solusi dari beberapa masalah di atas (tidak semuanya) pada chapter terpisah, yaitu [C.11. Best Practice Configuration Menggunakan Environment Variable](/C-best-practice-configuration-env-var.html)
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/B-template-actions-variables.md b/en/content-en/B-template-actions-variables.md
new file mode 100644
index 000000000..1150f9eee
--- /dev/null
+++ b/en/content-en/B-template-actions-variables.md
@@ -0,0 +1,256 @@
+# B.6. Template: Actions & Variables
+
+[**Actions**](https://golang.org/pkg/text/template/#hdr-Actions) merupakan *predefined* keyword yang disediakan oleh Go. Actions biasa dimanfaatkan dalam pembuatan template.
+
+Sebenarnya pada dua chapter sebelumnya, secara tidak sadar kita telah menggunakan beberapa jenis actions, di antaranya:
+
+ - Penggunaan **pipeline output**. Nilai yang diapit tanda
+ \{\{ \}\}
, yang nantinya akan dimunculkan di layar sebagai output, contohnya: \{\{"hello world"\}\}
.
+ - Include template lain menggunakan keyword `template`, contohnya: \{\{template "name"\}\}
.
+
+Pada chapter ini, kita akan belajar lebih banyak lagi tentang actions lain, juga cara pembuatan dan pemanfaatan variabel pada template view.
+
+## B.6.1. Persiapan
+
+Pertama-tama, siapkan sebuah file bernama `main.go`, lalu isi dengan kode berikut.
+
+```go
+package main
+
+import "net/http"
+import "fmt"
+import "html/template"
+
+type Info struct {
+ Affiliation string
+ Address string
+}
+
+type Person struct {
+ Name string
+ Gender string
+ Hobbies []string
+ Info Info
+}
+```
+
+Pada kode di atas, dua buah struct disiapkan, `Info` dan `Person` (yang mana struct `Info` di-embed ke dalam struct `Person`). Kedua struct tersebut nantinya akan digunakan untuk pembuatan objek untuk kemudian disisipkan ke dalam view.
+
+Selanjutnya, siapkan fungsi `main()`, dengan di dalamnya berisikan 1 buah route handler `/`, dan juga kode untuk menjalankan server pada port `9000`.
+
+```go
+func main() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ var person = Person{
+ Name: "Bruce Wayne",
+ Gender: "male",
+ Hobbies: []string{"Reading Books", "Traveling", "Buying things"},
+ Info: Info{"Wayne Enterprises", "Gotham City"},
+ }
+
+ var tmpl = template.Must(template.ParseFiles("view.html"))
+ if err := tmpl.Execute(w, person); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ })
+
+ fmt.Println("server started at localhost:9000")
+ http.ListenAndServe(":9000", nil)
+}
+```
+
+Pada route handler `/` di atas, variabel objek `person` dibuat, kemudian disisipkan sebagai data pada view `view.html`.
+
+Perlu diketahui, ketika data yang disisipkan ke view bertipe `map`, maka `key` (yang nantinya akan menjadi nama variabel) boleh dituliskan dalam huruf kecil. Sedangkan jika berupa variabel objek `struct`, maka property harus dituliskan public (huruf pertama kapital).
+
+> Data yang disisipkan ke view, jika tipe nya adalah struct, maka hanya properties ber-modifier public (ditandai dengan huruf kapital di awal nama property) yang bisa diakses dari view.
+
+OK, bagian back end sudah selesai, sekarang saatnya lanjut ke bagian depan. Buat file view baru bernama `view.html`, isi dengan kode berikut.
+
+```html
+
+
+ true
+{{end}} + +{{isTrue := true}} + +{{if isTrue}} +true
+{{end}} + +{{if eq isTrue}} +true
+{{end}} + +{{if ne isTrue}} +not true (false)
+{{end}} +``` + +--- + + + +--- + + diff --git a/en/content-en/B-template-custom-functions.md b/en/content-en/B-template-custom-functions.md new file mode 100644 index 000000000..f1f8d45af --- /dev/null +++ b/en/content-en/B-template-custom-functions.md @@ -0,0 +1,117 @@ +# B.8. Template: Custom Functions + +Pada chapter sebelumnya kita telah berkenalan dengan beberapa *predefined* function yang disediakan oleh Go. Kali ini kita akan belajar tentang fungsi custom, bagaimana cara pembuatan dan penggunaannya dalam template. + +## B.8.1. Front End + +Pertama, siapkan project baru. Buat file template `view.html`, lalu isi dengan kode berikut. + +```html + + + +\{\{template "namatemplate"\}\}
), maka akan dicari view yang namanya adalah `view.html`. Keseluruhan isi `view.html` akan dianggap sebagai sebuah template dengan nama template adalah nama file itu sendiri.
+
+## B.8.3. Testing
+
+Tes hasilnya lewat browser.
+
+![Custom Function](images/B_template_custom_functions_1_func.png)
+
+## B.8.4. Perbadaan Fungsi `template.ParseFiles()` & Method `ParseFiles()` Milik `*template.Template`
+
+Pada kode di atas, pemanggilan `template.New()` menghasilkan objek bertipe `*template.Template`.
+
+Pada chapter [B.5. Template: Render Partial HTML Template](/B-template-render-partial-html.html) kita telah belajar mengenai fungsi `template.ParseFiles()` yang fungsi tersebut juga mengembalikan objek bertipe `*template.Template`.
+
+Di contoh di chapter ini, method `ParseFiles()` yang dipanggil bukanlah fungsi `template.ParseFiles()` yang kita telah pelajari sebelumnya. Meskipun namanya sama, kedua fungsi/method ini berbeda.
+
+ - Fungsi `template.ParseFiles()`, adalah milik package `template`. Fungsi ini digunakan untuk mem-parsing semua view yang disisipkan sebagai parameter.
+ - Method `ParseFiles()`, milik `*template.Template`, digunakan untuk memparsing semua view yang disisipkan sebagai parameter, lalu diambil hanya bagian yang nama template-nya adalah sama dengan nama template yang sudah di-alokasikan menggunakan `template.New()`. Jika template yang dicari tidak ada, maka akan mencari yang nama file-nya sama dengan nama template yang sudah ter-alokasi.
+
+Chapter selanjutnya akan membahas lebih detail mengenai penggunaan method `ParseFiles()`.
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/B-template-functions.md b/en/content-en/B-template-functions.md
new file mode 100644
index 000000000..88f8680e1
--- /dev/null
+++ b/en/content-en/B-template-functions.md
@@ -0,0 +1,242 @@
+# B.7. Template: Functions
+
+Go menyediakan beberapa *predefiend* function yang bisa digunakan langsung dalam file template. Pada chapter ini kita akan membahas beberapa di antaranya beserta cara penggunaannya.
+
+Cara pemanggilan fungsi atau method sebuah objek pada file template sedikit berbeda dibanding dengan yang telah dicontohkan pada chapter sebelumnya.
+
+## B.7.1. Persiapan
+
+Siapkan folder proyek baru, dengan isi 2 buah file: `main.go` dan `view.html`. Di dalam file main siapkan sebuah struct berisikan 3 buah property dan 1 method.
+
+```go
+package main
+
+import "net/http"
+import "fmt"
+import "html/template"
+
+type Superhero struct {
+ Name string
+ Alias string
+ Friends []string
+}
+
+func (s Superhero) SayHello(from string, message string) string {
+ return fmt.Sprintf("%s said: \"%s\"", from, message)
+}
+```
+
+Struct `Superhero` di atas nantinya digunakan untuk membuat objek yang kemudian disisipkan ke template view.
+
+Selanjutnya buat fungsi `main()`, isi dengan handler untuk rute `/`. Secara umum isi dari file `main.go` ini mirip seperti yang ada pada chapter sebelumnya.
+
+```go
+func main() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ var person = Superhero{
+ Name: "Bruce Wayne",
+ Alias: "Batman",
+ Friends: []string{"Superman", "Flash", "Green Lantern"},
+ }
+
+ var tmpl = template.Must(template.ParseFiles("view.html"))
+ if err := tmpl.Execute(w, person); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ })
+
+ fmt.Println("server started at localhost:9000")
+ http.ListenAndServe(":9000", nil)
+}
+```
+
+Kemudian isi file `view.html` dengan kode berikut.
+
+```html
+
+
+
+ + {{html "
I'm the Batman!
+{{else if ne .Name "Clark Kent"}} +I'm neither Batman or Superman
+{{end}} +``` + +Lihat hasilnya pada browser. + +![Fungsi Operator Perbandingan](images/B_template_functions_2_cond.png) + +Berikut merupakan daftar operator perbandingan yang didukung oleh template view. + +| Operator | Penjelasan | Analogi | +|:--------:| ----------------------------------------------------- |:-----------:| +| eq | *equal*, sama dengan | `a == b` | +| ne | *not equal*, tidak sama dengan | `a != b` | +| lt | *lower than*, lebih kecil | `a < b` | +| le | *lower than or equal*, lebih kecil atau sama dengan | `a <= b` | +| gt | *greater than*, lebih besar | `a > b` | +| ge | *greater than or equal*, lebih besar atau sama dengan | `a >= b` | + +## B.7.4. Pemanggilan Method + +Cara memanggil method yang disisipkan ke view sama dengan cara pemanggilan fungsi, hanya saja perlu ditambahkan tanda titik `.` (menyesuaikan scope variabelnya). Contohnya bisa dilihat seperti pada kode berikut. + +```html ++ {{.SayHello "Gotham citizen" "You are our hero!"}} +
+``` + +Test hasilnya pada browser. + +![Pemanggilan Method](images/B_template_functions_3_method.png) + +## B.7.5. Fungsi String + +Go juga menyediakan beberapa fungsi string yang bisa dimanfaatkan, yaitu: + + - `print` (merupakan alias dari `fmt.Sprint`) + - `printf` (merupakan alias dari `fmt.Sprintf`) + - `println` (merupakan alias dari `fmt.Sprintln`) + +Cara penggunannya juga masih sama, contoh: + +```html ++ {{printf "%s because I'm %s" "You know why?" "Batman!"}} +
+``` + +Output program: + +![Fungsi String](images/B_template_functions_4_string_func.png) + +Jika merasa sedikit bingung memahami statement di atas, mungkin analogi berikut cukup membantu. + +```go +// template view +printf "%s because I'm %s" "You know why?" "Batman!" + +// go +fmt.Sprintf("%s because I'm %s", "You know why?", "Batman!") +``` + +Kedua statement di atas menghasilkan output yang sama. + +## B.7.6. Fungsi `len` dan `index` + +Kegunaan dari fungsi `len` seperti yang sudah diketahui adalah untuk menghitung jumlah elemen. Sedangkan fungsi `index` digunakan jika elemen tertentu ingin diakses. + +Sebagai contoh, `Friends` yang merupakan array, diakses elemen indeks ke-1 menggunakan `index`, maka caranya: + +```html +{{index .Friends 1}} +``` + +Berikut merupakan contoh penerapan fungsi `len` dan `index`. + +```html ++ Batman have many friends. {{len .Friends}} of them are: + {{index .Friends 0}}, + {{index .Friends 1}}, and + {{index .Friends 2}} +
+``` + +Output program: + +![Fungsilen
dan index
](images/B_template_functions_5_len_index.png)
+
+## B.7.7. Fungsi Operator Logika
+
+Selain fungsi operator perbandingan, terdapat juga operator logika `or`, `and`, dan `not`. Cara penggunaannya adalah dengan dituliskan setelah actions `if` atau `elseif`, sebagai fungsi dengan parameter adalah nilai yang ingin dibandingkan.
+
+> Fungsi `not` ekuivalen dengan `ne`
+
+```html
+{{$cond1 := true}}
+{{$cond2 := false}}
+
+{{if or $cond1 $cond2}}
+ Be like Batman!
+{{end}} +``` + +Output program: + +![Fungsior
, and
, dan not
](images/B_template_functions_6_or_and_not.png)
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/B-template-render-html.md b/en/content-en/B-template-render-html.md
new file mode 100644
index 000000000..9fb4f3816
--- /dev/null
+++ b/en/content-en/B-template-render-html.md
@@ -0,0 +1,148 @@
+# B.4. Template: Render HTML Template
+
+Pada bagian ini kita akan belajar bagaimana cara render file **template** yang berisi **HTML** untuk ditampilkan ke layar browser.
+
+Terdapat banyak jenis template pada Go, di sini yang akan kita pakai adalah template HTML. Package `html/template` berisi banyak sekali fungsi untuk operasi rendering dan parsing file template HTML.
+
+## B.4.1. Struktur Aplikasi
+
+Buat project baru, siapkan file dan folder dengan struktur sesuai dengan gambar berikut.
+
+![Structure](images/B_template_render_html_1_structure.png)
+
+## B.4.2. Back End
+
+Hal pertama yang perlu dilakukan adalah mempersiapkan back end. Buka file `main.go`, import package `net/http`, `html/template`, dan `path`. Siapkan juga rute `/`.
+
+```go
+package main
+
+import "fmt"
+import "net/http"
+import "html/template"
+import "path"
+
+func main() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ // not yet implemented
+ })
+
+ fmt.Println("server started at localhost:9000")
+ http.ListenAndServe(":9000", nil)
+}
+```
+
+Handler rute `/` akan kita isi dengan proses untuk rendering template html untuk ditampilkan ke layar browser. Beberapa data disisipkan dalam proses rendering template.
+
+Silakan tulis kode berikut di dalam handler rute `/`.
+
+```go
+var filepath = path.Join("views", "index.html")
+var tmpl, err = template.ParseFiles(filepath)
+if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+}
+
+var data = map[string]interface{}{
+ "title": "Learning Golang Web",
+ "name": "Batman",
+}
+
+err = tmpl.Execute(w, data)
+if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+}
+```
+
+Package `path` berisikan banyak fungsi yang berhubungan dengan lokasi folder atau path, yang salah satu di antaranya adalah fungsi `path.Join()`. Fungsi ini digunakan untuk menggabungkan folder atau file atau keduanya menjadi sebuah path, dengan separator relatif terhadap OS yang digunakan.
+
+> Separator yang digunakan oleh `path.Join()` adalah `\` untuk windows dan `/` untuk linux/unix/macos.
+
+Contoh penerapan `path.Join()` bisa dilihat di kode di atas, `views` di-join dengan `index.html`, menghasilkan `views/index.html`.
+
+Sedangkan `template.ParseFiles()`, digunakan untuk parsing file template, dalam contoh ini file `view/index.html`. Fungsi ini mengembalikan 2 data, yaitu hasil dari proses parsing yang bertipe `*template.Template`, dan informasi `error` jika ada.
+
+Fungsi `http.Error()` digunakan untuk menandai HTTP request dengan response berupa error dengan kode serta pesan error bisa kita tentukan sendiri. Pada contoh di atas yang digunakan adalah **500 - internal server error**, direpresentasikan oleh variabel `http.StatusInternalServerError`.
+
+Method `Execute()` milik `*template.Template`, digunakan untuk menyisipkan data pada template, kemudian menampilkannya ke browser. Data bisa disipkan ke view dalam bentuk `struct`, `map`, atau `interface{}`.
+
+ - Jika dituliskan dalam bentuk `map`, maka **key** akan menjadi nama variabel dan **value** menjadi nilainya
+ - Jika dituliskan dalam bentuk variabel objek cetakan `struct`, nama **property** akan menjadi nama variabel
+
+Pada contoh di atas, data map yang berisikan key `title` dan `name` disisipkan ke dalam template yang sudah di parsing.
+
+## B.4.3. Front End
+
+OK, back end sudah siap, selanjutnya kita masuk ke bagian user interface. Pada file `views/index.html`, tuliskan kode html sederhana berikut.
+
+```html
+
+
+
+
+ Welcome {{.name}}
+ + +``` + +Untuk menampilkan variabel yang disisipkan ke dalam template, gunakan notasi `{{.namaVariabel}}`. Pada contoh di atas, data `title` dan `name` yang dikirim dari back end ditampilkan. + +Tanda titik "\." pada \{\{\.namaVariabel\}\} menerangkan bahwa variabel tersebut diakses dari **current scope**. Dan current scope default adalah data `map` atau objek yang dilempar back end. + +## B.4.4. Testing + +Semua sudah siap, sekarang jalankan program, lalu lakukan testing di browser. + +![Output HTML](images/B_template_render_html_2_output.png) + +## B.4.5. Static File CSS + +Coba tambahkan sebuah stylesheet di sini. Buat file `assets/site.css`, isi dengan kode berikut. + +```css +body { + font-family: "Helvetica Neue"; + font-weight: bold; + font-size: 24px; + color: #07c; +} +``` + +Pada `views/index.html`, include-kan file css. + +```html + +``` + +Terakhir pada fungsi `main()`, tambahkan router untuk handling file statis. + +```go +func main() { + // ... + + http.Handle("/static/", + http.StripPrefix("/static/", + http.FileServer(http.Dir("assets")))) + + fmt.Println("server started at localhost:9000") + http.ListenAndServe(":9000", nil) +} +``` + +Jalankan aplikasi untuk test hasil. + +![CSS berhasil di-load](images/B_template_render_html_3_static_route.png) + +--- + + + +--- + + diff --git a/en/content-en/B-template-render-partial-html.md b/en/content-en/B-template-render-partial-html.md new file mode 100644 index 000000000..98dd9c38d --- /dev/null +++ b/en/content-en/B-template-render-partial-html.md @@ -0,0 +1,238 @@ +# B.5. Template: Render Partial HTML Template + +Satu buah halaman yang berisikan html bisa saja terbentuk dari lebih dari satu proses parsing template html (parsial). Pada chapter ini kita akan belajar bagaimana membuat, mem-parsing, dan me-render semua template file. + +Ada beberapa metode yang bisa digunakan, 2 di antaranya: + + - Menggunakan fungsi `template.ParseGlob()`. + - Menggunakan fungsi `template.ParseFiles()`. + +## B.5.1. Struktur Aplikasi + +Mari belajar sambil praktek seperti biasa. Buat project baru, siapkan file dan folder dengan susunan seperti dengan gambar berikut. + +![Structure](images/B_template_render_partial_html_1_structure.png) + +## B.5.2. Back End + +Buka `main.go`, isi dengan kode berikut. + +```go +package main + +import ( + "net/http" + "html/template" + "fmt" +) + +type M map[string]interface{} + +func main() { + var tmpl, err = template.ParseGlob("views/*") + if err != nil { + panic(err.Error()) + return + } +} +``` + +Tipe `M` merupakan alias dari `map[string]interface{}`, disiapkan untuk mempersingkat penulisan tipe map tersebut. Pada pembahasan-pembahasan selanjutnya kita akan banyak menggunakan tipe ini. + +Pada kode di atas, di dalam fungsi `main()`, fungsi `template.ParseGlob()` dipanggil, dengan parameter adalah pattern path `"views/*"`. Fungsi ini digunakan untuk memparsing semua file yang match dengan *pattern*/pola yang ditentukan. Fungsi ini mengembalikan 2 objek yaitu `*template.Template` & `error`. + +> Pattern path pada fungsi `template.ParseGlob()` nantinya akan di proses oleh `filepath.Glob()` + +Proses parsing semua file html dalam folder `views` dilakukan di awal, agar ketika suatu endpoint diakses nantinya tidak terjadi proses parsing melainkan hanya proses rendering saja. + +> Parsing semua file menggunakan `template.ParseGlob()` yang dilakukan di luar handler, tidak direkomendasikan dalam fase development. Karena akan mempersulit testing html. Lebih detailnya akan dibahas di bagian bawah. + +Selanjutnya, masih di dalam fungsi `main()`, siapkan 2 buah rute. + +```go +http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) { + var data = M{"name": "Batman"} + err = tmpl.ExecuteTemplate(w, "index", data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +}) + +http.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) { + var data = M{"name": "Batman"} + err = tmpl.ExecuteTemplate(w, "about", data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +}) + +fmt.Println("server started at localhost:9000") +http.ListenAndServe(":9000", nil) +``` + +Kedua rute tersebut sama, pembedanya adalah template yang di-render. Rute `/index` me-render template bernama `index`, dan rute `/about` me-render template bernama `about`. + +Karena semua file html sudah diparsing di awal, maka untuk render template tertentu cukup dengan memanggil method `ExecuteTemplate()`, dengan menyisipkan 3 parameter berikut: + + 1. Parameter ke-1, objek `http.ResponseWriter` + 2. Parameter ke-2, nama template + 3. Parameter ke-3, data + +> Nama template bukanlah nama file. Setelah masuk ke bagian front-end, akan diketahui apa yang dimaksud dengan nama template. + +## B.5.3. Front End + +## B.5.3.1. Template `index.html` + +OK, sekarang waktunya untuk mulai menyiapkan template view. Ada 4 buah template yang harus kita siapkan satu per satu. + +Buka file `index.html`, lalu tulis kode berikut. + +```html +{{define "index"}} + + + + {{template "_header"}} + + + {{template "_message"}} + +Page: Index
+Welcome {{.name}}
+ + +{{end}} +``` + +Pada kode di atas terlihat bahwa ada beberapa kode yang ditulis dengan notasinya `{{ }}`. Berikut adalah penjelasannya. + + - Statement\{\{define "index"\}\}
, digunakan untuk mendefinisikan nama template. Semua blok kode setelah statement tersebut (batasnya adalah hingga statement \{\{end\}\}
) adalah milik template dengan nama `index`. keyword `define` digunakan dalam penentuan nama template.
+ - Statement \{\{template "_header"\}\}
artinya adalah template bernama `_header` di-include ke bagian itu. keyword `template` digunakan untuk include template lain.
+ - Statement \{\{template "_message"\}\}
, sama seperti sebelumnya, template bernama `_message` akan di-include.
+ - Statement \{\{.name\}\}
akan memunculkan data, `name`, yang data ini sudah disisipkan oleh back end pada saat rendering.
+ - Statement \{\{end\}\}
adalah penanda batas akhir pendefinisian template.
+
+## B.5.3.2. Template `about.html`
+
+Template ke-2, `about.html` diisi dengan dengan kode yang sama seperti pada `index.html`, hanya berbeda di bagian nama template dan beberapa text.
+
+```html
+{{define "about"}}
+
+
+
+ {{template "_header"}}
+
+
+ {{template "_message"}}
+ Page: About
+Welcome {{.name}}
+ + +{{end}} +``` + +## B.5.3.3. Template `_header.html` + +Buka file `_header.html`, definisikan template bernama `_header` dengan isi adalah judul halaman. + +```html +{{define "_header"}} +Welcome
+{{end}} +``` + +## B.5.5. Run & Test + +Jalankan aplikasi, test via browser. + +![Rute/about
& /index
](images/B_template_render_partial_html_2_routes.png)
+
+Bisa dilihat pada gambar di atas, ketika rute `/index` dan `/about` di akses, konten yang keluar adalah berbeda, sesuai dengan template yang di-render di masing-masing rute.
+
+## B.5.6. Parsing Banyak File HTML Menggunakan `template.ParseFiles()`
+
+Metode parsing menggunakan `template.ParseGlob()` memiliki kekurangan yaitu sangat tergantung terhadap pattern path yang digunakan. Jika dalam suatu proyek terdapat sangat banyak file html dan folder, sedangkan hanya beberapa yang digunakan, pemilihan pattern path yang kurang tepat akan menjadikan file lain ikut ter-parsing sia-sia.
+
+Dan juga, karena statement `template.ParseGlob()` dieksekusi diluar handler, maka ketika ada perubahan pada salah satu view, lalu halaman di refresh, output yang dihasilkan akan tetap sama. Solusi dari masalah ini adalah dengan memanggil `template.ParseGlob()` di tiap handler rute-rute yang diregistrasikan.
+
+> Best practices yang bisa diterapkan, ketika environment adalah production, maka tempatkan `template.ParseGlob()` di luar (sebelum) handler. Sedangkan pada environment development, taruh `template.ParseGlob()` di dalam masing-masing handler. Gunakan seleksi kondisi untuk mengakomodir skenario ini.
+
+Alternatif metode lain yang bisa digunakan, yang lebih efisien, adalah dengan memanfaatkan fungsi `template.ParseFiles()`. Fungsi ini selain bisa digunakan untuk parsing satu buah file saja (seperti yang sudah dicontohkan pada chapter sebelumnya), bisa digunakan untuk parsing banyak file.
+
+Mari kita praktekan. Ubah handler rute `/index` dan `/about`. Gunakan `template.ParseFiles()` dengan isi parameter (variadic) adalah path dari file-file html yang akan dipergunakan di masing-masing rute. Lalu hapus statement `template.ParseGlob()`
+
+ - Rute `/index` dan handlernya.
+
+ ```go
+ http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) {
+ var data = M{"name": "Batman"}
+ var tmpl = template.Must(template.ParseFiles(
+ "views/index.html",
+ "views/_header.html",
+ "views/_message.html",
+ ))
+
+ var err = tmpl.ExecuteTemplate(w, "index", data)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ })
+ ```
+
+ - Rute `/about` dan handlernya.
+
+ ```go
+ http.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) {
+ var data = M{"name": "Batman"}
+ var tmpl = template.Must(template.ParseFiles(
+ "views/about.html",
+ "views/_header.html",
+ "views/_message.html",
+ ))
+
+ var err = tmpl.ExecuteTemplate(w, "about", data)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ })
+ ```
+
+ - Tak lupa **hapus** statement `template.ParseGlob()`.
+
+ ```go
+ var tmpl, err = template.ParseGlob("views/*")
+ if err != nil {
+ panic(err.Error())
+ return
+ }
+ ```
+
+Rute `/index` memakai view `_header.html`, `_message.html`, dan `index.html`; sedangkan rute `/about` tidak memakai `index.html`, melainkan menggunakan `about.html`.
+
+Wrap fungsi `template.ParseFiles()` dalam `template.Must()`. Fungsi ini berguna untuk deteksi error pada saat membuat instance `*template.Template` baru atau ketika sedang mengolahnya. Ketika ada error, `panic` dimunculkan.
+
+Jalankan aplikasi untuk test hasilnya.
+
+---
+
+
+
+---
+
+
diff --git a/en/content-en/C-advanced-configuration-viper.md b/en/content-en/C-advanced-configuration-viper.md
new file mode 100644
index 000000000..954097f92
--- /dev/null
+++ b/en/content-en/C-advanced-configuration-viper.md
@@ -0,0 +1,159 @@
+# C.10. Advanced Configuration Menggunakan Viper
+
+Pada chapter ini kita akan belajar cara mudah manajemen konfigurasi file JSON menggunakan [Viper](http://github.com/spf13/viper) library. Inti dari chapter ini sebenarnya adalah sama dengan yang sudah dibahas pada [B.22. Simple Configuration](/B-simple-configuration.html), hanya saja di sini proses parsing di-handle oleh 3rd party dengan tidak menggunakan struct untuk pengaksesannya.
+
+Kekurangan dari teknik menyimpan konfigurasi dalam object struct adalah, pada saat ada kebutuhan untuk menambah atau merubah isi konfigurasi file, maka mengharuskan developer juga mengubah skema struct penampung. Pada bagian ini, pengaksesan property konfigurasi dilakukan lewat notasi string konfigurasinya.
+
+## C.10.1. JSON Configuration
+
+Mari langsung kita praktekan. Buat project baru seperti biasa, buat file konfigurasi `app.config.json`, isi dengan data berikut.
+
+```json
+{
+ "appName": "SimpleApp",
+
+ "server": {
+ "port": 9000
+ }
+}
+```
+
+Property `appName` berisi nama aplikasi, sedangkan `server.port` representasi dari port web server.
+
+Selanjutnya buat `main.go`, lakukan parsing pada file konfigurasi.
+
+```go
+package main
+
+import (
+ "github.com/labstack/echo"
+ "github.com/spf13/viper"
+ "net/http"
+)
+
+func main() {
+ e := echo.New()
+
+ viper.SetConfigType("json")
+ viper.AddConfigPath(".")
+ viper.SetConfigName("app.config")
+
+ err := viper.ReadInConfig()
+ if err != nil {
+ e.Logger.Fatal(err)
+ }
+
+ // ...
+}
+```
+
+Kode di atas adalah contoh penggunaan dasar viper, untuk parsing file konfigurasi bertipe `JSON`. Fungsi `viper.SetConfigType()` digunakan untuk set jenis file konfigurasi.
+
+Berikut merupakan list format yang didukung oleh viper.
+
+ - json
+ - toml
+ - yaml
+ - yml
+ - properties
+ - props
+ - prop
+ - env
+ - dotenv
+ - tfvars
+ - ini
+ - hcl
+
+Fungsi `.AddConfigPath()` digunakan untuk mendaftarkan path folder di mana file-file konfigurasi berada. Fungsi ini bisa dipanggil beberapa kali, jika memang ada banyak file konfigurasi tersimpan dalam path berbeda.
+
+Statement `.SetConfigName()` dieksekusi dengan parameter berisi nama file konfigurasi secara eksplisit tanpa ekstensi. Misalkan nama file adalah `app.config.json`, maka parameter cukup ditulis `app.config`.
+
+Fungsi `.ReadInConfig()` digunakan untuk memproses file-file konfigurasi sesuai dengan path dan nama yang sudah ditentukan.
+
+OK, kembali ke bagian tulis-menulis kode. Tambahkan beberapa kode untuk print nama aplikasi, sebuah rute, dan start web server.
+
+```go
+e.GET("/index", func(c echo.Context) (err error) {
+ return c.JSON(http.StatusOK, true)
+})
+
+e.Logger.Print("Starting", viper.GetString("appName"))
+e.Logger.Fatal(e.Start(":" + viper.GetString("server.port")))
+```
+
+Cara pengaksesan konfigurasi bisa dilihat pada kode di atas. Statement `viper.GetString("appName")` mengembalikan string `"SimpleApp"`, sesuai dengan isi pada file konfigurasi.
+
+Selain `.GetString()`, masih banyak lagi fungsi lain yang bisa digunakan, sesuaikan dengan tipe data property yang akan diambil.
+
+| Fungsi | Return type |
+| ------------------------------- | ----------------------- |
+| Get(string) | interface{} |
+| GetBool(string) | bool |
+| GetDuration(string) | time.Duration |
+| GetFloat64(string) | float64 |
+| GetInt(string) | int |
+| GetInt32(string) | int32 |
+| GetInt64(string) | int64 |
+| GetSizeInBytes(string) | uint |
+| GetString(string) | string |
+| GetStringMap(string) | map[string]interface{} |
+| GetStringMapString(string) | map[string]string |
+| GetStringMapStringSlice(string) | map\[string\]\[\]string |
+| GetStringSlice(string) | []string |
+| GetTime(string) | time.Time |
+
+Pengaksesan property nested seperti `server.port` juga mudah, tinggal tulis saja skema property yang ingin diambil nilainya dengan separator tanda titik (`.`).
+
+Jalankan aplikasi untuk test hasilnya.
+
+![Sample output](images/C_advanced_configuration_viper_1_app.png)
+
+## C.10.2. YAML Configuration
+
+Cara penerapan viper pada file konfigurasi bertipe `.yaml` kurang lebih sama seperti pada file `.json`. Cukup ubah config type nya dan semua akan beres dengan sendirinya.
+
+Mari kita langsung praktekan saja. Buat file konfigurasi baru `app.config.yaml` dengan isi berikut.
+
+```yaml
+appName: SimpleApp
+server:
+ port: 9000
+```
+
+Pada bagian kode golang, cukup ubah argumen pemanggilan fungsi set config type.
+
+```go
+viper.SetConfigType("yaml")
+```
+
+Jalankan aplikasi, dan hasilnya sama seperti sebelumnya.
+
+## C.10.3. Watcher Configuration
+
+Viper memiliki banyak fitur, satu di antaranya adalah mengaktifkan watcher pada file konfigurasi. Dengan adanya watcher, maka kita bisa membuat callback yang akan dipanggil setiap kali ada perubahan konfigurasi.
+
+```go
+viper.WatchConfig()
+viper.OnConfigChange(func(e fsnotify.Event) {
+ fmt.Println("Config file changed:", e.Name)
+})
+```
+
+Penggunaan fasilitas watcher memerlukan tambahan 3rd party library [fsnotify](https://github.com/fsnotify/fsnotify), jadi jangan lupa juga untuk meng-*import*-nya.
+
+---
+
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+ - [fsnotify](https://github.com/fsnotify/fsnotify), by fsnotify team, BSD-3-Clause license
+ - [Viper](https://github.com/spf13/viper), by Steve Francia, MIT license
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/C-advanced-middleware-and-logging.md b/en/content-en/C-advanced-middleware-and-logging.md
new file mode 100644
index 000000000..87ee8bf6d
--- /dev/null
+++ b/en/content-en/C-advanced-middleware-and-logging.md
@@ -0,0 +1,280 @@
+# C.8. Advanced Middleware & Logging (Logrus, Echo Logger)
+
+Middleware adalah sebuah blok kode yang dipanggil sebelum ataupun sesudah http request di-proses. Middleware biasanya dibuat per-fungsi-nya, contohnya: middleware autentikasi, middleware untuk logging, middleware untuk gzip compression, dan lainnya.
+
+Pada chapter ini kita akan belajar cara membuat dan me-manage middleware.
+
+## C.8.1. Custom Middleware
+
+Pembuatan middleware pada echo sangat mudah, cukup gunakan method `.Use()` milik objek echo untuk registrasi middleware. Method ini bisa dipanggil berkali-kali, dan eksekusi middleware-nya sendiri adalah berurutan sesuai dengan urutan registrasi.
+
+OK, langsung saja, buat folder project baru dengan isi sebauh file `main.go` seperti biasanya. Lalu tulis kode berikut.
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/labstack/echo"
+ "net/http"
+)
+
+func main() {
+ e := echo.New()
+
+ // middleware here
+
+ e.GET("/index", func(c echo.Context) (err error) {
+ fmt.Println("threeeeee!")
+
+ return c.JSON(http.StatusOK, true)
+ })
+
+ e.Logger.Fatal(e.Start(":9000"))
+}
+```
+
+Kode di atas merupakan aplikasi web kecil, berisi satu buah rute `/index` yang ketika di akses akan print log `"threeeeee!"` ke console.
+
+Selanjutnya, buat dua middleware, `middlewareOne` dan `middlewareTwo`. Isinya juga menampilkan log.
+
+```go
+func middlewareOne(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ fmt.Println("from middleware one")
+ return next(c)
+ }
+}
+
+func middlewareTwo(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ fmt.Println("from middleware two")
+ return next(c)
+ }
+}
+```
+
+Registrasikan kedua middleware di atas. Kode di bawah ini adalah contoh cara registrasinya.
+
+```go
+func main() {
+ e := echo.New()
+
+ e.Use(middlewareOne)
+ e.Use(middlewareTwo)
+
+ // ...
+```
+
+Jalankan aplikasi, lihat hasilnya.
+
+![Middleware logging](images/C_advanced_middleware_and_logging_1_middleware_log.png)
+
+## C.8.2. Integrasi Middleware ber-skema Non-Echo-Middleware
+
+Di echo, fungsi middleware harus memiliki skema `func(echo.HandlerFunc)echo.HandlerFunc`. Untuk 3rd party middleware, tetap bisa dikombinasikan dengan echo, namun membutuhkan sedikit penyesuaian tentunya.
+
+Echo menyediakan solusi mudah untuk membantu integrasi 3rd party middleware, yaitu dengan menggunakan fungsi `echo.WrapMiddleware()` untuk mengkonversi middleware menjadi echo-compatible-middleware, dengan syarat skema harus dalam bentuk `func(http.Handler)http.Handler`.
+
+Silakan praktekan kode berikut.
+
+```go
+func middlewareSomething(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Println("from middleware something")
+ next.ServeHTTP(w, r)
+ })
+}
+
+func main() {
+ e := echo.New()
+
+ e.Use(echo.WrapMiddleware(middlewareSomething))
+
+ // ...
+```
+
+Bisa dilihat, fungsi `middlewareSomething` tidak menggunakan skema middleware milik echo, namun tetap bisa digunakan dalam `.Use()` dengan cara dibungkus fungsi `echo.WrapMiddleware()`.
+
+## C.8.3. Echo Middleware: Logger
+
+Seperti yang sudah penulis jelaskan pada awal chapter B, bahwa echo merupakan framework besar, di dalamnya terdapat banyak dependency dan library, salah satunya adalah logging middleware.
+
+Cara menggunakan logging middleware (ataupun middleware lainnya milik echo) adalah dengan meng-import package `github.com/labstack/echo/middleware`, lalu panggil nama middleware nya. Lebih detailnya silakan baca dokumentasi echo mengenai middleware di https://echo.labstack.com/middleware.
+
+Berikut merupakan contoh penggunaan echo logging middleware.
+
+```go
+package main
+
+import (
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+ "net/http"
+)
+
+func main() {
+ e := echo.New()
+
+ e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
+ Format: "method=${method}, uri=${uri}, status=${status}\n",
+ }))
+
+ e.GET("/index", func(c echo.Context) (err error) {
+ return c.JSON(http.StatusOK, true)
+ })
+
+ e.Logger.Fatal(e.Start(":9000"))
+}
+```
+
+Cara menggunakan echo logging middleware adalah dengan membuat objek logging baru lewat statement `middleware.Logger()`, lalu membungkusnya dengan `e.Use()`. Atau bisa juga menggunakan `middleware.LoggerWithConfig()` jika logger yang dibuat memerlukan beberapa konfigurasi (tulis konfigurasinya sebagai property objek cetakan `middleware.LoggerConfig`, lalu tempatkan sebagai parameter method pemanggilan `.LoggerWithConfig()`).
+
+Jalankan aplikasi, lalu lihat hasilnya.
+
+![Echo logging middleware](images/C_advanced_middleware_and_logging_2_middleware_logging.png)
+
+---
+
+Berikut merupakan list middleware yang disediakan oleh echo, atau cek https://echo.labstack.com/middleware untuk lebih detailnya.
+
+- Basic Auth
+- Body Dump
+- Body Limit
+- CORS
+- CSRF
+- Casbin Auth
+- Gzip
+- JWT
+- Key Auth
+- Logger
+- Method Override
+- Proxy
+- Recover
+- Redirect
+- Request ID
+- Rewrite
+- Secure
+- Session
+- Static
+- Trailing Slash
+
+## C.8.4. 3rd Party Logging Middleware: Logrus
+
+Selain dengan membuat middleware sendiri, ataupun menggunakan echo middleware, kita juga bisa menggunakan 3rd party middleware lain. Tinggal sesuaikan sedikit agar sesuai dengan skema fungsi middleware milik echo untuk bisa digunakan.
+
+Next, kita akan coba untuk meng-implementasi salah satu golang library terkenal untuk keperluan logging, yaitu [logrus](https://github.com/sirupsen/logrus).
+
+Buat file baru, import packages yang diperlukan, lalu buat fungsi `makeLogEntry()`, fungsi ini menerima satu parameter bertipe `echo.Context` dan mengembalikan objek logrus `*log.Entry`.
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/labstack/echo"
+ log "github.com/sirupsen/logrus"
+ "net/http"
+ "time"
+)
+
+func makeLogEntry(c echo.Context) *log.Entry {
+ if c == nil {
+ return log.WithFields(log.Fields{
+ "at": time.Now().Format("2006-01-02 15:04:05"),
+ })
+ }
+
+ return log.WithFields(log.Fields{
+ "at": time.Now().Format("2006-01-02 15:04:05"),
+ "method": c.Request().Method,
+ "uri": c.Request().URL.String(),
+ "ip": c.Request().RemoteAddr,
+ })
+}
+```
+
+Fungsi `makeLogEntry()` bertugas membuat basis log objek yang akan ditampilkan. Informasi standar seperti waktu, dibentuk di dalam fungsi ini. Khusus untuk log yang berhubungan dengan http request, maka informasi yang lebih detail dimunculkan (http method, url, dan IP).
+
+Selanjutnya, buat fungsi `middlewareLogging()` dan `errorHandler()`.
+
+```go
+func middlewareLogging(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ makeLogEntry(c).Info("incoming request")
+ return next(c)
+ }
+}
+
+func errorHandler(err error, c echo.Context) {
+ report, ok := err.(*echo.HTTPError)
+ if ok {
+ report.Message = fmt.Sprintf("http error %d - %v", report.Code, report.Message)
+ } else {
+ report = echo.NewHTTPError(http.StatusInternalServerError, err.Error())
+ }
+
+ makeLogEntry(c).Error(report.Message)
+ c.HTML(report.Code, report.Message.(string))
+}
+```
+
+Fungsi `middlewareLogging()` bertugas untuk menampilkan log setiap ada http request masuk. Dari objek `*log.Entry` -yang-dicetak-lewat-fungsi-`makeLogEntry()`-, panggil method `Info()` untuk menampilkan pesan log dengan level adalah INFO.
+
+Sedang fungsi `errorHandler` akan digunakan untuk meng-override default http error handler milik echo. Dalam fungsi ini log dengan level ERROR dimunculkan lewat pemanggilan method `Error()` milik `*log.Entry`.
+
+Buat fungsi `main()`, implementasikan semua fungsi tersebut, siapkan yang harus disiapkan.
+
+```go
+func main() {
+ e := echo.New()
+
+ e.Use(middlewareLogging)
+ e.HTTPErrorHandler = errorHandler
+
+ e.GET("/index", func(c echo.Context) error {
+ return c.JSON(http.StatusOK, true)
+ })
+
+ lock := make(chan error)
+ go func(lock chan error) { lock <- e.Start(":9000") }(lock)
+
+ time.Sleep(1 * time.Millisecond)
+ makeLogEntry(nil).Warning("application started without ssl/tls enabled")
+
+ err := <-lock
+ if err != nil {
+ makeLogEntry(nil).Panic("failed to start application")
+ }
+}
+```
+
+Fungsi `main()` di atas berisikan beberapa kode yang jarang kita gunakan, pada saat men-start web server.
+
+Web server di start dalam sebuah goroutine. Karena method `.Start()` milik echo adalah blocking, kita manfaatkan nilai baliknya untuk di kirim ke channel `lock`.
+
+Selanjutnya dengan delay waktu 1 milidetik, log dengan level WARNING dimunculkan. Ini hanya simulasi saja, karena memang aplikasi tidak di start menggunakan ssl/tls. Dengan memberi delay 1 milidetik, maka log WARNING bisa muncul setelah log default dari echo muncul.
+
+Nah pada bagian penerimaan channel, jika nilai baliknya tidak `nil` maka pasti terjadi error pada saat start web server, dan pada saat itu juga munculkan log dengan level PANIC.
+
+OK, sekarang jalankan lalu test aplikasi.
+
+![Logrus preview](images/C_advanced_middleware_and_logging_3_middleware_logrus.png)
+
+Satu kata, *cantik*.
+
+---
+
+ - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license
+ - [Logrus](https://github.com/sirupsen/logrus), by Simon Eskildsen, MIT license
+
+---
+
+
+
+
+---
+
+
diff --git a/en/content-en/C-best-practice-configuration-env-var.md b/en/content-en/C-best-practice-configuration-env-var.md
new file mode 100644
index 000000000..e56eccbab
--- /dev/null
+++ b/en/content-en/C-best-practice-configuration-env-var.md
@@ -0,0 +1,166 @@
+# C.11. Best Practice Configuration Menggunakan Environment Variable
+
+Pada bagian ini kita akan mempelajari penerapan konfigurasi pada *environment variable*.
+
+## C.11.1. Definisi
+
+*Environment variable* merupakan variabel yang berada di lapisan *runtime* sistem operasi. Karena *env var* atau *environment variable* merupakan variabel seperti pada umumnya, maka kita bisa melakukan operasi seperti mengubah nilainya atau mengambil nilainya.
+
+Salah satu *env var* yang mungkin sering pembaca temui adalah `PATH`. `PATH` sendiri merupakan variabel yang digunakan oleh sistem operasi untuk men-*specify* direktori tempat di mana *binary* atau *executable* berada.
+
+Default-nya, sistem operasi pasti mempunyai beberapa *env var* yang sudah ada tanpa kita set, salah satunya seperti `PATH` tadi, juga lainnya. Variabel-variabel tersebut digunakan oleh sistem operasi untuk keperluan mereka. Tapi karena variabel juga bisa diakses oleh kita (selaku developer), maka kita pun juga bisa mempergunakannya untuk kebutuhan tertentu.
+
+Selain *reserved env var*, kita bisa juga membuat variabel baru yang hanya digunakan untuk keperluan program secara spesifik.
+
+## C.11.2. Penggunaan *env var* Sebagai Media Untuk Definisi Konfigurasi Program
+
+Pada chapter [B.22. Simple Configuration](/B-simple-configuration.html) dan juga [C.10. Advanced Configuration: Viper](/C-advanced-configuration-viper.html), kita telah belajar cara pendefinisian konfigurasi dengan memanfaatkan file seperti JSON maupun YAML.
+
+Pada chapter kali ini kita akan mendefinisikan konfigurasi yang sama tapi tidak di file, melainkan di *environment variable*.
+
+Definisi konfigurasi di env var banyak manfaatnya, salah satunya:
+
+- Di support secara *native* oleh **semua sistem operasi**.
+- Sudah sangat umum diterapkan di banyak aplikasi dan platform.
+- *Straightforward* dan tidak tergantung ke file tertentu.
+- Sharing konfigurasi dengan aplikasi/service lain menjadi lebih mudah.
+- Mudah untuk di maintain, tidak perlu repot buka file kemudian edit lalu simpan ulang.
+- ... dan banyak lagi lainnya.
+
+Jadi bisa dibilang penulisan konfigurasi di env var merupakan *best practice* untuk banyak jenis kasus, terutama pada *microservice*, pada aplikasi/service yang *distributable*, maupun pada aplikasi monolith yang manajemenya ter-automatisasi.
+
+Memang kalau dari sisi readability sangat kalah kalau dibandingkan dengan JSON atau YAML, tapi saya sampaikan bahwa meski effort koding bakal lebih banyak, akan ada sangat banyak manfaat yang bisa didapat dengan menuliskan konfigurasi di *env var*, terutama pada bagian **devops**.
+
+## C.11.3. Praktek
+
+Mari kita praktekan, buat 1 folder project baru, kemudian `main.go`, lalu isi file tersebut dengan kode berikut.
+
+```go
+package main
+
+import (
+ "net/http"
+ "os"
+ "strconv"
+ "time"
+
+ "github.com/labstack/echo"
+)
+
+func main() {
+ e := echo.New()
+
+ // ...
+}
+```
+
+Pada bagian main, tepat di bawah *statement* pembuatan objek `echo`, ambil nilai konfigurasi nama aplikasi dari *env var*. Caranya kurang lebih seperti berikut.
+
+```go
+confAppName := os.Getenv("APP_NAME")
+if confAppName == "" {
+ e.Logger.Fatal("APP_NAME config is required")
+}
+```
+
+Jadi `APP_NAME` di situ merupakan nama *env var*-nya. Umumnya env var tidak dituliskan dalam bentuk `camelCase`, tapi dalam bentuk `UPPERCASE` dengan separator kata adalah underscore. Untuk *value*-nya nanti tinggal kita siapkan saja sebelum proses eksekusi program.
+
+> `man bash`:
+ + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Proin tempor ut ipsum et feugiat. Phasellus porttitor, + felis et gravida aliquam, + eros orci dignissim magna, at tristique elit massa at magna. + Etiam et dignissim mi. Phasellus laoreet nulla non aliquam imperdiet. + Aenean varius turpis at orci posuere, ut bibendum lorem porta. + Maecenas ullamcorper posuere ante quis ultricies. Aliquam erat volutpat. + Vestibulum ante ipsum primis in faucibus orci luctus et + ultrices posuere cubilia Curae; + Pellentesque eu tellus ante. Vivamus varius nisi non nulla imperdiet, + vitae pellentesque nibh varius. Fusce diam magna, iaculis eget felis id, + accumsan convallis elit. + Phasellus in magna placerat, aliquet ante sed, luctus massa. + Sed fringilla bibendum feugiat. Suspendisse tempus, purus sit amet + accumsan consectetur, ipsum odio commodo nisi, + vel dignissim massa mi ac turpis. Ut fringilla leo ut risus facilisis, + nec malesuada nunc ornare. Nulla a dictum augue. +
+ + + + + + +``` + +File html di atas akan kita konversi menjadi sebuah file baru bertipe PDF. Konversi dilakukan menggunakan library wkhtmltopdf. Library ini sebenarnya adalah aplikasi CLI yang dibuat menggunakan bahasa **C++**. Untuk bisa menggunakannya kita harus mengunduh lalu meng-install-nya terlebih dahulu. + +Silakan unduh installer wkhtmltopdf di https://wkhtmltopdf.org/downloads.html, pilih sesuai dengan sistem operasi yang digunakan. + +karena wkhtmltopdf merupakan sebuah aplikasi CLI, maka penggunaannya bisa lewat dua cara. + + - Cara ke-1: Menggunakan `exec.Command()` untuk mengeksekusi binary. Path file html target disisipkan sebagai argumen command. Silakan merujuk ke referensi pada chapter [A.49. Exec](/A-exec.html) untuk mempelajari cara penggunaan exec. + - Cara ke-2: Menggunakan golang wrapper [go-wkhtmltopdf](github.com/SebastiaanKlippert/go-wkhtmltopdf). Cara ini adalah yang kita pilih. + +Secara teknis, go-wkhtmltopdf melakukan hal yang sama dengan cara pertama, yaitu mengeksekusi binary wkhtmltopdf menggunakan `exec.Command()`. + +Mari langsung kita praktekan, buat folder project baru. Siapkan file main. Isi dengan kode berikut. + +```go +package main + +import ( + "github.com/SebastiaanKlippert/go-wkhtmltopdf" + "log" + "os" +) + +func main() { + pdfg, err := wkhtmltopdf.NewPDFGenerator() + if err != nil { + log.Fatal(err) + } + + // ... +} +``` + +Pembuatan objek PDF dilakukan lewat `wkhtmltopdf.NewPDFGenerator()`. Fungsi tersebut mengembalikan dua buah objek, objek dokumen dan error (jika ada). + +Satu objek dokumen merepresentasikan 1 buah file PDF. + +Kemudian tambahkan kode untuk membaca file `input.html`. Gunakan `os.Open`, agar file tidak langsung dibaca, melainkan dijadikan sebagai `io.Reader`. Masukan objek reader ke fungsi `wkhtmltopdf.NewPageReader()`, lalu sisipkan kembaliannya sebagai page baru di objek PDF. + +Kurang lebih kode-nya seperti berikut. + +```go +f, err := os.Open("./input.html") +if f != nil { + defer f.Close() +} +if err != nil { + log.Fatal(err) +} + +pdfg.AddPage(wkhtmltopdf.NewPageReader(f)) + +pdfg.Orientation.Set(wkhtmltopdf.OrientationPortrait) +pdfg.Dpi.Set(300) +``` + +Method `.AddPage()` milik objek PDF, digunakan untuk menambahkan halaman baru. Halaman baru sendiri dibuat lewat `wkhtmltopdf.NewPageReader()`. Jika ternyata konten pada halaman terlalu panjang untuk dijadikan 1 buah page, maka akan secara otomatis dibuatkan page selanjutnya menyesuaikan konten. + +Statement `pdfg.Orientation.Set()` digunakan untuk menentukan orientasi dokumen, apakah portrait atau landscape. Statement `pdfg.Dpi.Set()` digunakan untuk menge-set DPI dokumen. + +Gunakan API yang tersedia milik go-wkhtmltopdf sesuai kebutuhan. Silakan merujuk ke https://godoc.org/github.com/SebastiaanKlippert/go-wkhtmltopdf untuk melihat API apa saja yang tersedia. + +Untuk menyimpan objek dokumen menjadi file fisik PDF, ada beberapa step yang harus dilakukan. Pertama, buat dokumen dalam bentuk buffer menggunakan method `.Create()`. + +```go +err = pdfg.Create() +if err != nil { + log.Fatal(err) +} +``` + +Setelah itu, outputkan buffer tersebut sebagai file fisik menggunakan `.WriteFile()`. Sisipkan path destinasi file sebagai parameter method tersebut. + +```go +err = pdfg.WriteFile("./output.pdf") +if err != nil { + log.Fatal(err) +} + +log.Println("Done") +``` + +Test aplikasi yang sudah kita buat, lihat hasil generated PDF-nya. + +![HTML to PDF](images/C_convert_html_to_pdf_1_convert_html_to_pdf.png) + +Bisa dilihat, dalam satu PDF dua page muncul, hal ini karena memang isi `input.html` terlalu panjang untuk dijadikan sebagai satu page. + +Cara yang kita telah pelajari ini cocok digunakan pada file html yang isinya sudah pasti pada saat file tersebut di-load. + +## C.21.2. Konversi HTML dari URL Menjadi PDF + +Bagaimana untuk HTML yang sumber nya bukan dari file fisik, melainkan dari URL? tetap bisa dilakukan. Caranya dengan mendaftarkan url sebagai objek page lewat `wkhtmltopdf.NewPage()`, lalu memasukannya ke dalam dokumen sebagai page. Contoh penerapannya bisa dilihat pada kode di bawah ini. + +```go +pdfg, err := wkhtmltopdf.NewPDFGenerator() +if err != nil { + log.Fatal(err) +} + +pdfg.Orientation.Set(wkhtmltopdf.OrientationLandscape) + +page := wkhtmltopdf.NewPage("http://localhost:9000") +page.FooterRight.Set("[page]") +page.FooterFontSize.Set(10) +pdfg.AddPage(page) + +err = pdfg.Create() +if err != nil { + log.Fatal(err) +} + +err = pdfg.WriteFile("./output.pdf") +if err != nil { + log.Fatal(err) +} + +log.Println("Done") +``` + +Cara ini cocok digunakan untuk konversi data HTML yang isinya muncul pada saat page load. Untuk konten-konten yang munculnya asynchronous, seperti di event `documen.onload` ada AJAX lalu setelahnya konten baru ditulis, tidak bisa menggunakan cara ini. Solusinya bisa menggunakan teknik export ke PDF dari sisi front end. + +--- + +- [gofpdf](https://github.com/jung-kurt/gofpdf), by Kurt Jung, MIT license +- [wkhtmltopdf](https://github.com/wkhtmltopdf/wkhtmltopdf), by Ashish Kulkarni, LGPL-3.0 license +- [go-wkhtmltopdf](https://github.com/SebastiaanKlippert/go-wkhtmltopdf), by Sebastiaan Klippert, MIT license + +--- + + + +--- + + diff --git a/en/content-en/C-cors-preflight-request.md b/en/content-en/C-cors-preflight-request.md new file mode 100644 index 000000000..9b6ab9958 --- /dev/null +++ b/en/content-en/C-cors-preflight-request.md @@ -0,0 +1,301 @@ +# C.14. CORS & Preflight Request + +Pada chapter ini kita akan belajar tentang Cross-Origin Resource Sharing (CORS) dan Preflight Request. + +## C.14.1. Teori & Penerapan + +CORS adalah mekanisme untuk memberi tahu browser, apakah sebuah request yang di-dispatch dari aplikasi web domain lain atau origin lain, ke aplikasi web kita itu diperbolehkan atau tidak. Jika aplikasi kita tidak mengijinkan maka akan muncul error, dan request pasti digagalkan oleh browser. + +CORS hanya berlaku pada request-request yang dilakukan lewat browser, dari javascript; dan tujuan request-nya berbeda domain/origin. Jadi request yang dilakukan dari curl maupun dari back end, tidak terkena dampak aturan CORS. + +> Request jenis ini biasa disebut dengan istilah cross-origin HTTP request. + +Konfigurasi CORS dilakukan di **response header** aplikasi web. Penerapannya di semua bahasa pemrograman yang web-based adalah sama, yaitu dengan memanipulasi response header-nya. Berikut merupakan list header yang bisa digunakan untuk konfigurasi CORS. + + - Access-Control-Allow-Origin + - Access-Control-Allow-Methods + - Access-Control-Allow-Headers + - Access-Control-Allow-Credentials + - Access-Control-Max-Age + +Konfigurasi CORS berada di sisi server, di aplikasi web tujuan request. + +Permisalan: aplikasi kita di local mengambil data dari google.com, maka konfigurasi CORS berada di google.com; Jika kita terkena error CORS maka tak ada lagi yang bisa dilakukan, karena CORS aplikasi tujuan dikontrol oleh orang-orang yang ada di google.com. + +Agar lebih mudah untuk dipahami bagaimana penerapannya, mari langsung kita praktekan seperti biasanya. + +## C.14.2. Aplikasi dengan konfigurasi CORS sederhana + +Buat project baru, lalu isi fungsi `main()` dengan kode berikut. Aplikasi sederhana ini akan kita jalankan pada domain atau origin `http://localhost:3000/`, lalu akan kita coba akses dari domain berbeda. + +```go +package main + +import ( + "log" + "net/http" +) + +func main() { + http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "https://www.google.com") + w.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token") + + if r.Method == "OPTIONS" { + w.Write([]byte("allowed")) + return + } + + w.Write([]byte("hello")) + }) + + log.Println("Starting app at :9000") + http.ListenAndServe(":9000", nil) +} +``` + +Seperti yang sudah dijelaskan, bahwa konfigurasi CORS berada di header response. Pada kode di atas 3 buah property header untuk keperluan CORS digunakan. + +Header `Access-Control-Allow-Origin` digunakan untuk menentukan domain mana saja yang diperbolehkan mengakses aplikasi ini. Kita bisa set value-nya dengan banyak origin, hal ini diperbolehkan dalam [spesifikasi CORS](https://www.w3.org/TR/cors/#access-control-allow-origin-response-header) namun sayangnya banyak browser yang tidak support. + +```http +Access-Control-Allow-Origin: https://www.google.com +``` + +Kode di atas artinya request yang di-dispatch dari https://www.google.com diijinkan untuk masuk; Penulis memilih domain google karena testing akan dilakukan dari sana, dengan tujuan destinasi request adalah `http://localhost:3000/`. + +Simulasi pada chapter ini adalah **aplikasi web localhost:3000 diakses dari google.com** (eksekusi request sendiri kita lakukan dari browser dengan memanfaatkan developer tools milik chrome). BUKAN google.com diakses dari aplikasi web localhost:3000, jangan sampai dipahami terbalik. + +Kembali ke pembahasan source code. Dua header CORS lainnya digunakan untuk konfigurasi yang lebih mendetail. + +```http +Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT +Access-Control-Allow-Headers: Content-Type, X-CSRF-Token +``` + +Header `Access-Control-Allow-Methods` menentukan HTTP Method mana saja yang diperbolehkan masuk (penulisannya dengan pembatas koma). + +> Dianjurkan untuk selalu memasukan method `OPTIONS` karena method ini dibutuhkan oleh preflight request. + +Header `Access-Control-Allow-Headers` menentukan key header mana saja yang diperbolehkan di-dalam request. + +Jika request tidak memenuhi salah satu saja dari ke-tiga rules di atas, maka request bisa dipastikan gagal. Contoh: + + - Request dari https://novalagung.com ke http://localhost:3000, hasilnya pasti gagal, karena origin novalagung.com tidak diijinkan untuk mengakses http://localhost:3000. + - Request dari https://www.google.com ke http://localhost:3000 dengan method adalah `DELETE`, hasilnya pasti gagal. Method `DELETE` adalah tidak di-ijinkan. hanya empat method `OPTIONS`, `GET`, `POST`, `PUT` yang diijinkan. + - Request dari https://www.google.com ke http://localhost:3000 dengan method GET, degan header `Authorization: Basic xxx` dan `X-CSRF-Token: xxxx`, hasilnya adalah gagal. Karena salah satu dari kedua header tersebut tidak diijinkan (header `Authorization`). + - Request dari https://www.google.com ke http://localhost:3000 dengan method `GET` dan memiliki header `Content-Type` adalah diijinkan masuk, karena memenuhi semua aturan yang kita definiskan. + +Khusus untuk beberapa header seperti `Accept`, `Origin`, `Referer`, dan `User-Agent` tidak terkena efek CORS, karena header-header tersebut secara otomatis di-set di setiap request. + +## C.14.3. Testing CORS + +#### ◉ Persiapan + +Ada beberapa hal yang perlu dipersiapkan. Pertama, pastikan punya google chrome. Lalu install extension [jQuery Injector](https://chrome.google.com/webstore/detail/jquery-injector/ekkjohcjbjcjjifokpingdbdlfekjcgi?hl=en). Buka https://www.google.com lalu inject jQuery. Dengan melakukan inject jQuery secara paksa maka dari situs google kita bisa menggunakan jQuery. + +Buka chrome developer tools, klik tab console. Lalu jalankan perintah jQuery AJAX berikut. + +```js +$.ajax({ + url: "http://localhost:9000/index", +}) +``` + +Silakan lihat gambar berikut untuk memperjelas. + +![CORS google to localhost](images/C_cors_preflight_request_1_cors_from_google_to_localhost.png) + +Bisa dilihat, tidak ada error, karena memang request dari google diijinkan. Silakan coba-coba melakukan request AJAX lainnya dengan method POST, DELETE, atau lainnya; atau ditambah dengan menyisipkan header tertentu dalam ajax request. + +#### ◉ Akses http://localhost:9000 dari Origin yang Tidak Didaftarkan di CORS + +Selanjutnya coba buka tab baru, buka https://novalagung.com, lalu jalankan script yang sama. + +![CORS novalagung to localhost](images/C_cors_preflight_request_2_cors_from_novalagung_to_localhost.png) + + +Failed to load http://localhost:9000/index: The 'Access-Control-Allow-Origin' header has a value 'https://www.google.com' that is not equal to the supplied origin. Origin 'https://novalagung.com' is therefore not allowed access.+ +Dari screenshot dan error log di atas, bisa dilihat bahwa request gagal. Hal ini dikarenakan origin https://novalagung.com tidak diijinkan untuk mengakses http://localhost:9000. + +#### ◉ CORS Multiple Origin + +Sekarang coba tambahkan situs https://novalagung.com ke CORS header. + +```go +http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", + "https://www.google.com, https://novalagung.com") + + // ... +``` + +Jalankan ulang aplikasi, lalu dispatch lagi AJAX dari situs tersebut. + +![CORS multiple domain](images/C_cors_preflight_request_3_cors_multiple_domain.png) + +Masih tetap error, tapi berbeda dengan error sebelumnya. + +
Failed to load http://localhost:9000/index: The 'Access-Control-Allow-Origin' header contains multiple values 'https://www.google.com, https://novalagung.com', but only one is allowed. Origin 'https://novalagung.com' is therefore not allowed access.+ +Sebenarnya sudah kita singgung juga di atas, bahwa di spesifikasi adalah diperbolehkan isi header `Access-Control-Allow-Origin` lebih dari satu website. Namun, kebanyakan browser tidak mendukung bagian ini. Oleh karena itu error di atas muncul. Konfigurasi ini termasuk tidak valid, hasilnya kedua website tersebut tidak punya ijin masuk. + +#### ◉ Allow All + +Gunakan tanda asteriks (`*`) sebagai nilai ketiga CORS header untuk memberi ijin ke semua. + +```go +// semua origin mendapat ijin akses +w.Header().Set("Access-Control-Allow-Origin", "*") + +// semua method diperbolehkan masuk +w.Header().Set("Access-Control-Allow-Methods", "*") + +// semua header diperbolehkan untuk disisipkan +w.Header().Set("Access-Control-Allow-Headers", "*") +``` + +## C.14.4. Preflight Request + +#### ◉ Teori + +Dalam konteks CORS, request dikategorikan menjadi 2 yaitu, **Simple Request** dan **Preflighted Request**. Beberapa contoh request yang sudah kita pelajari di atas termasuk simple request. + +Sedangkan mengenai preflighted request sendiri, mungkin pembaca secara tidak langsung juga pernah menerapkannya, terutama ketika bekerja di bagian front-end yang mengonsumsi data dari RESTful API yang server nya terpisah antara layer front end dan back end. + +Ketika melakukan cross origin request dengan payload adalah JSON, atau request jenis lainnya, biasanya di developer tools -> network log muncul 2 kali request, request pertama method-nya `OPTIONS` dan request ke-2 adalah actual request. + +Request ber-method `OPTIONS` tersebut disebut dengan **Preflight Request**. Request ini akan otomatis muncul ketika http request yang kita dispatch memenuhi kriteria preflighted request. + +Tujuan dari preflight request adalah untuk mengecek apakah destinasi url mendukung CORS. Tiga buah informasi dikirimkan `Access-Control-Request-Method`, `Access-Control-Request-Headers`, dan `Origin`, dengan method adalah `OPTIONS`. + +Berikut merupakan kriteria preflighted request. + + - Method yang digunakan adalah salah satu dari method berikut: + - `PUT` + - `DELETE` + - `CONNECT` + - `OPTIONS` + - `TRACE` + - `PATCH` + - Terdapat header SELAIN yang otomatis di-set dalam http request. Contoh header untuk kriteria ini adalah `Authorization`, `X-CSRF-Token`, atau lainnya. + - Isi header `Content-Type` adalah SELAIN satu dari 3 berikut. + - application/x-www-form-urlencoded + - multipart/form-data + - text/plain + - Ada event yang ter-registrasi dalam objek `XMLHttpRequestUpload` yang digunakan dalam request. + - Menggunakan objek `ReadableStream` dalam request. + +> Lebih detailnya mengenai simple dan preflighted request silakan baca https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS. + +Pada kode yang kita tulis, terdapat pengecekan method `OPTIONS`. Pengecekan ini digunakan untuk mencegah eksekusi statement selanjutnya. Hal ini dikarenakan preflight request tidak membutuhkan kembalian data, tugas si dia hanya mengecek apakah cross origin request didukung atau tidak. Jadi pada handler, ketika method nya adalah `OPTIONS`, langsung saja intercept proses utamanya. + +> Header `Access-Control-Max-Age` diisi dengan data waktu, digunakan untuk menentukan sebearapa lama informasi preflight request di-cache. Jika diisi dengan `-1` maka cache di-non-aktifkan. + +```go +http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) { + // ... + + if r.Method == "OPTIONS" { + w.Write([]byte("allowed")) + return + } + + // ... +}) +``` + +#### ◉ Praktek + +Langsung saja buka google.com lalu lakukan AJAX request yang memenuhi alah satu kriteria preflighted request, misalnya, gunakan header `Content-Type: application/json`. + +![Preflight Request Preview](images/C_cors_preflight_request_4_preflight_allowed.png) + +Bisa dilihat pada screenshot, dua request muncul, yang pertama adalah preflight yang kedua adalah actual request. + +## C.14.5. CORS Handling Menggunakan Golang CORS Library dan Echo + +Pada bagian ini kita akan mengkombinasikan library CORS golang buatan Olivier Poitrey, dan Echo, untuk membuat back end yang mendukung cross origin request. + +Pertama `go get` dulu library-nya. + +```bash +go get https://github.com/rs/cors +``` + +Buat file baru, import library yang diperlukan lalu buat fungsi main. + +```go +package main + +import ( + "github.com/labstack/echo" + "github.com/rs/cors" + "net/http" +) + +func main() { + e := echo.New() + + // ... + + e.GET("/index", func(c echo.Context) error { + return c.String(http.StatusOK, "hello") + }) + + e.Logger.Fatal(e.Start(":9000")) +} +``` + +Siapkan objek `corsMiddleware`, cetak dari fungsi `cors.New()` . Pada parameter konfigurasi, isi spesifikasi CORS sesuai kebutuhan. + +Gaya konfigurasi library ini menarik, mudah sekali untuk dipahami. + +```go +corsMiddleware := cors.New(cors.Options{ + AllowedOrigins: []string{"https://novalagung.com", "https://www.google.com"}, + AllowedMethods: []string{"OPTIONS", "GET", "POST", "PUT"}, + AllowedHeaders: []string{"Content-Type", "X-CSRF-Token"}, + Debug: true, +}) +e.Use(echo.WrapMiddleware(corsMiddleware.Handler)) +``` + +Pada kode di atas, kita meng-allow dua buah origin. Sebelumnya sudah kita bahas bahwa kebanyakan browser tidak mendukung ini. Dengan menggunakan CORS library, hal itu bisa teratasi. + +> Sebenarnya mekanisme yang diterapkan oleh CORS library adalah meng-allow semua origin, lalu kemudian mem-filter sesuai dengan spesifikasi yang kita buat, lalu memodifikasi response header `Access-Control-Allow-Origin`-nya. + +Jalankan aplikasi, coba test dari dua domain, https://novalagung.com dan https://www.google.com. + +![CORS multi domain](images/C_cors_preflight_request_5_multi_domain.png) + +Berikut adalah list konfigurasi yang bisa dimanfaatkan dari library ini. + +| Key | Description | +| ------------------ | ---------------------------------------------------------------------------------------------------- | +| AllowedOrigins | list origin/domain yang diperbolehkan mengakses, gunakan `*` untuk allow all | +| AllowOriginFunc | callback untuk validasi origin. cocok digunakan untuk menge-set CORS header origin dengan ijin rumit | +| AllowedMethods | list HTTP method yang diperbolehkan untuk pengaksesan | +| AllowedHeaders | list header yang diperbolehkan untuk pengaksesan | +| ExposedHeaders | menentukan header mana saja yang di-expose ke consumer | +| AllowCredentials | enable/disable credentials | +| MaxAge | durasi cache preflight request | +| OptionsPassthrough | digunakan untuk menginstruksikan handler selanjutnya untuk memproses OPTIONS method | +| Debug | aktifkan properti ini pada stage development, agar banyak informasi log tambahan bisa muncul | + +--- + + - [CORS](https://github.com/rs/cors), by Olivier Poitrey, MIT license + - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license + +--- + + + +--- + + diff --git a/en/content-en/C-csrf.md b/en/content-en/C-csrf.md new file mode 100644 index 000000000..94fb8ab7b --- /dev/null +++ b/en/content-en/C-csrf.md @@ -0,0 +1,229 @@ +# C.15. CSRF + +Pada chapter ini kita akan belajar tentang serangan Cross-Site Request Forgery (CSRF) dan cara mengantisipasinya. + +## C.15.1. Teori + +Cross-Site Request Forgery atau CSRF adalah salah satu teknik hacking yang dilakukan dengan cara mengeksekusi perintah yang seharusnya tidak diizinkan, tetapi output yang dihasilkan sesuai dengan yang seharusnya. Contoh serangan jenis ini: mencoba untuk login lewat media selain web browser, seperti menggunakan CURL, menembak langsung endpoint login. Masih banyak contoh lainnya yang lebih ekstrim. + +Ada beberapa cara untuk mencegah serangan ini, salah satunya adalah dengan memanfaatkan csrf token. Di setiap halaman yang ada form nya, csrf token di-generate. Pada saat submit form, csrf disisipkan di request, lalu di sisi back end dilakukan pengecekan apakah csrf yang dikirim valid atau tidak. + +Csrf token sendiri merupakan sebuah random string yang di-generate setiap kali halaman form muncul. Biasanya di tiap POST request, token tersebut disisipkan sebagai header, atau form data, atau query string. + +Lebih detailnya silakan merujuk ke https://en.wikipedia.org/wiki/Cross-site_request_forgery. + +## C.15.2. Praktek: Back End + +Di golang, pencegahan CSRF bisa dilakukan dengan membuat middleware untuk pengecekan setiap request POST yang masuk. Cukup mudah sebenarnya, namun agar lebih mudah lagi kita akan gunakan salah satu middleware milik echo framework untuk belajar. + +Di setiap halaman, jika di dalam html nya terdapat form, maka harus disisipkan token csrf. Token tersebut di-generate oleh middleware. + +Di tiap POST request hasil dari form submit, token tersebut harus ikut dikirimkan. Proses validasi token sendiri di-handle oleh middleware. + +Mari kita praktekkan, siapkan project baru. Buat file `main.go`, isi dengan kode berikut. + +```go +package main + +import ( + "fmt" + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" + "html/template" + "net/http" +) + +type M map[string]interface{} + +func main() { + tmpl := template.Must(template.ParseGlob("./*.html")) + + e := echo.New() + + // ... + + e.Logger.Fatal(e.Start(":9000")) +} +``` + +Nantinya akan ada endpoint `/index`, isinya menampilkan html form. Objek `tmpl` kita gunakan untuk rendering form tersebut. API echo renderer tidak digunakan dalam chapter ini. + +Siapkan routing untuk `/index`, dan registrasikan middleware CSRF. + +```go +const CSRFTokenHeader = "X-CSRF-Token" +const CSRFKey = "csrf" + +e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{ + TokenLookup: "header:" + CSRFTokenHeader, + ContextKey: CSRFKey, +})) + +e.GET("/index", func(c echo.Context) error { + data := make(M) + data[CSRFKey] = c.Get(CSRFKey) + return tmpl.Execute(c.Response(), data) +}) +``` + +Objek middleware CSRF dibuat lewat statement `middleware.CSRF()`, konfigurasi default digunakan. Atau bisa juga dibuat dengan disertakan konfigurasi custom, lewat `middleware.CSRFWithConfig()` seperti pada kode di atas. + +Property `ContextKey` digunakan untuk mengakses token csrf yang tersimpan di `echo.Context`, pembuatan token sendiri terjadi pada saat ada http request GET masuk. + +Property tersebut kita isi dengan konstanta `CSRFKey`, maka dalam pengambilan token cukup panggil `c.Get(CSRFKey)`. Token kemudian disisipkan sebagai data pada saat rendering `view.html`. + +Property `TokenLookup` adalah acuan di bagian mana informasi csrf disisipkan dalam objek request, apakah dari header, query string, atau form data. Ini penting karena dibutuhkan oleh middleware yang bersangkutan untuk memvalidasi token tersebut. Bisa dilihat, kita memilih `header:X-CSRF-Token`, artinya csrf token dalam request akan disisipkan dalam header dengan key adalah `X-CSRF-Token`. + +> Isi value `TokenLookup` dengan `"form: +
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c+ +Skema JWT: + +![JWT scheme](images/C_golang_jwt_1_jwt_scheme.png) + + - **Header**, isinya adalah jenis algoritma yang digunakan untuk generate signature. + - **Payload**, isinya adalah data penting untuk keperluan otentikasi, seperti *grant*, *group*, kapan login terjadi, dan atau lainnya. Data ini dalam konteks JWT biasa disebut dengan **CLAIMS**. + - **Signature**, isinya adalah hasil dari enkripsi data menggunakan algoritma kriptografi. Data yang dimaksud adalah gabungan dari (encoded) header, (encoded) payload, dan secret key. + +Umumnya pada aplikasi yang menerapkan teknik otorisasi menggunakan token, token di-generate di back end secara acak (menggunakan algoritma tertentu) lalu disimpan di database bersamaan dengan data user. Token tersebut bisa jadi tidak ada isinya, hanya random string, atau mungkin saja ada isinya. + +Nantinya di setiap http call, token yang disertakan pada request header dicocokan dengan token yang ada di database, dilanjutkan dengan pengecekan grant/group/sejenisnya, untuk menentukan apakah request tersebut adalah verified request yang memang berhak mengakses endpoint. + +Pada aplikasi yang menerapkan JWT, yang terjadi sedikit berbeda. Token adalah hasil dari proses kombinasi, encoding, dan enkripsi terhadap beberapa informasi. Nantinya pada sebuah http call, pengecekan token tidak dilakukan dengan membandingkan token yang ada di request vs token yang tersimpan di database, karena memang token pada JWT tidak disimpan di database. Pengecekan token dilakukan dengan cara mengecek hasil decode dan decrypt token yang ditautkan dalam request. + +> Ada kalanya token JWT perlu juga untuk disimpan di back-end, misalnya untuk keperluan auto-invalidate session pada multiple login, atau kasus lainnya. + +Mungkin sekilas terlihat mengerikan, terlihat sangat gampang sekali untuk di-retas, buktinya adalah data otorisasi bisa dengan mudah diambil dari token JWT. Dan penulis sampaikan, bahwa ini adalah presepsi yang salah. + +Informasi yang ada dalam token, selain di-encode, juga di-enkripsi. Dalam enkripsi diperlukan private key atau secret key, dan hanya pengembang yang tau. Jadi pastikan simpan baik-baik key tersebut. + +Selama peretas tidak tau secret key yang digunakan, hasil decoding dan dekripsi data **PASTI TIDAK VALID**. + +## C.32.2. Persiapan Praktek + +Kita akan buat sebuah aplikasi RESTful web service sederhana, isinya dua buah endpoint `/index` dan `/login`. Berikut merupakan spesifikasi aplikasinya: + + - Pengaksesan `/index` memerlukan token JWT. + - Token didapat dari proses otentikasi ke endpoint `/login` dengan menyisipkan username dan password. + - Pada aplikasi yang sudah kita buat, sudah ada data list user yang tersimpan di database (sebenarnya bukan di-database, tapi di file json). + +Ok, sekarang siapkan folder project baru. + +```bash +mkdir chapter-c32 +cd chapter-c32 +go mod init chapter-c32 + +# then prepare underneath structures + +tree . +. +├── go.mod +├── main.go +├── middleware.go +└── users.json +``` + +#### ◉ File `middleware.go` + +Lanjut isi file `middleware.go` dengan kode middleware yang sudah biasa kita gunakan. + +```go +package main + +import "net/http" + +type CustomMux struct { + http.ServeMux + middlewares []func(next http.Handler) http.Handler +} + +func (c *CustomMux) RegisterMiddleware(next func(next http.Handler) http.Handler) { + c.middlewares = append(c.middlewares, next) +} + +func (c *CustomMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var current http.Handler = &c.ServeMux + + for _, next := range c.middlewares { + current = next(current) + } + + current.ServeHTTP(w, r) +} +``` + +#### ◉ File `users.json` + +Juga isi file `users.json` yang merupakan database aplikasi. Silakan tambahkan data JSON berikut. + +```json +[{ + "username": "noval", + "password": "kaliparejaya123", + "email": "terpalmurah@gmail.com", + "group": "admin" +}, { + "username": "farhan", + "password": "masokpakeko", + "email": "cikrakbaja@gmail.com", + "group": "publisher" +}] +``` + +#### ◉ File `main.go` + +Sekarang kita fokus ke file `main.go`. Import packages yang diperlukan. Salah satu dari packages tersebut adalah [golang-jwt/jwt](https://github.com/golang-jwt/jwt), yang digunakan untuk keperluan JWT. + +```bash +go get -u github.com/golang-jwt/jwt/v4@v4.2.0 +go get -u github.com/novalagung/gubrak/v2 +``` + +```go +package main + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + + jwt "github.com/golang-jwt/jwt/v4" + gubrak "github.com/novalagung/gubrak/v2" +) +``` + +Masih di file yang sama, siapkan 4 buah konstanta yaitu: nama aplikasi, durasi login, metode enkripsi token, dan secret key. + +```go +type M map[string]interface{} + +var APPLICATION_NAME = "My Simple JWT App" +var LOGIN_EXPIRATION_DURATION = time.Duration(1) * time.Hour +var JWT_SIGNING_METHOD = jwt.SigningMethodHS256 +var JWT_SIGNATURE_KEY = []byte("the secret of kalimdor") +``` + +Kemudian buat fungsi `main()`, siapkan didalmnya sebuah `mux` baru, dan daftarkan middleware `MiddlewareJWTAuthorization` dan dua buah rute `/index` dan `/login`. + +```go +func main() { + mux := new(CustomMux) + mux.RegisterMiddleware(MiddlewareJWTAuthorization) + + mux.HandleFunc("/login", HandlerLogin) + mux.HandleFunc("/index", HandlerIndex) + + server := new(http.Server) + server.Handler = mux + server.Addr = ":8080" + + fmt.Println("Starting server at", server.Addr) + server.ListenAndServe() +} +``` + +Middleware `MiddlewareJWTAuthorization` nantinya akan kita buat, tugasnya memvalidasi setiap request yang masuk, dengan cara mengecek token JWT yang disertakan. Middleware ini hanya berguna pada request ke selain endpoint `/login`, karena pada endpoint tersebut proses otentikasi terjadi. + +## C.32.3. Otentikasi & Generate Token + +Siapkan handler `HandlerLogin`. Tulis kode berikut. + +```go +func HandlerLogin(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.Error(w, "Unsupported http method", http.StatusBadRequest) + return + } + + username, password, ok := r.BasicAuth() + if !ok { + http.Error(w, "Invalid username or password", http.StatusBadRequest) + return + } + + // ... +} +``` + +Handler ini bertugas untuk meng-otentikasi client/consumer. Data username dan password dikirimkan ke endpoint dalam bentuk [B.18. HTTP Basic Auth](/B-http-basic-auth.html). Data tersebut kemudian disisipkan dalam pemanggilan fungsi otentikasi `authenticateUser()`, yang nantinya juga akan kita buat. + +```go +ok, userInfo := authenticateUser(username, password) +if !ok { + http.Error(w, "Invalid username or password", http.StatusBadRequest) + return +} +``` + +Fungsi `authenticateUser()` memiliki dua nilai balik yang ditampung oleh variabel berikut: + + - Variabel `ok`, penanda sukses tidaknya otentikasi. + - Variabel `userInfo`, isinya informasi user yang sedang login, datanya didapat dari `data.json` (tetapi tanpa password). + +Selanjutnya kita akan buat objek claims. Objek ini harus memenuhi persyaratan interface `jwt.Claims`. Objek claims bisa dibuat dari tipe `map` dengan cara membungkusnya dalam fungsi `jwt.MapClaims()`; atau dengan meng-embed interface `jwt.StandardClaims` pada struct baru, dan cara inilah yang akan kita pakai. + +Seperti yang sudah kita bahas di awal, bahwa claims isinya adalah data-data untuk keperluan otentikasi. Dalam prakteknya, claims merupakan sebuah objek yang memilik banyak property atau fields. Nah, objek claims **harus** memiliki fields yang termasuk di dalam list [JWT Standard Fields](https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields). Dengan meng-embed interface `jwt.StandardClaims`, maka fields pada struct dianggap sudah terwakili. + +Pada aplikasi yang sedang kita kembangkan, claims selain menampung standard fields, juga menampung beberapa informasi lain, oleh karena itu kita perlu buat `struct` baru yang meng-embed `jwt.StandardClaims`. + +```go +type MyClaims struct { + jwt.StandardClaims + Username string `json:"Username"` + Email string `json:"Email"` + Group string `json:"Group"` +} +``` + +Ok, struct `MyClaims` sudah siap, sekarang buat objek baru dari struct tersebut. + +```go +claims := MyClaims{ + StandardClaims: jwt.StandardClaims{ + Issuer: APPLICATION_NAME, + ExpiresAt: time.Now().Add(LOGIN_EXPIRATION_DURATION).Unix(), + }, + Username: userInfo["username"].(string), + Email: userInfo["email"].(string), + Group: userInfo["group"].(string), +} +``` + +Ada beberapa standard claims, pada contoh di atas hanya dua yang di-isi nilainya, `Issuer` dan `ExpiresAt`, selebihnya kita kosongi. Lalu 3 fields tambahan yang kita buat (username, email, dan group) di-isi menggunakan data yang didapat dari `userInfo`. + + - `Issuer` (code `iss`), adalah penerbit JWT, dalam konteks ini adalah `APPLICATION_NAME`. + - `ExpiresAt` (code `exp`), adalah kapan token JWT dianggap expired. + +Ok, objek claims sudah siap, sekarang buat token baru. Pembuatannya dilakukan menggunakan fungsi `jwt.NewWithClaims()` yang menghasilkan nilai balik bertipe `jwt.Token`. Parameter pertama adalah metode enkripsi yang digunakan, yaitu `JWT_SIGNING_METHOD`, dan parameter kedua adalah `claims`. + +```go +token := jwt.NewWithClaims( + JWT_SIGNING_METHOD, + claims, +) +``` + +Kemudian tanda-tangani token tersebut menggunakan secret key yang sudah didefinisikan di `JWT_SIGNATURE_KEY`, caranya dengan memanggil method `SignedString()` milik objek `jwt.Token`. Pemanggilan method ini mengembalikan data token string yang kemudian dijadikan nilai balik handler. Token string inilah yang dibutuhkan client/consumer untuk bisa mengakses endpoints yang ada. + +```go +signedToken, err := token.SignedString(JWT_SIGNATURE_KEY) +if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return +} + +tokenString, _ := json.Marshal(M{ "token": signedToken }) +w.Write([]byte(tokenString)) +``` + +Bagian otentikasi dan generate token sebenarnya cukup sampai di sini. Tapi sebenarnya ada yang kurang, yaitu fungsi `authenticateUser()`. Silakan buat fungsi tersebut. + +```go +func authenticateUser(username, password string) (bool, M) { + basePath, _ := os.Getwd() + dbPath := filepath.Join(basePath, "users.json") + buf, _ := os.ReadFile(dbPath) + + data := make([]M, 0) + err := json.Unmarshal(buf, &data) + if err != nil { + return false, nil + } + + res := gubrak.From(data).Find(func(each M) bool { + return each["username"] == username && each["password"] == password + }).Result() + + if res != nil { + resM := res.(M) + delete(resM, "password") + return true, resM + } + + return false, nil +} +``` + +Isi fungsi `authenticateUser()` cukup jelas, sesuai namanya, yaitu melakukan pencocokan username dan password dengan data yang ada di dalam file `users.json`. + +## C.32.4. JWT Authorization Middleware + +Sekarang kita perlu menyiapkan `MiddlewareJWTAuthorization`, yang tugasnya adalah mengecek setiap request yang masuk ke endpoint selain `/login`, apakah ada akses token yang dibawa atau tidak. Dan jika ada, akan diverifikasi valid tidaknya token tersebut. + +```go +func MiddlewareJWTAuthorization(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + if r.URL.Path == "/login" { + next.ServeHTTP(w, r) + return + } + + authorizationHeader := r.Header.Get("Authorization") + if !strings.Contains(authorizationHeader, "Bearer") { + http.Error(w, "Invalid token", http.StatusBadRequest) + return + } + + tokenString := strings.Replace(authorizationHeader, "Bearer ", "", -1) + + // ... + }) +} +``` + +Kita gunakan skema header `Authorization: Bearer
Lorem Ipsum Dolor Sit Amet Gedhang Goreng
") +doc.Find("p").AppendHtml(" Tournesol") +doc.Find("h1").SetAttr("class", "header") +doc.Find("footer").First().Remove() +doc.Find("body > *:nth-child(4)").Remove() +``` + +Berikut penjelasan beberapa API yang digunakan pada kode di atas. + + - Method `.AfterHtml()`, digunakan untuk menambahkan elemen baru, posisinya ditempatkan setelah elemen objek pemanggilan method. + - Method `.AppendHtml()`, digunakan untuk menambahkan child elemen baru. + - Method `.SetAttr()`, digunakan untuk menambahkan/mengubah atribut elemen. + - Method `.First()`, digunakan untuk mengakses elemen pertama. Pada kode di atas `doc.Find("footer")` mengembalikan semua footer yang ada, sesuai selector. Pengaksesan method `.First()` menjadikan hanya elemen footer pertama yang diambil. + - Method `.Remove()`, digunakan untuk menghapus current element. + +Ambil bentuk string html dari objek `doc` yang sudah banyak dimodifikasi. Jangan gunakan `doc.Html()` karena yang dikembalikan adalah [inner html](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML). Gunakan `goquery.OuterHtml(doc.Selection)` agar yang dikembalikan [outer html](https://developer.mozilla.org/en-US/docs/Web/API/Element/outerHTML)-nya. + +```go +modifiedHTML, err := goquery.OuterHtml(doc.Selection) +if err != nil { + log.Fatal(err) +} + +log.Println(gohtml.Format(modifiedHTML)) +``` + +Pada kode di atas kita menggunakan satu lagi library, [gohtml](https://github.com/yosssi/gohtml). Fungsi `.Format()` dalam library tersebut digunakan untuk code beautification. + +Jalankan aplikasi, lihat hasilnya. + +![Beautified HTML](images/C_scraping_parsing_html_4_beautified_html.png) + +--- + + - [goquery](https://github.com/PuerkitoBio/goquery), by Martin Angers, BSD-3-Clause license + - [gohtml](https://github.com/yosssi/gohtml), by Keiji Yoshida, MIT license + +--- + + + +--- + + diff --git a/en/content-en/C-secure-insecure-client-http-request.md b/en/content-en/C-secure-insecure-client-http-request.md new file mode 100644 index 000000000..016163984 --- /dev/null +++ b/en/content-en/C-secure-insecure-client-http-request.md @@ -0,0 +1,194 @@ +# C.27. Secure & Insecure Client HTTP Request + +Pada chapter ini topik yang dibahas adalah cara melakukan http request ke SSL/TLS-enabled web server, menggunakan dua teknik: + + - Insecure request + - Secure request menggunakan file certificate + +## C.27.1. Handshake + +Sebelum masuk ke inti pembahasan, kita perlu mempelajari terlebih dahulu tentang pembeda antara secure request dan http request biasa. + +Dalam secure request, sebelum data benar-benar diterima oleh server, terjadi proses negosiasi antara client (yg men-dispatch request) dan server (tujuan request), proses ini biasa disebut dengan handshake. + +Proses negosiasi tersebut dipecah menjadi 5 fase. + + 1. Fase **Client Hello**. Pada fase ini handshake dimulai dengan client mengirimkan pesan yang kita sebut dengan **client hello** ke se server. Pesan tersebut berisikan semua informasi milik client yang diperlukan oleh server untuk bisa terhubung dengan client via SSL. Informasi yang dimaksud di antaranya adalah versi SSL/TLS dan konfigurasi cipher. Cipher suite sendiri adalah seperangkat algoritma, digunakan untuk membantu pengamanan koneksi yang menerapkan TLS/SSL. + + 2. Fase **Server Hello**. Setelah diterima, server merespon dengan pesan yang mirip, yaitu **server hello**, isinya juga informasi yang kurang lebih sejenis. Informasi ini diperlukan oleh client untuk bisa terhubung balik dengan server. + + 3. Fase **Otentikasi dan Pre-Master Secret**. Setelah kontak antara client dan server terjadi, server mengenalkan dirinya ke client lewat file certificate. Anggap saja certificate tersebut sebagai KTP (Kartu Tanda Penduduk). Client selanjutnya melakukan pengecekan, apakah KTP tersebut valid dan dipercaya, atau tidak. Jika memang terpercaya, client selanjutnya membuat data yang disebut dengan **pre-master secret**, meng-enkripsi-nya menggunakan public key, lalu mengirimnya ke server sebagai response. + + 4. Fase **Decryption dan Master Secret**. Data encrypted pre-master secret yang dikirim oleh client diterima oleh server. Data tersebut kemudian di-decrypt menggunakan private key. Selanjutnya server dan client melakukan beberapa hal untuk men-generate **master secret** lewat cipher yang sudah disepakati. + + 5. Fase **Encryption with Session Key**. Server dan client melakukan pertukaran pesan untuk menginfokan bahwa data yang dikirim dalam request tersebut dan request-request selanjutnya akan di-enkripsi. + +Sekarang kita tau, bahwa agar komunikasi antara client dan server bisa terjalin, pada sisi client harus ada file certificate, dan pada sisi server harus private key & certificate. + +OK, saya rasa bagian teori sudah cukup, mari kita lanjut ke bagian praktek. + +## C.27.2. Persiapan + +Salin project pada chapter sebelumnya, [C.26. Advanced Client HTTP Request](/C-client-http-request-advanced.html) sebagai folder project baru. + +## C.27.3. Konfigurasi SSL/TLS pada Web Server + +Pada chapter [A.55. Simple Client HTTP Request](/A-client-http-request-simple.html) kita telah belajar implementasi client http request, penerapannya dengan 2 buah aplikasi terpisah, satu aplikasi web server dan satu lagi adalah aplikasi consumer. + +Kita perlu menambahkan sedikit modifikasi pada aplikasi web server (yang sudah di salin), mengaktifkan SSL/TLS-nya dengan cara mengubah bagian `.ListenAndServe()` menjadi `.ListenAndServeTLS()`, dengan disisipkan dua parameter berisi path certificate dan private key. + +```go +err := server.ListenAndServeTLS("server.crt", "server.key") +``` + +Silakan generate certificate dan private key baru, caranya sama seperti pada chapter [C.24. HTTPS/TLS Web Server](/C-https-tls.html). + +> Konfigurasi SSL/TLS lewat `server.ListenAndServeTLS("server.crt", "server.key")` merupakan cara yang paling mudah dengan konfigurasi adalah paling minimal. + +## C.27.4. Insecure Request + +Dari yang sudah dijelaskan di atas, agar komunikasi antara client dan server bisa ter-enkripsi, di sisi client atau consumer harus ada yang namanya file certificate. + +Jika client tidak menyertakan certificate dalam request-nya, maka pasti terjadi error (pada saat handshake). Contohnya bisa dilihat pada screenshot berikut. + +![SSL request error](images/C_secure_insecure_client_http_request_1_http_request_to_ssl_enabled_web_server.png) + +Akan tetapi, jika memang client tidak memilik certificate dan komunikasi ingin tetap dilakukan, masih bisa (dengan catatan server meng-allow kapabilitas ini), caranya yaitu menggunakan teknik *insecure request*. + +> Dalam insecure request, komunikasi terjalin tanpa ada proses enkripsi data. + +Cara membuat insecure request sangat mudah, cukup aktifkan atribut insecure pada request. Misal menggunakan **curl**, maka cukup tambahkan flag `--insecure` pada command. + +```bash +curl -X POST https://localhost/data \ + --insecure \ + -H 'Content-Type: application/json' \ + -d '{"Name": "Noval Agung"}' +``` + +Penerapan inscure request dalam golang juga tidak terlalu sulit. Pada object `http.Client`, isi property `.Transport` dengan objek baru buatan struct `http.Transport` yang di dalamnya berisi konfigurasi insecure request. + +```go +client := new(http.Client) +client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, +} +``` + +Ubah kode pada aplikasi client (yang sudah disalin) seperti di atas. Jangan lupa juga untuk mengganti protokol base url destinasi, dari `http` ke `https`. + +```go +baseURL := "https://localhost:9000" +``` + +Jalankan ulang aplikasi server yang sudah ssl-enabled dan aplikasi client yang sudah dikonfigurasi untuk insecure request, lalu test hasilnya. + +![Insecure Request](images/C_secure_insecure_client_http_request_2_insecure_client_request.png) + +## C.27.5. Secure Request + +Secure request adalah bentuk request yang datanya ter-enkripsi, bisa dibilang kebalikan dari insecure request. Request jenis ini pada sisi client atau consumer membutuhkan konfigurasi di mana file certificate diperlukan. + +Secure request bisa dilakukan dengan mudah di golang. Mari langsung saja kita praktekan. Pertama, pada file consumer, tambahkan package `crypto/x509`. + +```go +import ( + // ... + "crypto/x509" +) +``` + +> X.509 adalah standar format public key certificates. + +Lalu buat objek baru bertipe `x509.CertPool` lewat `x509.NewCertPool()`. Objek ini nantinya menampung list certificate yang digunakan. + +Buat objek menggunakan struct `tls.Config`, dengan isi property `RootCAs` adalah objek list certificate yang sudah dibuat. + +Isi `client.Transport` dengan konfigurasi secure request. Hapus saja konfigurasi insecure request sebelumnya. + +Kurang lebih kode-nya seperti berikut. + +```go +certFile, err := os.ReadFile("server.crt") +if err != nil { + return nil, err +} + +caCertPool := x509.NewCertPool() +caCertPool.AppendCertsFromPEM(certFile) + +tlsConfig := &tls.Config{ RootCAs: caCertPool } +tlsConfig.BuildNameToCertificate() + +client := new(http.Client) +client.Transport = &http.Transport{ + TLSClientConfig: tlsConfig, +} +``` + +Bisa dilihat pada kode di atas, file `server.crt` dibaca isinya, lalu dimasukan ke `caCertPool`. Objek `caCertPool` ini bisa menampung banyak certificate, jika memang dibutuhkan banyak. + +OK, silakan langsung run aplikasi untuk testing. + +![Secure Request](images/C_secure_insecure_client_http_request_3_secure_client_request.png) + +## C.27.6. Konfigurasi SSL/TLS Lanjutan + +Di atas kita sudah belajar cara setting SSL/TLS pada web server, dengan konfigurasi minimal menggunakan `server.ListenAndServeTLS("server.crt", "server.key")`. + +Konfigurasi yang lebih complex bisa kita lakukan menggunakan `tls.Config`. Buat objek menggunakan struct tersebut lalu manfaatkan property struct-nya untuk menciptakan konfigurasi yang sesuai dengan kebutuhan. Contoh kurang lebih seperti kode di bawah ini. + +```go +certPair1, err := tls.LoadX509KeyPair("server.crt", "server.key") +if err != nil { + log.Fatalln("Failed to start web server", err) +} + +tlsConfig := new(tls.Config) +tlsConfig.NextProtos = []string{"http/1.1"} +tlsConfig.MinVersion = tls.VersionTLS12 +tlsConfig.PreferServerCipherSuites = true + +tlsConfig.Certificates = []tls.Certificate{ + certPair1, /** add other certificates here **/ +} +tlsConfig.BuildNameToCertificate() + +tlsConfig.ClientAuth = tls.VerifyClientCertIfGiven +tlsConfig.CurvePreferences = []tls.CurveID{ + tls.CurveP521, + tls.CurveP384, + tls.CurveP256, +} +tlsConfig.CipherSuites = []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, +} +``` + +Tampung saja objek cetakan `server.TLSConfig` di atas ke dalam `server.TLSConfig`. Jika file certificate dan private key sudah ditambahkan dalam `tlsConfig`, maka dalam pemanggilan `server.ListenAndServeTLS()` kosongkan saja parameter-nya. + +```go +server := new(http.Server) +server.Handler = mux +server.Addr = ":9000" +server.TLSConfig = tlsConfig + +err := server.ListenAndServeTLS("", "") +if err != nil { + log.Fatalln("Failed to start web server", err) +} +``` + +Tujuan mengapa penulis tambahkan sub chapter **Konfigurasi SSL/TLS Lanjutan** ini adalah agar pembaca tau bahwa konfigurasi SSL/TLS yang compleks bisa dilakukan dengan mudah dalam aplikasi web golang. Mengenai pembahasan tiap-tiap property silakan pelajari sendiri. + +--- + + + + +--- + + diff --git a/en/content-en/C-secure-middleware.md b/en/content-en/C-secure-middleware.md new file mode 100644 index 000000000..618576d1d --- /dev/null +++ b/en/content-en/C-secure-middleware.md @@ -0,0 +1,157 @@ +# C.16. Secure Middleware + +Pada chapter ini kita akan belajar menggunakan library [secure](https://github.com/unrolled/secure) untuk meningkatkan keamanan aplikasi web. + +## C.16.1. Keamanan Web Server + +Jika berbicara mengenai keamanan aplikasi web, sangat luas sebenarnya cakupannya, ada banyak hal yang perlu diperhatian dan disiapkan. Mungkin tiga di antaranya sudah kita pelajari sebelumnya, yaitu penerapan Secure Cookie, CORS, dan CSRF. + +Selain 3 topik tersebut masih terdapat banyak lagi. Beruntungnya ada library [secure](https://github.com/unrolled/secure). Sesuai tagline-nya, secure library digunakan untuk membantu mengatasi beberapa masalah keamanan aplikasi. + +Secure library merupakan middleware, penggunaannya sama seperti middleware pada umumnya. + +## C.15.2. Praktek + +Mari langsung kita praktekan. Buat folder project baru. Di file main tulis kode berikut. Sebuah aplikasi dibuat, isinya satu buah rute `/index` yang bisa diakses dari mana saja. + +```go +package main + +import ( + "net/http" + "github.com/labstack/echo" +) + +func main() { + e := echo.New() + + e.GET("/index", func(c echo.Context) error { + c.Response().Header().Set("Access-Control-Allow-Origin", "*") + + return c.String(http.StatusOK, "Hello") + }) + + e.Logger.Fatal(e.StartTLS(":9000", "server.crt", "server.key")) +} +``` + +Perlu diketahui, aplikasi di atas di-start dengan SSL/TLS enabled. Dua buah file dibutuhkan, yaitu file certificate `server.crt` dan file private key `server.key`. Silakan unduh kedua file tersebut dari source code di [GitHub, folder chapter-C.16-secure-middleware](https://github.com/novalagung/dasarpemrogramangolang-example/tree/master/chapter-C.16-secure-middleware). Pada chapter [C.24. HTTPS/TLS Web Server](/C-https-tls.html) nantinya akan kita pelajari lebih lanjut mengenai cara generate kedua file di atas hingga cara penggunannya. + +Kembali ke pembahasan, sekarang tambahkan secure middleware. Import package-nya, buat instance middleware, lalu registrasikan ke echo. + +```go +import ( + // ... + "github.com/unrolled/secure" +) + +func main() { + // ... + + secureMiddleware := secure.New(secure.Options{ + AllowedHosts: []string{"localhost:9000", "www.google.com"}, + FrameDeny: true, + CustomFrameOptionsValue: "SAMEORIGIN", + ContentTypeNosniff: true, + BrowserXssFilter: true, + }) + + e.Use(echo.WrapMiddleware(secureMiddleware.Handler)) + + // ... +} +``` + +Pembuatan objek secure middleware dilakukan menggunakan `secure.New()` dengan isi parameter adalah konfigurasi. Bisa dilihat ada 5 buah property konfigurasi di-set. Berikut merupakan penjelasan tiap-tiap property tersebut. + +#### ◉ Konfigurasi `AllowedHosts` + +```go +AllowedHosts: []string{"localhost:9000", "www.google.com"} +``` + +Host yang diperbolehkan mengakses web server ditentukan hanya 2, yaitu localhost:9000 yang merupakan web server itu sendiri, dan google.com. Silakan coba mengakses aplikasi kita ini menggunakan AJAX lewat google.com dan domainnya lainnya untuk mengetes apakah fungsionalitas nya berjalan. + +#### ◉ Konfigurasi `FrameDeny` + +```go +FrameDeny: true +``` + +Secara default sebuah aplikasi web adalah bisa di-load di dalam iframe yang berada host nya berbeda. Misalnya di salah satu laman web www.kalipare.com ada iframe yang atribut src nya berisi www.novalagung.com, hal seperti ini diperbolehkan. + +Perijinan apakah website boleh di-load lewat iframe atau tidak, dikontrol lewat header [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options). + +Di library secure, untuk men-disable ijin akses aplikasi dari dalam iframe, bisa dilakukan cukup dengan mengeset proerty `FrameDeny` dengan nilai `true`. + +Untuk mengetes, silakan buat aplikasi web terpisah yang mer-render sebuah view. Dalam view tersebut siapkan satu buah iframe yang mengarah ke `https://localhost:9000/index`. + +#### ◉ Konfigurasi `CustomFrameOptionsValue` + +```go +CustomFrameOptionsValue: "SAMEORIGIN" +``` + +Jika `FrameDeny` di-set sebagai `true`, maka semua host (termasuk aplikasi itu sendiri) tidak akan bisa me-load url lewat iframe. + +Dengan menambahkan satu buah property lagi yaitu `CustomFrameOptionsValue: "SAMEORIGIN"` maka ijin pengaksesan url lewat iframe menjadi eksklusif hanya untuk aplikasi sendiri. + +Untuk mengetes, buat rute baru yang me-render sebuah view. Dalam view tersebut siapkan satu buah iframe yang mengarah ke `/index`. + +#### ◉ Konfigurasi `ContentTypeNosniff` + +```go +ContentTypeNosniff: true +``` + +Property `ContentTypeNosniff: true` digunakan untuk disable MIME-sniffing yang dilakukan oleh browser IE. Lebih jelasnya silakan baca [X-Content-Type-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options). + +#### ◉ Konfigurasi `BrowserXssFilter` + +```go +BrowserXssFilter: true +``` + +Property di atas digunakan untuk mengaktifkan header [X-XSS-Protection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection), dengan isi header adalah `1; mode=block`. + +## C.15.3. Property Library Secure + +Selain 5 property yang kita telah pelajari di atas, masih ada banyak lagi konfigurasi yang bisa digunakan. + + - AllowedHosts + - HostsProxyHeaders + - SSLRedirect + - SSLTemporaryRedirect + - SSLHost + - SSLHostFunc + - SSLProxyHeaders + - STSSeconds + - STSIncludeSubdomains + - STSPreload + - ForceSTSHeader + - FrameDeny + - CustomFrameOptionsValue + - ContentTypeNosniff + - BrowserXssFilter + - CustomBrowserXssValue + - ContentSecurityPolicy + - PublicKey + - ReferrerPolicy + +Lebih mendetailnya silakan langsung cek halaman official library secure di https://github.com/unrolled/secure. + +--- + + - [Secure](https://github.com/unrolled/secure), by Cory Jacobsen, MIT license + - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license + +--- + + + + +--- + + diff --git a/en/content-en/C-securecookie.md b/en/content-en/C-securecookie.md new file mode 100644 index 000000000..5b1bad649 --- /dev/null +++ b/en/content-en/C-securecookie.md @@ -0,0 +1,162 @@ +# C.12. Secure Cookie (Gorilla Securecookie) + +Pada chapter [B.21. HTTP Cookie](/B-cookie.html), kita telah mempelajari tentang cookie dan implementasinya di golang. + +Cookie memiliki beberapa atribut, di antaranya adalah `secure`. Dengan mengaktifkan atribut ini, informasi cookie menjadi lebih aman karena di-enkripsi, namun kapabilitas ini hanya akan aktif pada kondisi aplikasi SSL/TLS enabled. + +> TL;DR; Jika atribut `secure` di-isi `true`, namun web server TIDAK menggunakan SSL/TLS, maka cookie disimpan seperti biasa tanpa di-enkripsi. + +Lalu bagaimana cara untuk membuat cookie aman pada aplikasi yang meng-enable SSL/TLS maupun yang tidak? caranya adalah dengan menambahkan step enkripsi data sebelum disimpan dalam cookie (dan men-decrypt data tersebut saat membaca). + +Gorilla toolkit menyediakan library bernama [securecookie](https://github.com/gorilla/securecookie), berguna untuk mempermudah enkripsi informasi cookie, dengan penerapan yang mudah. Pada chapter ini kita akan mempelajari penggunaannya. + +## C.12.1. Create & Read Secure Cookie + +Penggunaan securecookie cukup mudah, buat objek secure cookie lewat `securecookie.New()` lalu gunakan objek tersebut untuk operasi encode-decode data cookie. Pemanggilan fungsi `.New()` memerlukan 2 buah argument. + + - Hash key, diperlukan untuk otentikasi data cookie menggunakan algoritma kriptografi HMAC. + - Block key, adalah opsional, diperlukan untuk enkripsi data cookie. Default algoritma enkripsi yang digunakan adalah AES. + +OK, langsung saja kita praktekan. Buat folder project seperti biasa lalu isi `main.go` dengan kode berikut. + +```go +package main + +import ( + "github.com/gorilla/securecookie" + "github.com/labstack/echo" + gubrak "github.com/novalagung/gubrak/v2" + "net/http" + "time" +) + +type M map[string]interface{} + +var sc = securecookie.New([]byte("very-secret"), []byte("a-lot-secret-yay")) +``` + +Variabel `sc` adalah objek secure cookie. Objek ini kita gunakan untuk encode data yang akan disimpan dalam cookie, dan juga untuk decode data. + +Buat fungsi `setCookie()`, bertugas untuk mempermudah pembuatan dan penyimpanan cookie. + +```go +func setCookie(c echo.Context, name string, data M) error { + encoded, err := sc.Encode(name, data) + if err != nil { + return err + } + + cookie := &http.Cookie{ + Name: name, + Value: encoded, + Path: "/", + Secure: false, + HttpOnly: true, + Expires: time.Now().Add(1 * time.Hour), + } + http.SetCookie(c.Response(), cookie) + + return nil +} +``` + +Method `sc.Encode()` digunakan untuk encoding data dengan identifier adalah isi variabel `name`. Variabel `encoded` menampung data setelah di-encode, lalu variabel ini dimasukan ke dalam objek cookie. + +Cara menyimpan cookie masih sama, menggunakan `http.SetCookie`. + +Selanjutnya buat fungsi `getCookie()`, untuk mempermudah proses pembacaan cookie yang tersimpan. + +```go +func getCookie(c echo.Context, name string) (M, error) { + cookie, err := c.Request().Cookie(name) + if err == nil { + + data := M{} + if err = sc.Decode(name, cookie.Value, &data); err == nil { + return data, nil + } + } + + return nil, err +} +``` + +Setelah cookie diambil menggunakan `c.Request().Cookie()`, data di dalamnya perlu di-decode agar bisa terbaca. Method `sc.Decode()` digunakan untuk decoding data. + +OK, sekarang buat fungsi `main()`, lalu isi dengan kode di bawah ini. + +```go +const CookieName = "data" + +e := echo.New() + +e.GET("/index", func(c echo.Context) error { + data, err := getCookie(c, CookieName) + if err != nil && err != http.ErrNoCookie && err != securecookie.ErrMacInvalid { + return err + } + + if data == nil { + data = M{"Message": "Hello", "ID": gubrak.RandomString(32)} + + err = setCookie(c, CookieName, data) + if err != nil { + return err + } + } + + return c.JSON(http.StatusOK, data) +}) + +e.Logger.Fatal(e.Start(":9000")) +``` + +Konstanta `CookieName` disiapkan, kita gunakan sebagai identifier cookie. Dan sebuah rute juga disiapkan dengan tugas menampilkan data cookie jika sudah ada, dan membuat cookie baru jika belum ada. + +Dalam handler rute, terdapat beberapa proses terjadi. Pertama, objek cookie dengan identifier `CookieName` diambil, jika muncul error, dan jenisnya adalah selain error karena cookie tidak ada, dan error-nya selain *invalid cookie*, maka kembalikan objek error tersebut. + +> `http.ErrNoCookie` adalah variabel penanda error karena cookie kosong, sedangkan `securecookie.ErrMacInvalid` adalah representasi dari invalid cookie. + +Lalu, kita cek data cookie yang dikembalikan, jika kosong (bisa karena cookie belum dibuat ataupun sudah ada tetapi datanya kosong) maka buat data baru untuk disimpan dalam cookie. Data tersebut bertipe `map`, salah satu elemen map tersebut ada yg value-nya adalah random. + +> Pada kode di atas, generate random string dilakukan dengan memanfaatkan 3rd party library [Gubrak v2](https://github.com/novalagung/gubrak). + +Pengaksesan rute akan memunculkan data yang sama. Karena pembuatan cookie hanya dilakukan ketika datanya kosong atau cookie nya belum dibuat. + +Jalankan aplikasi untuk mengetes hasilnya. Lakukan refresh beberapa kali, data yang muncul pasti sama. + +![Secure cookie test](images/C_securecookie_1_securecookie.png) + +Lihat pada response header url `index`, data pada cookie terlihat sudah dalam kondisi encoded dan encrypted. + +![Cookie header](images/C_securecookie_2_cookie_header.png) + +## C.12.2. Delete Secure Cookie + +Securecookie perannya hanya pada bagian encode-decode data cookie, sedangkan proses simpan baca cookie masih sama seperti penerapan cookie biasa. Maka cara menghapus cookie pun masih sama, yaitu dengan meng-expired-kan cookie yang sudah disimpan. + +```go +cookie := &http.Cookie{} +cookie.Name = name +cookie.Path = "/" +cookie.MaxAge = -1 +cookie.Expires = time.Unix(0, 0) +http.SetCookie(c.Response(), cookie) +``` + +--- + + - [Echo](https://github.com/labstack/echo), by Vishal Rana (Lab Stack), MIT license + - [Gorilla Securecookie](https://github.com/gorilla/securecookie), by Gorilla web toolkit team, BSD-3-Clause license + - [Gubrak v2](https://github.com/novalagung/gubrak), by Noval Agung, MIT license + +--- + + + + +--- + + diff --git a/en/content-en/C-send-email.md b/en/content-en/C-send-email.md new file mode 100644 index 000000000..6e86ed218 --- /dev/null +++ b/en/content-en/C-send-email.md @@ -0,0 +1,203 @@ +# C.18. Send Mail (`net/smtp`, Gomail v2) + +Pada chapter ini kita akan belajar cara mengirim email dari aplikasi golang, menggunakan dua cara berikut. + + 1. Dengan memanfaatkan package `net/smtp`. + 2. Menggunakan Library [gomail](https://gopkg.in/gomail.v2). + +## C.18.1. Kirim Email Menggunakan `net/smtp` + +Golang menyediakan package `net/smtp`, isinya banyak API untuk berkomunikasi via protokol SMTP. Lewat package ini kita bisa melakukan operasi kirim email. + +Sebuah akun email diperlukan dalam mengirim email, silakan gunakan provider email apa saja. Pada chapter ini kita gunakan Google Mail (gmail), jadi siapkan satu buah akun gmail untuk keperluan testing. + +Mari kita praktekan. Buat folder project baru, salin kode berikut. + +```go +package main + +import ( + "fmt" + "log" + "net/smtp" + "strings" +) + +const CONFIG_SMTP_HOST = "smtp.gmail.com" +const CONFIG_SMTP_PORT = 587 +const CONFIG_SENDER_NAME = "PT. Makmur Subur Jaya +The referrer localhost does not match the referrer restrictions configured on your API key. Please use the API Console to update your key restrictions.+ +Error di atas muncul karena, host `localhost` belum didaftarkan pada API console. Berbeda dengan `mysampletestapp.com` yang sudah didaftarkan, host ini berhak mengakses menggunakan API key yang kita gunakan. + +![Api configuration](images/D_google_api_search_5_api_hostname.png) + +--- + + + +--- + + diff --git a/en/content-en/D-insert-1mil-csv-record-into-db-in-a-minute.md b/en/content-en/D-insert-1mil-csv-record-into-db-in-a-minute.md new file mode 100644 index 000000000..8d764f91d --- /dev/null +++ b/en/content-en/D-insert-1mil-csv-record-into-db-in-a-minute.md @@ -0,0 +1,351 @@ +# D.1. Insert 1 Juta Data dari File CSV Ke Database Server, Menggunakan Teknik Worker Pool, Database Connection Pool, dan Mekanisme Failover. + +Pada chapter ini kita akan praktek penerapan salah satu teknik concurrent programming di Go yaitu worker pool, dikombinasikan dengan database connection pool, untuk membaca 1 juta rows data dari sebuah file csv untuk kemudian di-insert-kan ke mysql server. + +Pada bagian insert data kita terapkan mekanisme failover, jadi ketika ada operasi insert gagal, maka akan otomatis di recover dan di retry. Jadi idealnya di akhir, semua data, sejumlah satu juta, akan berhasil di-insert. + +## D.1.1. Penjelasan + +#### ◉ Worker Pool + +Worker pool adalah teknik manajemen goroutine dalam *concurrent programming* pada Go. Sejumlah worker dijalankan dan masing-masing memiliki tugas yang sama yaitu menyelesaikan sejumlah jobs. + +Dengan metode worker pool ini, maka penggunaan memory dan performansi program akan bisa optimal. + +#### ◉ Database Connection Pool + +*Connection pool* adalah metode untuk manajemen sejumlah koneksi database, agar bisa digunakan secara optimal. + +Connection pool sangat penting dalam kasus operasi data yang berhubungan dengan database yang mana concurrent programming diterapkan. + +Karena pada concurrent programming, beberapa proses akan berjalan bersamaan, maka penggunaan 1 koneksi db akan menghambat proses tersebut. Perlu ada beberapa koneksi database, agar goroutine tidak rebutan objek koneksi database. + +#### ◉ Failover + +Failover merupakan mekanisme backup ketika sebuah proses gagal. Pada konteks ini, failover mengarah ke proses untuk me-retry operasi insert ketika gagal. + +## D.1.2. Persiapan + +File [majestic-million-csv](https://blog.majestic.com/development/majestic-million-csv-daily/) digunakan sebagai bahan dalam praktek. File tersebut gratis dengan lisensi CCA3. Isinya adalah list dari top website berjumlah 1 juta. + +Silakan download file nya di sini http://downloads.majestic.com/majestic_million.csv. + +Setelah itu siapkan My SQL database server, create database dan sebuah tabel di dalamnya dengan nama domain. + +```sql +CREATE DATABASE IF NOT EXISTS test; +USE test; +CREATE TABLE IF NOT EXISTS domain ( + GlobalRank int, + TldRank int, + Domain varchar(255), + TLD varchar(255), + RefSubNets int, + RefIPs int, + IDN_Domain varchar(255), + IDN_TLD varchar(255), + PrevGlobalRank int, + PrevTldRank int, + PrevRefSubNets int, + PrevRefIPs int +); +``` + +Setelah itu buat project baru, dan sebuah file `main.go`, dan tempatkan file csv yang sudah didownload dalam folder yang sama. + +Karena di contoh ini saya menggunakan My SQL, maka perlu untuk go get driver RDBMS ini untuk go. + +```bash +go get -u github.com/go-sql-driver/mysql +``` + +Jika pembaca ingin menggunakan driver lain, juga silakan. + +## D.1.3. Praktek + +#### ◉ Definisi Konstanta + +Ada beberapa konstanta yang perlu dipersiapkan. Pertama connection string untuk komunikasi ke database server. Sesuaikan value nya dengan yang dipergunakan. + +```go +const dbConnString = "root@/test" +``` + +Lalu jumlah koneksi idle yang diperbolehkan, kita set saja 4, karena nantinya semua connection yg di create akan sibuk untuk bekerja meng-insert data. + +```go +const dbMaxIdleConns = 4 +``` + +Jumlah maksimum koneksi database dalam pool. + +```go +const dbMaxConns = 100 +``` + +Jumlah worker yang akan bekerja untuk menjalankan job. + +```go +const totalWorker = 100 +``` + +Path dari file CSV. Karena file berada satu level dengan `main.go` maka tulis saja nama file nya. + +```go +const csvFile = "majestic_million.csv" +``` + +Terakhir, siapkan variabel untuk menampung data header dari pembacaan CSV nanti. + +```go +var dataHeaders = make([]string, 0) +``` + +#### ◉ Fungsi Buka Koneksi Database + +Buat fungsi untuk buka koneksi database, yg dikembalikan objek database kembalian fungsi `sql.Open()`. + +Jangan lupa set nilai `MaxOpenConns` dan `MaxIdleConns`. + +O iya, untuk yang tidak menggunakan mysql, maka sesuaikan saja nilai argument pertama statement `sql.Open()`. + +```go +func openDbConnection() (*sql.DB, error) { + log.Println("=> open db connection") + + db, err := sql.Open("mysql", dbConnString) + if err != nil { + return nil, err + } + + db.SetMaxOpenConns(dbMaxConns) + db.SetMaxIdleConns(dbMaxIdleConns) + + return db, nil +} +``` + +O iya jangan lupa untuk import driver nya. + +```go +import _ "github.com/go-sql-driver/mysql" +``` + +#### ◉ Fungsi Baca CSV + +Buka file CSV, lalu gunakan objek file untuk membuat objek CSV reader baru. + +```go +func openCsvFile() (*csv.Reader, *os.File, error) { + log.Println("=> open csv file") + + f, err := os.Open(csvFile) + if err != nil { + return nil, nil, err + } + + reader := csv.NewReader(f) + return reader, f, nil +} +``` + +#### ◉ Fungsi Menjalankan Workers + +Ok, sekarang kita mulai masuk ke aspek konkurensi dari pembahasan ini. Siapkan fungsi yang isinya men-dispatch beberapa goroutine sejumlah `totalWorker`. + +Tiap-tiap goroutine tersebut adalah worker atau pekerja, yang tugasnya nanti akan meng-insert data ke database. + +Saat aplikasi dijalankan, sejumlah 100 worker akan berlomba-lomba menyelesaikan job insert data sejumlah 1 juta data. + +1 job adalah 1 data, maka rata-rata setiap worker akan menyelesaikan operasi insert sekitar 10k. Tapi ini jelasnya tidak pasti karena worker akan berkompetisi dalam penyelesaian job, jadi sangat besar kemungkinan akan ada job yang menyelesaikan lebih dari 10k jobs, ataupun yg di bawah 10k jobs. + +```go +func dispatchWorkers(db *sql.DB, jobs <-chan []interface{}, wg *sync.WaitGroup) { + for workerIndex := 0; workerIndex <= totalWorker; workerIndex++ { + go func(workerIndex int, db *sql.DB, jobs <-chan []interface{}, wg *sync.WaitGroup) { + counter := 0 + + for job := range jobs { + doTheJob(workerIndex, counter, db, job) + wg.Done() + counter++ + } + }(workerIndex, db, jobs, wg) + } +} +``` + +Bisa dilihat dalam fungsi di atas, di dalam goroutine/worker, isi channel jobs (yang berupa data dari proses pembacaan CSV), didistribusikan ke worker, ke goroutine. + +Fungsi `doTheJob()` yang nantinya kita buat, isinya adalah operasi insert data ke database server. Setiap satu operasi insert selesai, `wg.Done()` untuk menandai bahwa 1 job adalah selesai. + +Idealnya di akhir aplikasi akan terjadi pemanggilan `wg.Done()` sejumlah 1 juta karena ada 1 juta jobs. + +#### ◉ Fungsi Baca CSV dan Pengiriman Jobs ke Worker + +Proses pembacaan CSV, apapun metodenya pasti yang dijalankan adalah membaca data dari line ke line dari baris paling bawah. + +> Proses baca satu file tidak bisa di-konkurensi-kan + +```go +func readCsvFilePerLineThenSendToWorker(csvReader *csv.Reader, jobs chan<- []interface{}, wg *sync.WaitGroup) { + for { + row, err := csvReader.Read() + if err != nil { + if err == io.EOF { + err = nil + } + break + } + + if len(dataHeaders) == 0 { + dataHeaders = row + continue + } + + rowOrdered := make([]interface{}, 0) + for _, each := range row { + rowOrdered = append(rowOrdered, each) + } + + wg.Add(1) + jobs <- rowOrdered + } + close(jobs) +} +``` + +Data dibaca dalam perulangan per baris. Pada pembacaan pertama, rows akan ditampung ke variabel `dataHeaders`. Selanjutnya, data dikirimkan ke worker lewat channel `jobs`. + +Setelah proses baca data selesai, channel di close. Karena pengiriman dan penerimaan data pada channel bersifat synchronous untuk unbuffered channel. Jadi aman untuk berasumsi bahwa ketika semua data berhasil dikirim, maka semua data tersebut juga berhasil diterima. + +Jika blok kode perulangan dalam fungsi di atas selesai, maka sudah tidak ada lagi operasi kirim terima data, maka kita close channelnya. + +#### ◉ Fungsi Insert Data ke Database + +```go +func doTheJob(workerIndex, counter int, db *sql.DB, values []interface{}) { + for { + var outerError error + func(outerError *error) { + defer func() { + if err := recover(); err != nil { + *outerError = fmt.Errorf("%v", err) + } + }() + + conn, err := db.Conn(context.Background()) + query := fmt.Sprintf("INSERT INTO domain (%s) VALUES (%s)", + strings.Join(dataHeaders, ","), + strings.Join(generateQuestionsMark(len(dataHeaders)), ","), + ) + + _, err = conn.ExecContext(context.Background(), query, values...) + if err != nil { + log.Fatal(err.Error()) + } + + err = conn.Close() + if err != nil { + log.Fatal(err.Error()) + } + }(&outerError) + if outerError == nil { + break + } + } + + if counter%100 == 0 { + log.Println("=> worker", workerIndex, "inserted", counter, "data") + } +} + +func generateQuestionsMark(n int) []string { + s := make([]string, 0) + for i := 0; i < n; i++ { + s = append(s, "?") + } + return s +} +``` + +Pada kode di atas bisa dilihat bahwa kode insert dibungkus dalam IIFE dalam sebuah perulangan. + +Kenapa butuh perulangan? keyword `for` di atas perannya sangat penting. Di sini diterapkan mekanisme failover di mana ketika proses insert gagal akan di recover dan di-retry ulang. + +Nah jadi ketika operasi insert di atas gagal, maka error tetap di tampilkan tapi kemudian diulang kembali insert data yang gagal tadi, hingga sukses. + +O iya, mengenai kode untuk manajemen db connection poll mana ya? sepertinya tidak ada. Yups, memang tidak ada. As per Go official documentation untuk package `sql/database`, connection pool di manage oleh Go, kita engineer cukup panggil method `.Conn()` milik `*sql.DB` untuk mengambil pool item, yang pool item ini bisa berupa connection lama yang di reuse atau connection yang baru dibuat. + +> Conn returns a single connection by either opening a new connection or returning an existing connection from the connection pool. Conn will block until either a connection is returned or ctx is canceled. Queries run on the same Conn will be run in the same database session. +