Setelah mempelajari cara kerja Echo dan membuat REST API sederhana, selanjutnya adalah mencoba menghubungkan dengan database menggunakan Gorm. Selain itu pada artikel ini juga saya akan mempelajari memisahkan fungsi-fungsi dalam file terpisah sehingga kode menjadi lebih clean dan mudah dibaca.
Saya masih baru, baru sekali dalam menggunakan bahasa pemrograman Go , sehingga mungkin Anda akan melihat beberapa kekurangan dalam penulisan kode atau penjelasan, silakan masukkan di komentar.
Persiapan
Seperti biasa, persiapan yang dilakukan adalah membuat direktori, membuat go.mod dan melakukan instalasi package yang dibutuhkan. Berikut ada perintah-perintahnya yang dituliskan dalam terminal.
mkdir golang-echo
cd golang-echo
go mod init golang-echo
Instalasi Library
go get -u github.com/spf13/viper
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
go get -u github.com/labstack/echo/
Struktur Direktori Project
Dalam project ini saya membagi dalam beberapa direktori. Pembagian ini sebenarnya tidak mengacu ke arsitektur manapun, cuma seinget saya aja pas nulis kodenya, jadi mohon maaf apabila dirasa kurang sesuai. Nama-nama dan penjelasan direktorinya adalah sebagai berikut
- config : Lokasi file konfigurasi aplikasi
- controllers : Lokasi handler aplikasi
- db : lokasi konfigurasi database
- entities : entitas model table
- router : route aplikasi
Config
konfigurasi aplikasi menggunakan library viper. Buat file dengan nama config.go pada direktiri config, kodenya adalah sebagai berikut
package config
import (
"log"
"github.com/spf13/viper"
)
type Config struct {
DB_USERNAME string
DB_PASSWORD string
DB_PORT string
DB_HOST string
DB_NAME string
}
func GetConfig() Config {
configuration := Config{}
viper.AddConfigPath("config")
viper.SetConfigName("config")
viper.SetConfigType("json")
err := viper.ReadInConfig()
if err != nil {
log.Println(err)
}
err = viper.Unmarshal(&configuration)
if err != nil {
log.Println(err)
}
return configuration
}
Setelah membuat konfigurasinya, sekarang membuat isi konfigurasi pada file config.json yang juga terletak di direktori config. Isinya seperti ini, sesuaikan dengan nilai yang ada pada komputer Anda.
{
"DB_USERNAME": "root",
"DB_PASSWORD": "secret",
"DB_PORT": "3306",
"DB_HOST": "127.0.0.1",
"DB_NAME": "go_crud_api"
}
Entity
Selanjutnya adalah membuat struct untuk model product (nama table yang digunakan), isinya seperti berikut
package entities
type Product struct {
ID uint64 `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Description string `json:"description"`
}
Database
Untuk melakukan koneksi database menggunakan gorm, saya meletakkan fungsinya dalam direktori db, file yang digunakan adalah db.go yang isinya sebagai berikut
package db
import (
"fmt"
"golang-echo/config"
"golang-echo/entities"
"log"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var db *gorm.DB
var err error
func Connect() {
appConfig := config.GetConfig()
connection_string := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", appConfig.DB_USERNAME,
appConfig.DB_PASSWORD,
appConfig.DB_HOST,
appConfig.DB_PORT,
appConfig.DB_NAME)
db, err = gorm.Open(mysql.Open(connection_string), &gorm.Config{})
if err != nil {
log.Fatal(err)
panic("Cannot connect to database")
}
log.Println("Connecting to database")
Migrate()
}
func Migrate() {
db.AutoMigrate(&entities.Product{})
log.Println("Database migration completed")
}
func DbManager() *gorm.DB {
return db
}
isinya dari db.go sebenarnya ada 3 fungsi yaitu connect untuk menghubungkan ke database, Migrate untuk melakukan migration dan DbManager untuk mengembalikan variable db yang bertipe gorm.DB yang digunakan dalam operasi database nantinya.
Controllers
Controllers berfungsi menangani request dari router, setiap route pada router akan ditangani pada masing-masing fungsi di controller sesuai handler yang ditentukan. Buat file dengan nama product_controllers.go pada direktori controllers, isi kodenya sebagai berikut, saya tuliskan per fungsi dan penjelasannya
Get Product
func GetProduct(ctx echo.Context) error {
db := db.DbManager()
products := []entities.Product{}
results := make(map[string]interface{})
err := db.Find(&products).Error
if err != nil {
results["status"] = "error"
results["data"] = nil
results["message"] = err
} else {
results["status"] = "success"
results["message"] = "Success get all products"
results["data"] = products
}
return ctx.JSON(http.StatusOK, results)
}
Fungsi ini digunakan untuk mengambil seluruh data dari table products. Pada baris 3 kita membuat slice dengan tipe data dari struct entitas product. baris 5 adalah perintah gorm untuk mendapatkan seluruh baris dari table product. Selanjutnya adalah melakukan format data yang dihasilkan untuk disajikan dalam format JSON.
Get Product By Id
func GetProductById(ctx echo.Context) error {
product_id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
if err != nil {
log.Fatal(err)
}
db := db.DbManager()
product := entities.Product{}
err = db.First(&product, product_id).Error
results := make(map[string]interface{})
if err != nil {
results["status"] = "error"
results["data"] = nil
results["message"] = "Product not found"
} else {
results["status"] = "success"
results["message"] = fmt.Sprintf("Success get product with id %s", ctx.Param("id"))
results["data"] = product
}
return ctx.JSON(http.StatusOK, results)
}
Fungsi ini adalah mengambil sebuah product dengan ID tertentu. Langkahnya adalah menerima variable ID dari parameter url dan melakukan konversi dari string ke integer unsigned. Dari variable inilah kemudian dipanggil fungsi db.First untuk mendapatkan baris yang sesuai. Selanjutnya ditampilkan dalam format JSON
Create Product
func CreateProduct(ctx echo.Context) error {
db := db.DbManager()
product := new(entities.Product)
ctx.Bind(product)
result := db.Create(&product)
results := make(map[string]interface{})
if result.Error != nil {
results["status"] = "error"
results["data"] = nil
results["message"] = result.Error
} else {
results["status"] = "sucess"
results["data"] = product
results["message"] = "Product save succesfully"
}
return ctx.JSON(http.StatusOK, results)
}
Fungsi ini digunakan untuk menyimpan data ke dalam tabel product. Langkahnya adalah membuat variabel dari struct product, melakukan maping dari request yang diterima dengan fungsi Bind, dan langkah selanjutnya adalah menyimpan ke table dengan fungsi Create.
Update Product
func UpdateProduct(ctx echo.Context) error {
product_id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
if err != nil {
log.Fatal(err)
}
db := db.DbManager()
product := entities.Product{}
err = db.First(&product, product_id).Error
results := make(map[string]interface{})
ctx.Bind(&product)
if err != nil {
results["status"] = "error"
results["data"] = nil
results["message"] = "Product not found"
} else {
db.Updates(product)
results["status"] = "success"
results["message"] = fmt.Sprintf("Success update product with id %s", ctx.Param("id"))
results["data"] = product
}
return ctx.JSON(http.StatusOK, results)
}
Fungsi update product ini akan menjalankan 2 query, yaitu First untuk mencari product yang akan diedit. Jika product tidak ditemukan, maka akan dikembalikan dengan pesan error. Jika product ditemukan maka akan diupdate dengan fungsi gorm Updates.
Delete Product
func DeleteProduct(ctx echo.Context) error {
product_id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
if err != nil {
log.Fatal(err)
}
db := db.DbManager()
product := entities.Product{}
err = db.First(&product, product_id).Error
results := make(map[string]interface{})
if err != nil {
results["status"] = "error"
results["data"] = nil
results["message"] = "Product not found"
} else {
db.Delete(&product)
results["status"] = "success"
results["message"] = fmt.Sprintf("Success delete product with id %s", ctx.Param("id"))
results["data"] = product
}
return ctx.JSON(http.StatusOK, results)
}
Hampir sama dengan function update, tetapi pada funcion deleteProduct ini, setelah data ditemukan maka data tersebut akan dihapus dengan fungsi db.Delete
Router
Buat sebuah file dengan nama product_router.go dan simpan pada direktori router. File ini nantinya akan berfungsi sebagai pengatur routing aplikasi, mengatur path yang ditentukan akan mengeksekusi function controller yang disebutkan. Isi dari file ini adalah sebagai berikut
package router
func ProductHandler(e *echo.Echo) {
e.GET("/", controllers.Hello)
e.GET("/products", controllers.GetProduct)
e.POST("/products", controllers.CreateProduct)
e.GET("/products/:id", controllers.GetProductById)
e.PUT("/products/:id", controllers.UpdateProduct)
e.DELETE("/products/:id", controllers.DeleteProduct)
}
Menyatukan Kode
Saatnya menyatukan kode-kode yang sudah dibuat dalam sebuah main.go. Buka main.go dan isikan sebagai berikut
package main
import (
"golang-echo/db"
"golang-echo/router"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
router.ProductHandler(e)
db.Connect()
e.Logger.Fatal(e.Start(":8080"))
}
Salah satu manfaat memisahkan file-file sesuai fungsinya adalah kode dalam fungsi main menjadi lebih bersih dan rapi. Kita tinggal memanggil fungsi-fungsi yang sudah dibuat tadi
Ujicoba
Langsung saja menjalankan program dengan perintah
go run main.go
Lakukan ujicoba dengan membuat request ke aplikasi, bisa menggunakan CURL atau postman dan sejenisnya
#mendapatkan semua rows product
curl -X GET 'http://localhost:8080/products'
#mendapatkan sebuah data dengan id tertentu
curl -X GET 'http://localhost:8080/products/1'
#menyimpan sebuah product dalam table
curl -X POST \
'http://localhost:8080/products' \
--header 'Content-Type: application/json' \
--data-raw '{
"name":"Barang Baru lagi",
"price":78450,
"description":"Deskripsi barang yang digunakaan"
}'
#melakukan update product
curl -X PUT \
'http://localhost:8080/products' \
--header 'Content-Type: application/json' \
--data-raw '{
"name":"Barang Baru Diupdate lagi",
"price":878787,
"description":"Deskripsi barang yang digunakan"
}'
#menghapus product
curl -X DELETE 'http://localhost:8080/products/2'
Demikian artikel membuat REST API menggunakan Golang Echo dan Gorm. Seluruh code dalam artikel ini dapat dilihat di laman https://github.com/dwijonarko/golang-echo-gorm/tree/main . Semoga bermanfaat