Pernah nggak sih kamu ngerasain momen di mana tiba-tiba kepikiran, “Kayaknya seru ya bikin API sendiri.” Terus langsung buka editor, tapi begitu lihat banyak pilihan framework, malah bingung duluan. Kalau kamu lagi di situasi itu dan tertarik sama Go (atau Golang), tenang — kamu nggak sendirian.

Beberapa waktu lalu, gue juga ada di posisi yang sama. Udah biasa pakai Node.js atau Python buat bikin backend, tapi penasaran kenapa banyak startup dan perusahaan besar kayak Uber, Dropbox, sampai Tokocrypto pakai Go. Setelah coba-coba, ternyata oh ternyata — bikin REST API dengan Go itu refreshing banget. Kodenya ringan, performanya kenceng, dan belajarnya nggak serumit yang gue bayangin.

Artikel ini gue tulis sebagai panduan personal sekaligus catatan buat kamu yang mau mulai bikin REST API pakai Go dengan Gin Framework. Gue akan bahas step-by-step, dari instalasi sampai endpoint bisa dipanggil. Nggak pake ribet, langsung praktik.

Kenapa Pakai Go dan Gin?

Sebelum masuk ke kode, gue mau jujur dulu: kenapa sih akhirnya gue milih Go + Gin, bukan yang lain?

Go itu simpel. Bahasanya minimalis, nggak ada inheritance yang njelimet, nggak ada class yang berlapis-lapis. Buat bikin API, ini ideal banget karena yang kamu butuhin biasanya cuma: terima request, proses data, kirim response. Nggak lebih.

Gin itu cepet. Gin adalah salah satu framework HTTP paling populer untuk Go. Menurut benchmark yang banyak beredar, Gin bisa handle ribuan request per detik dengan latensi yang sangat kecil. Router-nya pakai radix tree, jadi pencarian route-nya efisien banget.

Dan satu lagi yang bikin gue betah: Go punya standar library yang powerful banget. Package net/http udah cukup buat bikin web server dari nol. Tapi Gin nambahin banyak kemudahan di atasnya — middleware, binding, validation, rendering — tanpa bikin semuanya jadi kompleks.

Jadi kalau kamu cari stack backend yang performa-nya oke, deployment-nya gampang (single binary, bro!), dan belajar curve-nya nggak terlalu curam, Go + Gin adalah combo yang sangat worth it buat dicoba.

Persiapan: Install Go dan Setup Project

Oke, mulai dari nol ya. Pastikan Go udah ter-install di mesin kamu. Kalau belum, langsung ke https://go.dev/dl/ dan download versi terbaru sesuai OS kamu.

Setelah install, cek di terminal:

go version

Kalau muncul sesuatu kayak go version go1.22.0 darwin/arm64, berarti udah ready. Sekarang, buat folder project baru:

mkdir go-gin-api
cd go-gin-api

Inisialisasi module Go:

mod init go-gin-api

Setelah itu, install Gin framework:

go get -u github.com/gin-gonic/gin

Kalau proses ini lancar, folder project kamu sekarang punya go.mod dan go.sum. Dua file ini penting — semacam package.json-nya Go kalau kamu familiar dengan Node.js.

Sekarang buat file utama:

touch main.go

Dan tulis kode paling dasar ini:

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	r.GET("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "pong",
		})
	})

	r.Run() // listen and serve on 0.0.0.0:8080
}

Jalankan:

go run main.go

Buka browser atau Postman, akses http://localhost:8080/ping. Kalau muncul {"message": "pong"}, selamat — server pertama kamu dengan Gin udah jalan!

Yang gue suka dari Gin: lima baris kode udah cukup buat bikin HTTP server yang jalan. Nggak perlu setup konfigurasi yang njelimet. gin.Default() udah include logger dan recovery middleware secara otomatis.

Membuat CRUD REST API: Studi Kasus Manajemen Buku

Sekarang kita masuk ke bagian yang seru: bikin API CRUD (Create, Read, Update, Delete) beneran. Kita akan bikin API sederhana untuk manajemen data buku.

Struktur Data dan Model

Pertama, definisikan struct untuk data buku. Kita simpen di file yang sama dulu biar gampang dipahami:

type Book struct {
	ID     string `json:"id"`
	Title  string `json:"title" binding:"required"`
	Author string `json:"author" binding:"required"`
	Year   int    `json:"year" binding:"required"`
}

Perhatikan tag json:"..." di belakang setiap field. Ini buat mapping nama field ke JSON. Tag binding:"required" adalah fitur validasi dari Gin — kalau field ini kosong waktu request masuk, Gin otomatis nolak.

Sebagai penyimpanan data sementara, kita pakai slice (array dinamis di Go). Di production tentu kamu akan pakai database, tapi buat belajar, ini udah cukup:

var books = []Book{
	{ID: "1", Title: "Laskar Pelangi", Author: "Andrea Hirata", Year: 2005},
	{ID: "2", Title: "Bumi Manusia", Author: "Pramoedya Ananta Toer", Year: 1980},
}

Endpoint GET — Ambil Semua Buku

r.GET("/books", func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"status": "success",
		"data":   books,
	})
})

Mudah banget kan? gin.H itu sebenarnya cuma alias untuk map[string]interface{}. Jadi kamu bisa bikin JSON response dengan nested object sebebas yang kamu mau.

Endpoint GET by ID — Ambil Buku Spesifik

r.GET("/books/:id", func(c *gin.Context) {
	id := c.Param("id")

	for _, book := range books {
		if book.ID == id {
			c.JSON(http.StatusOK, gin.H{
				"status": "success",
				"data":   book,
			})
			return
		}
	}

	c.JSON(http.StatusNotFound, gin.H{
		"status":  "error",
		"message": "Buku tidak ditemukan",
	})
})

Di sini kita pakai c.Param("id") buat ambil parameter dari URL. Misalnya akses /books/1, maka id bernilai "1".

Endpoint POST — Tambah Buku Baru

r.POST("/books", func(c *gin.Context) {
	var newBook Book

	if err := c.ShouldBindJSON(&newBook); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"status":  "error",
			"message": err.Error(),
		})
		return
	}

	newBook.ID = fmt.Sprintf("%d", len(books)+1)
	books = append(books, newBook)

	c.JSON(http.StatusCreated, gin.H{
		"status":  "success",
		"message": "Buku berhasil ditambahkan",
		"data":    newBook,
	})
})

ShouldBindJSON adalah method andalan Gin buat parsing request body JSON ke struct. Kalau ada field required yang kosong, dia otomatis return error. Ini ngebantu banget karena kamu nggak perlu nulis validasi manual.

Endpoint PUT — Update Buku

r.PUT("/books/:id", func(c *gin.Context) {
	id := c.Param("id")
	var updatedBook Book

	if err := c.ShouldBindJSON(&updatedBook); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"status":  "error",
			"message": err.Error(),
		})
		return
	}

	for i, book := range books {
		if book.ID == id {
			books[i].Title = updatedBook.Title
			books[i].Author = updatedBook.Author
			books[i].Year = updatedBook.Year

			c.JSON(http.StatusOK, gin.H{
				"status":  "success",
				"message": "Buku berhasil diperbarui",
				"data":    books[i],
			})
			return
		}
	}

	c.JSON(http.StatusNotFound, gin.H{
		"status":  "error",
		"message": "Buku tidak ditemukan",
	})
})

Endpoint DELETE — Hapus Buku

r.DELETE("/books/:id", func(c *gin.Context) {
	id := c.Param("id")

	for i, book := range books {
		if book.ID == id {
			books = append(books[:i], books[i+1:]...)
			c.JSON(http.StatusOK, gin.H{
				"status":  "success",
				"message": "Buku berhasil dihapus",
			})
			return
		}
	}

	c.JSON(http.StatusNotFound, gin.H{
		"status":  "error",
		"message": "Buku tidak ditemukan",
	})
})

Dan itu dia — lima endpoint CRUD yang udah bisa dipakai. Total kode? Masih sangat ringkas. Kalau kamu coba di bahasa lain dengan setup serupa, kemungkinan besar kodenya lebih panjang.

Kode Lengkap main.go

Biar kamu nggak bingung nyusun puzzle-nya, ini kode lengkap yang udah gue gabung:

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

type Book struct {
	ID     string `json:"id"`
	Title  string `json:"title" binding:"required"`
	Author string `json:"author" binding:"required"`
	Year   int    `json:"year" binding:"required"`
}

var books = []Book{
	{ID: "1", Title: "Laskar Pelangi", Author: "Andrea Hirata", Year: 2005},
	{ID: "2", Title: "Bumi Manusia", Author: "Pramoedya Ananta Toer", Year: 1980},
}

func main() {
	r := gin.Default()

	// GET semua buku
	r.GET("/books", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"status": "success",
			"data":   books,
		})
	})

	// GET buku by ID
	r.GET("/books/:id", func(c *gin.Context) {
		id := c.Param("id")
		for _, book := range books {
			if book.ID == id {
				c.JSON(http.StatusOK, gin.H{
					"status": "success",
					"data":   book,
				})
				return
			}
		}
		c.JSON(http.StatusNotFound, gin.H{
			"status":  "error",
			"message": "Buku tidak ditemukan",
		})
	})

	// POST tambah buku
	r.POST("/books", func(c *gin.Context) {
		var newBook Book
		if err := c.ShouldBindJSON(&newBook); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"status":  "error",
				"message": err.Error(),
			})
			return
		}
		newBook.ID = fmt.Sprintf("%d", len(books)+1)
		books = append(books, newBook)
		c.JSON(http.StatusCreated, gin.H{
			"status":  "success",
			"message": "Buku berhasil ditambahkan",
			"data":    newBook,
		})
	})

	// PUT update buku
	r.PUT("/books/:id", func(c *gin.Context) {
		id := c.Param("id")
		var updatedBook Book
		if err := c.ShouldBindJSON(&updatedBook); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"status":  "error",
				"message": err.Error(),
			})
			return
		}
		for i, book := range books {
			if book.ID == id {
				books[i].Title = updatedBook.Title
				books[i].Author = updatedBook.Author
				books[i].Year = updatedBook.Year
				c.JSON(http.StatusOK, gin.H{
					"status":  "success",
					"message": "Buku berhasil diperbarui",
					"data":    books[i],
				})
				return
			}
		}
		c.JSON(http.StatusNotFound, gin.H{
			"status":  "error",
			"message": "Buku tidak ditemukan",
		})
	})

	// DELETE hapus buku
	r.DELETE("/books/:id", func(c *gin.Context) {
		id := c.Param("id")
		for i, book := range books {
			if book.ID == id {
				books = append(books[:i], books[i+1:]...)
				c.JSON(http.StatusOK, gin.H{
					"status":  "success",
					"message": "Buku berhasil dihapus",
				})
				return
			}
		}
		c.JSON(http.StatusNotFound, gin.H{
			"status":  "error",
			"message": "Buku tidak ditemukan",
		})
	})

	r.Run(":8080")
}

Coba jalankan lagi dengan go run main.go, lalu test satu-satu pakai Postman atau curl:

# Ambil semua buku
curl http://localhost:8080/books

# Ambil buku dengan ID 1
curl http://localhost:8080/books/1

# Tambah buku baru
curl -X POST http://localhost:8080/books \
  -H "Content-Type: application/json" \
  -d '{"title":"Sang Pemimpi","author":"Andrea Hirata","year":2006}'

# Update buku
curl -X PUT http://localhost:8080/books/1 \
  -H "Content-Type: application/json" \
  -d '{"title":"Laskar Pelangi (Edisi Revisi)","author":"Andrea Hirata","year":2008}'

# Hapus buku
curl -X DELETE http://localhost:8080/books/2

Semua endpoint berjalan? Mantap. Kamu baru aja bikin full REST API dengan Go dan Gin!

Tips Tambahan: Middleware dan Struktur Project

Setelah basic CRUD jalan, biasanya gue mulai mikirin beberapa hal lagi. Ini yang sering gue pelajari setelah bikin API pertama kali dan semoga bisa ngebantu kamu juga.

CORS Middleware

Kalau API kamu mau dipanggil dari frontend (React, Vue, dll), kamu butuh CORS. Gin udah punya package terpisah buat ini:

go get -u github.com/gin-contrib/cors

Pakainya gampang banget:

import "github.com/gin-contrib/cors"

func main() {
	r := gin.Default()

	r.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"http://localhost:3000"},
		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE"},
		AllowHeaders:     []string{"Content-Type", "Authorization"},
		AllowCredentials: true,
	}))

	// ... route lainnya
}

Custom Logger dan Recovery

gin.Default() udah include logger dan recovery. Tapi kalau kamu mau custom, bisa pakai gin.New() dan tambahin middleware sendiri:

r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())

gin.Recovery() ini penting — dia nangkap panic supaya server nggak crash total kalau ada error di handler.

Penyimpanan Buku dengan Slice: Peringatan Penting

Satu hal yang perlu kamu catat: di contoh ini kita pakai slice biasa buat nyimpan data. Artinya, setiap server restart, data hilang. Ini cuma untuk pembelajaran.

Di production, kamu perlu database. Beberapa opsi yang gue suka:

  • PostgreSQL — pakai library pgx atau gorm
  • MySQL — pakai go-sql-driver/mysql atau gorm
  • MongoDB — pakai official mongo-driver

GORM adalah ORM paling populer di ekosistem Go. Kalau kamu familiar dengan Sequelize (Node.js) atau Eloquent (Laravel), konsepnya mirip.

Struktur Project yang Rapi

Saat project mulai besar, jangan taruh semua di main.go. Gue biasanya pakai struktur kayak gini:

go-gimggcmrmnaoooooi-i..ndudanmstetdp.ourblbeblcigdmoosseo/olooowrlkkkase_._r.rcgregsoo/onuttreosl.lgeor.go

Pisahkan model, controller, dan route ke file masing-masing. Go mendukung package system yang bikin ini sangat natural. Kamu tinggal import antar package.

Menggunakan net/http Standar Library

Satu hal yang bikin Go unik: Gin sebenarnya implementasi dari http.Handler. Artinya, kamu bisa combine Gin dengan standard library Go. Misalnya buat health check sederhana:

r.GET("/health", func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"status": "healthy",
		"time":   time.Now().Format(time.RFC3339),
	})
})

Ini fleksibilitas yang jarang ada di framework bahasa lain.

Mengapa Setelah Coba Go, Gue Susah Balik?

Jujur, setelah bikin beberapa API pakai Go + Gin, ada sensasi yang susah dijelasin. Mungkin karena Go itu… to the point. Nggak ada yang namanya bikin abstraksi berlapis-lapis cuma buat handle satu endpoint. Kodenya verbose tapi jelas — kamu baca baris per baris dan langsung ngerti apa yang terjadi.

Deployment juga jadi lebih gampang. Kompilasi Go menghasilkan satu binary file. Nggak perlu install runtime, nggak perlu setup virtual environment. Tinggal copy file binary ke server, jalankan, selesai. Buat Docker? Cukup pakai base image alpine dan ukuran container-nya bisa di bawah 20MB.

Performa? Gue pernah test API sederhana Gin di VPS 1GB RAM, dia bisa handle 10.000+ concurrent request tanpa masalah. Kalau kamu bandingin dengan Express.js di spec yang sama, selisihnya signifikan.

Tapi bukan berarti Go sempurna ya. Ada trade-off. Error handling di Go itu… repetitif banget. Kamu akan sering nemu pola if err != nil. Beberapa orang benci ini, tapi gue pribadi malah suka karena bikin setiap error point eksplisit. Nggak ada yang tersembunyi.


Kalau kamu udah sampai di sini dan berhasil bikin REST API pertama kamu dengan Go + Gin, selamat! Ini langkah awal yang bagus. Dari sini, kamu bisa explore lebih jauh: tambahin autentikasi JWT, connect ke database, bikin middleware custom, atau bahkan deploy ke cloud.

Gue harap artikel ini ngebantu. Kalau ada pertanyaan, saran, atau mau ngobrol lebih lanjut soal Go, backend development, atau apapun — jangan ragu buat reach out.

Hubungi gue di: [email protected]


FAQ (Pertanyaan yang Sering Ditanyakan)

1. Gin vs Echo vs Chi — mana yang lebih bagus buat pemula?

Semuanya bagus, tapi gue rekomendasiin Gin buat pemula karena dokumentasinya lengkap, komunitasnya besar, dan banyak contoh di internet. Echo mirip-mirip, sedikit lebih “clean” syntax-nya. Chi lebih ringan dan idiomatic Go. Tapi di awal, pilih satu dan fokus belajar. Skill-nya transferable kok antar framework.

2. Apakah Go cocok untuk project kecil atau cuma buat enterprise?

Go cocok untuk semua skala. Binary-nya kecil, startup time-nya cepet, dan memory usage-nya rendah. Bahkan buat microservice kecil atau API side project, Go tetap masuk akal. Nggak ada aturan yang bilang “Go cuma buat perusahaan besar.”

3. Bagaimana cara deploy REST API Go ke production?

Paling gampang: compile binary di mesin target (go build -o api main.go), lalu jalankan. Atau pakai Docker dengan multi-stage build. Banyak juga yang deploy Go apps ke Railway, Fly.io, atau VPS biasa. Proses build-nya cepet dan hasilnya single executable — bikin deployment jadi straightforward banget.

4. Apakah data di contoh ini hilang kalau server restart?

Ya, karena kita pakai slice in-memory. Untuk persistent storage, kamu perlu integrasi database. Mulai dari SQLite buat development, lalu migrasi ke PostgreSQL atau MySQL buat production. Pakai GORM sebagai ORM bisa mempermudah proses ini.


Ditulis berdasarkan pengalaman pribadi membangun backend service dengan Go. Semoga bermanfaat dan happy coding!