Saya sudah bertahun-tahun menulis JavaScript dan TypeScript, kebanyakan di sisi frontend. Belakangan saya mulai belajar Go (sering disebut Golang), dan ternyata tidak semenakutkan yang saya kira.
Banyak hal yang kamu tahu dari JS/TS langsung berlaku di Go. Beberapa hal berbeda, dan ada juga yang bakal bikin kaget. Tulisan ini membandingkan ketiga bahasa itu secara berdampingan, supaya kamu bisa melihat mana yang sama dan mana yang baru.
Kalau kamu bisa menulis TypeScript, kamu bisa belajar Go. Ayo mulai 🚀
Kenapa frontend dev perlu belajar Go?#
- Backend dan API yang cepat. Go populer untuk web server, microservice, dan CLI.
- Bahasanya sederhana. Go punya sangat sedikit keyword. Biasanya hanya ada satu cara yang jelas untuk melakukan sesuatu.
- Banyak tool yang kamu pakai ditulis dengan Go. esbuild (bundler yang dipakai Vite di balik layar) ditulis dengan Go. Begitu juga Docker, Kubernetes, dan Hugo.
- Satu binary.
go buildmenghasilkan satu file yang bisa dijalankan di mana saja. Tanpanode_modules😅
Gambaran besarnya#
| Topik | JavaScript | TypeScript | Go |
|---|---|---|---|
| Tipe | Dinamis | Statis (dicek saat build, hilang saat runtime) | Statis (dicek saat compile, tetap ada saat runtime) |
| Cara berjalan | Interpreted / JIT (Node, browser) | Dikompilasi ke JS, lalu sama seperti JS | Dikompilasi ke native binary |
| Package manager | npm / pnpm / yarn | npm / pnpm / yarn | Bawaan: go mod |
| Formatter | Prettier / Biome | Prettier / Biome | Bawaan: go fmt |
| Titik koma | Opsional | Opsional | Tidak perlu |
| Class | Ada | Ada | Tidak ada (pakai struct + method) |
| Penanganan error | try / catch |
try / catch |
Error dikembalikan sebagai nilai |
| Async | Promise, async/await |
Promise, async/await |
Goroutine dan channel |
null / undefined |
Keduanya | Keduanya | Hanya nil |
Jangan khawatir kalau beberapa baris terlihat aneh. Kita akan bahas satu per satu.
1. Hello World#
JavaScript / TypeScript
console.log("Hello, World!");Go
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}Apa yang berbeda
- Setiap file Go diawali dengan
package. Program yang bisa dijalankan selalu memakaipackage main. - Program dimulai dari
func main(). Anggap saja seperti entry file di konfigurasi bundler kamu. fmtadalah package bawaan untuk mencetak output.fmt.Println≈console.log.
Jalankan dengan:
go run main.go2. Variabel#
JavaScript
let name = "Fachalik";
const age = 25;TypeScript
let name: string = "Fachalik";
const age: number = 25;Go
var name string = "Fachalik" // bentuk lengkap
var city = "Jakarta" // tipe ditebak otomatis
age := 25 // bentuk singkat, hanya di dalam fungsi
const pi = 3.14Apa yang sama
- Type inference bekerja seperti di TypeScript. Go menebak tipe dari nilainya.
constberarti nilainya tidak bisa diubah.
Apa yang berbeda
- Tipe ditulis setelah nama, seperti TS, tapi tanpa titik dua:
name string, bukanname: string. :=adalah shortcut yang paling sering kamu pakai. Artinya "deklarasikan dan isi".constdi Go hanya untuk nilai sederhana (angka, string, boolean). Kamu tidak bisa menulisconst user = { ... }.- Variabel yang tidak dipakai adalah compile error. Go menolak build kalau kamu mendeklarasikan sesuatu tapi tidak pernah memakainya. Awalnya menyebalkan, tapi ini membuat kode tetap bersih.
Zero value sebagai pengganti undefined#
Di JS, variabel yang belum diisi bernilai undefined. Di Go, setiap tipe punya zero value:
var count int // 0
var title string // "" (string kosong)
var done bool // falseJadi, kamu tidak akan pernah ketemu error "cannot read property of undefined" dari angka atau string biasa. 🎉
3. Tipe data#
| TypeScript | Go |
|---|---|
string |
string |
number |
int, int64, float64, ... |
boolean |
bool |
string[] |
[]string (sebuah slice) |
Record<string, number> |
map[string]int |
interface User { ... } / type User = { ... } |
type User struct { ... } |
any / unknown |
any (alias dari interface{}) |
Perubahan terbesar: Go punya tipe terpisah untuk bilangan bulat dan desimal. int untuk bilangan bulat, float64 untuk desimal. Kamu tidak bisa menjumlahkan keduanya tanpa konversi:
a := 10 // int
b := 2.5 // float64
sum := float64(a) + b // harus dikonversi secara eksplisitTypeScript akan membiarkan kamu menulis a + b begitu saja. Go memaksamu menyatakan maksudmu dengan jelas.
4. Fungsi#
JavaScript
function add(a, b) {
return a + b;
}
const multiply = (a, b) => a * b;TypeScript
function add(a: number, b: number): number {
return a + b;
}Go
func add(a int, b int) int {
return a + b
}
// tipe yang sama bisa dikelompokkan
func multiply(a, b int) int {
return a * b
}Apa yang sama: parameter bertipe, tipe return, dan return. Cara membacanya hampir sama dengan TypeScript.
Apa yang berbeda
- Keyword-nya
func, dan tipe return tidak pakai titik dua. - Tidak ada arrow function. Tapi kamu tetap bisa menyimpan fungsi di variabel dan mengopernya ke mana-mana:
greet := func(name string) string {
return "Hi, " + name
}
fmt.Println(greet("Gopher"))Beberapa nilai return#
Ini hal baru yang kemungkinan besar bakal kamu suka. Fungsi Go bisa mengembalikan lebih dari satu nilai:
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}
result, err := divide(10, 2)Rasanya seperti array destructuring di JS (const [result, err] = divide(10, 2)), tapi sudah jadi bagian dari bahasanya.
5. Object → Struct#
Go tidak punya class. Sebagai gantinya kamu memakai struct (bentuk datanya) lalu menempelkan method ke struct tersebut.
TypeScript
class User {
constructor(
public name: string,
public age: number,
) {}
greet() {
return `Hi, I'm ${this.name}`;
}
}
const user = new User("Fachalik", 25);
user.greet();Go
type User struct {
Name string
Age int
}
func (u User) Greet() string {
return "Hi, I'm " + u.Name
}
user := User{Name: "Fachalik", Age: 25}
user.Greet()Apa yang berbeda
(u User)sebelum nama method disebut receiver. Miripthis, tapi kamu yang memberi namanya.- Tidak ada
new, tidak ada constructor. Cukup tulisUser{...}. Secara konvensi, orang membuat fungsi sepertiNewUser(...)kalau butuh logika inisialisasi. - Huruf kapital = public, huruf kecil = private.
Namebisa dipakai dari package lain;nametidak bisa. Tidak ada keywordexport, tidak ada keywordprivate.
Interface (bagian yang familiar)#
Kabar baiknya: interface di Go terasa mirip structural typing di TypeScript. Kamu tidak perlu menulis implements. Kalau sebuah tipe punya method yang sesuai, tipe itu otomatis cocok dengan interface-nya.
type Greeter interface {
Greet() string
}
func sayHello(g Greeter) {
fmt.Println(g.Greet())
}
sayHello(user) // jalan, karena User punya Greet()Ini konsep duck typing ("kalau kelihatan seperti bebek, berarti bebek") yang sama dengan yang sudah kamu kenal dari TS.
6. Array, slice, dan map#
JavaScript / TypeScript
const fruits: string[] = ["apple", "banana"];
fruits.push("mango");
const scores: Record<string, number> = { alice: 90 };
scores.bob = 80;Go
fruits := []string{"apple", "banana"}
fruits = append(fruits, "mango") // catatan: hasilnya di-assign ulang
scores := map[string]int{"alice": 90}
scores["bob"] = 80
value, ok := scores["charlie"] // ok bernilai false kalau key-nya tidak ada[]stringadalah slice. Pakai saja seperti array di JS.appendmengembalikan slice baru, jadi kamu selalu menulisfruits = append(fruits, ...).- Pola
value, ok :=adalah cara mengecek apakah sebuah key ada. Lebih aman daripada mendapatundefined.
Tidak ada .map(), .filter(), .reduce() ... cukup for#
Ini momen "lho, kok?" terbesar buat saya. Go tidak punya method array bawaan seperti itu. Kamu menulis loop:
JavaScript
const evens = numbers.filter((n) => n % 2 === 0);Go
var evens []int
for _, n := range numbers {
if n%2 == 0 {
evens = append(evens, n)
}
}Kodenya memang lebih panjang, tapi sangat mudah dibaca. (Package slices di standard library membantu untuk tugas umum seperti slices.Contains dan slices.Sort.)
7. Loop dan kondisi#
Go hanya punya satu keyword loop: for. Semua jenis perulangan ditulis dengan for.
// loop klasik, seperti for (let i = 0; i < 5; i++)
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// seperti while (count < 3)
count := 0
for count < 3 {
count++
}
// seperti for...of / forEach
for index, fruit := range fruits {
fmt.Println(index, fruit)
}
// tidak butuh index? pakai _
for _, fruit := range fruits {
fmt.Println(fruit)
}if terlihat familiar, hanya saja tanpa tanda kurung (tapi kurung kurawal wajib):
if age >= 18 {
fmt.Println("adult")
} else {
fmt.Println("minor")
}Perbedaan kecil lainnya:
- Tidak ada
===. Go hanya punya==, dan selalu ketat karena tipenya harus sama. - Tidak ada ternary
a ? b : c. Pakaiifbiasa. switchtidak otomatis lanjut ke case berikutnya (fall through), jadi kamu tidak perlubreakdi setiap case.
8. Penanganan error: tanpa try/catch#
JavaScript / TypeScript
try {
const data = JSON.parse(text);
} catch (err) {
console.error(err);
}Go
var data map[string]any
err := json.Unmarshal([]byte(text), &data)
if err != nil {
fmt.Println("failed:", err)
return
}Di Go, error hanyalah nilai yang dikembalikan oleh fungsi. Kamu langsung mengeceknya dengan if err != nil. Kamu akan menulis baris ini sangat sering.
Awalnya terasa repetitif. Lama-lama saya mulai suka: kamu selalu tahu persis baris mana yang bisa gagal, dan tidak ada yang tiba-tiba di-throw "entah dari mana".
9. null / undefined → nil, dan pointer#
JS punya dua nilai "kosong". Go hanya punya satu: nil, yang dipakai untuk pointer, slice, map, interface, dan error.
Go juga punya pointer. Jangan panik, ini lebih sederhana daripada di C. Idenya:
- Nilai biasa adalah salinan.
- Pointer (
*User) adalah referensi ke aslinya.
func birthday(u *User) {
u.Age++ // mengubah data aslinya
}
user := User{Name: "Fachalik", Age: 25}
birthday(&user) // & artinya "berikan pointer ke ini"
fmt.Println(user.Age) // 26Di JS, object selalu dioper sebagai referensi, jadi kamu tidak pernah memikirkan ini. Di Go, struct disalin secara default, dan kamu memakai pointer kalau ingin mengubah aslinya. Hanya itu yang perlu kamu tahu untuk memulai.
10. Async: Promise vs goroutine#
JavaScript / TypeScript
async function fetchAll() {
const [users, posts] = await Promise.all([getUsers(), getPosts()]);
}Go
func fetchAll() {
var wg sync.WaitGroup
var users []User
var posts []Post
wg.Add(2)
go func() {
defer wg.Done()
users = getUsers()
}()
go func() {
defer wg.Done()
posts = getPosts()
}()
wg.Wait() // seperti await Promise.all
}go someFunction()menjalankan sebuah goroutine: tugas yang sangat ringan dan berjalan bersamaan dengan sisa kode kamu.sync.WaitGroupmenunggu semuanya selesai, sepertiPromise.all.defermenjalankan sebuah baris saat fungsi selesai, sepertifinallyversi mini.
Tidak ada keyword async / await. Di Go, kode biasa cukup memblokir dan menunggu, dan itu tidak masalah karena Go menjalankan banyak goroutine sekaligus. Kamu tidak perlu "mewarnai" fungsi sebagai async.
Untuk mengirim data antar goroutine, Go punya channel. Bayangkan saja seperti pipa:
ch := make(chan string)
go func() {
ch <- "done!" // kirim ke dalam pipa
}()
msg := <-ch // tunggu dan terima dari pipa
fmt.Println(msg)11. Package dan import#
JavaScript / TypeScript
import { readFile } from "fs/promises";
import express from "express";Go
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)| Dunia npm | Dunia Go |
|---|---|
package.json |
go.mod |
pnpm-lock.yaml |
go.sum |
pnpm init |
go mod init my-app |
pnpm add express |
go get github.com/gin-gonic/gin |
node_modules/ |
Module cache bersama (tidak di dalam project) |
Keyword export |
Huruf pertama kapital |
Standard library Go itu besar. Kamu bisa membuat HTTP server lengkap dengan net/http tanpa package pihak ketiga:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello from Go!")
})
http.ListenAndServe(":3000", nil)
}Bandingkan dengan repotnya menyiapkan Express. Lumayan, kan?
12. Contekan tooling#
| Yang kamu lakukan di JS/TS | Di Go |
|---|---|
node index.js / tsx index.ts |
go run . |
tsc / vite build |
go build |
| Prettier / Biome | go fmt ./... |
| ESLint | go vet ./... |
| Jest / Vitest | go test ./... |
Formatting, testing, dan linting semuanya sudah bawaan. Tidak ada file konfigurasi yang perlu diperdebatkan. 😄
Ringkasan singkat#
Hal-hal yang akan terasa familiar
- Static type dan type inference (seperti TypeScript)
- Interface yang dicocokkan berdasarkan bentuk, tanpa
implements(seperti TypeScript) - Fungsi sebagai nilai, closure
if,for,switch, kurung kurawal- Import package dan file modul
Hal-hal yang baru
- Dikompilasi jadi satu binary, tanpa runtime seperti Node
- Tidak ada class: pakai struct + method
- Huruf kapital berarti di-export
- Error dikembalikan, bukan di-throw
- Beberapa nilai return
- Pointer (
*dan&) - Goroutine dan channel sebagai pengganti Promise
- Hanya satu jenis loop:
for - Tidak ada
.map()/.filter(): pakai loop - Variabel dan import yang tidak dipakai membuat kode gagal di-compile
Tips saya untuk memulai#
- Ikuti Tour of Go resmi. Bisa langsung dicoba di browser dan selesai dalam satu sore.
- Bangun ulang sesuatu yang kecil dan sudah kamu kenal, misalnya REST API todo, dengan
net/http. - Jangan melawan
if err != nil. Terima saja. - Biarkan
go fmtmerapikan kode kamu dan tidak usah lagi memikirkan gaya penulisan. - Baca Go by Example kalau lupa sintaks sesuatu.
Saya masih di awal perjalanan belajar Go, tapi berangkat dari TypeScript membuatnya jauh lebih mudah dari yang saya bayangkan. Kalau kamu frontend developer yang penasaran dengan backend, Go adalah tempat yang bagus untuk memulai.
Selamat ngoding, sesama gopher! 🐹
