Rest API dengan Golang Echo dan GORM

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

direktori

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

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.