Membuat Restful API menggunakan Golang dan Gin

Membuat Restful API menggunakan Golang dan Gin

Sekarang banyak sekali startup dan perusahaan-perusahaan yang menggunakan Go sebagai bahasa pemrograman primary nya. Disamping dari mudah untuk dipelajari, Go juga sangatlah ramah dalam penggunaan memory dan performance yang sangat cepat.

Dalam artikel ini, kita akan coba membuat sebuah restul api menggunakan Golang dan Gin untuk Http nya.

Apa yang akan kita buat ?

Kita akan membuat sebuah restul api yang akan mengcover beberapa fitur dibawah ini :

  • Proses registrasi menggunakan Username dan Password

  • Login menggunakan username dan password

  • Membuat sebuah diary / catatan baru

  • Mengambil semua catatan

Prerequisites

Untuk bisa dan lancar mengikuti artikel ini, kamu harus sudah menginstall beberapa software yang ada dibawah ini :

Get Started

Untuk memulai project ini, dalam gopath kalian buatlah sebuah folder dengan nama diary_api dan arahkan terminal ke dalam folder tersebut menggunakan perintah :

mkdir diary_api
cd diary_api

Berikutnya, initialize sebuah go module dalam project itu dengan menggunakan perintah berikut.

go mod init diary_api

perintah ini membuat sebuah file go.mod dalam root folder project diary_api dimana dalam file itu, semua dependency yang akan digunakan dalam project ini akan disimpan disana.

Install Dependensi / Library Project

Seperti yang telah kita tahu, bahwa diartikel ini kita akan menggunakan Gin Framework, jalankan perintah ini untuk menambahkan beberapa library atau dependensi.

go get \
    github.com/gin-gonic/gin \
    github.com/golang-jwt/jwt/v4 \
    github.com/joho/godotenv \
    golang.org/x/crypto \
    gorm.io/driver/postgres \
    gorm.io/gorm

Setelah proses mendownload library selesai, kamu akan mempunyai Gin dan beberapa library kedalam aplikasi kamu seperti :

Go Cryptography : menyediakan tambahan Go Cryptography

GoDotEnv : menolong kamu untuk mengatur environtment variables

GORM : ini adalah sebuah ORM (Object Relational Mapper) khusus Golang, dalam library ini juga dialect (driver) untuk Postgres DB sudah diinstall untuk mengijinkan membuat sebuah koneksi ke PostgrsSQL

JWT-Go : Sebuah implementasi dari JSON Web Token dari Golang

Menyiapkan Database dan Environtment Variable

Sebelum menulis kode, buatlah sebuah Postgresql database bernama diary_app dengan menggunakan perintah psql berikut :

createdb -h <DB_HOSTNAME> -p <DB_PORT> -U <DB_USER> diary_app --password

Ketika terminal meminta inputan, masukan password yang digunakan oleh DB_USER yang kamu gunakan. Atau jika tidak kamu bisa menggunakan SQL Editor seperti DBeaver untuk memudahkan melakukan manajemen database.

Next, masuk ke folder project diary_api dan buat sebuah file dengan nama .env dan masukan beberapa variable

# Database credentials
DB_HOST="<<DB_HOST>>"
DB_USER="<<DB_USER>>"
DB_PASSWORD="<<DB_PASSWORD>>"
DB_NAME="diary_app"
DB_PORT="<<DB_PORT>>"

# Authentication credentials
TOKEN_TTL="2000"
JWT_PRIVATE_KEY="s3cr3t"

Untuk part Database Crendentials, kamu masukan username, password, port beserta host dari posgresql localhost. dan untuk part Authentication Credentials akan digunakan untuk menggenerate a sebuah JWT.

Menyiapkan Model

Next, kamu akan membuat dua buah model untuk aplikasi ini, yaitu User dan Entry didalam folder model. Dalam folder tersebut buatlah sebuah file dengan nama user.go dan tambahkan kode dibawah ini.

package model

import "gorm.io/gorm"

type User struct {
    gorm.Model
    Username string `gorm:"size:255;not null;unique" json:"username"`
    Password string `gorm:"size:255;not null;" json:"-"`
    Entries  []Entry
}

struct diatas disusun oleh Gorm Model Struct, sebuah string untuk username, dan string lainnya untuk password, beserta sebuah slice dari item Entry. Dalam hal ini, kamu akan menspesifikasikan relational one-to-many antara User struct dan Entry struct.

Perhatian untuk JSON binding pada field Password menggunakan "-". Hal ini untuk memastikan password tidak dikembalikan sebagai sebuah response json.

Next, masih dalam folder model, buatlah sebuah file dengan nama entry.go dan masukan potongan kode berikut ini.

package model

import "gorm.io/gorm"

type Entry struct {
    gorm.Model
    Content string `gorm:"type:text" json:"content"`
    UserID  uint
}

Dalam struct Entry kita mendefinisikan sebuah string yang berisi konten dan UserId untuk menyimpan user id dari si pembuat entry ini.

Lalu, hal selanjutnya adalah membuat sebuah function untuk bisa melakukan koneksi ke database dengan membuat sebuah folder baru dengan nama database dan buat sebuah file dengan nama database.go lalu tambahkan beberapa potongan kode dibawah ini.

package database

import (
    "fmt"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "os"
)

var Database *gorm.DB

func Connect() {
    var err error
    host := os.Getenv("DB_HOST")
    username := os.Getenv("DB_USER")
    password := os.Getenv("DB_PASSWORD")
    databaseName := os.Getenv("DB_NAME")
    port := os.Getenv("DB_PORT")

    dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Africa/Lagos", host, username, password, databaseName, port)
    Database, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})

    if err != nil {
        panic(err)
    } else {
        fmt.Println("Successfully connected to the database")
    }
}

function Connect() mengambil environtment variables yang sebelum nya didefinisikan pada file .env untuk membuat setup koneksi ke database dan membuka koneksi menggunakan Gorm Postgresql driver.

Lalu buat sebuah file main.go yang digunakan sebagai entry point dari aplikasi dan tambahkan beberapa potongan kode di bawah ini.

package main

import (
    "diary_api/database"
    "diary_api/model"
    "github.com/joho/godotenv"
    "log"
)

func main() {
    loadEnv()
    loadDatabase()
}

func loadDatabase() {
    database.Connect()
    database.Database.AutoMigrate(&model.User{})
    database.Database.AutoMigrate(&model.Entry{})
}

func loadEnv() {
    err := godotenv.Load(".env")
    if err != nil {
        log.Fatal("Error loading .env file")
    }
}

Didalam fungsi main.go, environtment variable dimuat dan koneksi ke database langsung dibuatkan. Jika koneksi ke database berhasil dan sukses, maka fungsi AutoMigrate() akan dipanggil dan akan membuat table user dan entry (Jika tabel tersebut belumada).

Next, jalankan aplikasi nya menggunakan perintah dibawah ini.

go run main.go

setelah aplikasi berjalan, kamu akan melihat sebuah pesan seperti berikut

Successfully connected to the database

jika kamu melihat ke database, kamu akan menemukan dua buah table seperti berikut

diary_app=#  \dt
            List of relations
 Schema |  Name   | Type  | Owner
--------+---------+-------+-------
 public | entries | table | user
 public | users   | table | user
(2 rows)

Mengimplementasikan Fitur Registrasi dan Login

Sebelum kita membuat sebuah endpoint API, terlebih dahulu buatlah sebuah file authenticationInput.go didalam folder model dan tambahkan potongan kode berikut ini.

package model

type AuthenticationInput struct {
    Username string `json:"username" binding:"required"`
    Password string `json:"password" binding:"required"`
}

Registrasi

Tambahkan fungsi Save() dan BeforeSave() ke dalam file User struct yang berada didalam model/user.go

func (user *User) Save() (*User, error) {
    err := database.Database.Create(&user).Error
    if err != nil {
        return &User{}, err
    }
    return user, nil
}

func (user *User) BeforeSave(*gorm.DB) error {
    passwordHash, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
    if err != nil {
        return err
    }
    user.Password = string(passwordHash)
    user.Username = html.EscapeString(strings.TrimSpace(user.Username))
    return nil
}

Fungsi Save() digunakan untuk menyimpan data user baru ke dalam database dan mengembalikan object user dan error.

Fungsi BeforeSave() digunakan untuk mengenkripsi password sebelum disimpan ke dalam database.

Jangan lupa untuk mengimport beberapa dependensi dalam file model.user.go

import (
    "diary_api/database"
    "golang.org/x/crypto/bcrypt"
    "gorm.io/gorm"
    "html"
    "strings"
)

Next, buatlah sebuah folder dengan nama controller dan tambahkan sebuah file dengan nama authentication.go lalu tambahkan potongan fungsi Register() dibawah ini.

package controller

import (
    "diary_api/model"
    "github.com/gin-gonic/gin"
    "net/http"
)

func Register(context *gin.Context) {
    var input model.AuthenticationInput

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

    user := model.User{
        Username: input.Username,
        Password: input.Password,
    }

    savedUser, err := user.Save()

    if err != nil {
        context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    context.JSON(http.StatusCreated, gin.H{"user": savedUser})
}

Fungsi Register() ini menerima request dari client dan memvalidasi json, membuat sebuah user baru dan mengambalikan nya dalam bentuk json response.

Login

Sebelum membuat fungsi Login() buatlah fungsi ValidatePassword() didalam User struct di file model/user.go yang akan kita gunakan untuk membandingkan password yang diberi oleh user dengan password yang disimpan didalam database ketika login.

func (user *User) ValidatePassword(password string) error {
    return bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
}

func FindUserByUsername(username string) (User, error) {
    var user User
    err := database.Database.Where("username=?", username).Find(&user).Error
    if err != nil {
        return User{}, err
    }
    return user, nil
}

menggunakan bcrypt library sebuah hash akan digenerate dari password yang diberikan oleh user kemudian akan dibandingan dengan hash dari password user yang disimpan didatabase. Jika error muncul, maka itu artinya password nya tidak cocok.

Sedangkan fungsi FindUserByUsername() digunakan untuk mengambil data user dari database berdasarkan inputan username.

Next, buatlah sebuah folder dengan nama helper. Didalam folder itu buat sebuah file dengan nama jwt.go dan tambahkan beberapa potongan kode sebagai berikut.

package helper

import (
    "diary_api/model"
    "github.com/golang-jwt/jwt/v4"
    "os"
    "strconv"
    "time"
)

var privateKey = []byte(os.Getenv("JWT_PRIVATE_KEY"))

func GenerateJWT(user model.User) (string, error) {
    tokenTTL, _ := strconv.Atoi(os.Getenv("TOKEN_TTL"))
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
        "id":  user.ID,
        "iat": time.Now().Unix(),
        "eat": time.Now().Add(time.Second * time.Duration(tokenTTL)).Unix(),
    })
    return token.SignedString(privateKey)
}

fungsi ini mengambil sebuah user model dan menggenerate JWT yang berisi user_id, waktu dimana token dikeluarkan (iat) dan waktu expire dari token (eat). Fungsi ini juga mengambil sebuah environtment variable JWT_PRIVATE_KEY yang disimpan pada file .env. Fungsi ini akan mengembalikan nilai string yang berisi token.

Next, didalam folder controller/authentication.go buat sebuah fungsi Login()

func Login(context *gin.Context) {
    var input model.AuthenticationInput

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

    user, err := model.FindUserByUsername(input.Username)

    if err != nil {
        context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    err = user.ValidatePassword(input.Password)

    if err != nil {
        context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    jwt, err := helper.GenerateJWT(user)
    if err != nil {
        context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    context.JSON(http.StatusOK, gin.H{"jwt": jwt})
}

jangan lupa untuk mengimport beberapa dependencies dibawah ini

import (
    "diary_api/helper"
    "diary_api/model"
    "net/http"

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

lalu, update file main.go dengan beberapa potongan kode dibawah ini

package main

import (
    "diary_api/controller"
    "diary_api/database"
    "diary_api/model"
    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/joho/godotenv"
    "log"
)

func main() {
    loadEnv()
    loadDatabase()
    serveApplication()
}

func loadEnv() {
    err := godotenv.Load(".env")
    if err != nil {
        log.Fatal("Error loading .env file")
    }
}

func loadDatabase() {
    database.Connect()
    database.Database.AutoMigrate(&model.User{})
    database.Database.AutoMigrate(&model.Entry{})
}

func serveApplication() {
    router := gin.Default()

    publicRoutes := router.Group("/auth")
    publicRoutes.POST("/register", controller.Register)
    publicRoutes.POST("/login", controller.Login)

    router.Run(":8000")
    fmt.Println("Server running on port 8000")
}

Dengan penambahan routes, sekarang kamu mempunyai dua endpoint yaitu /auth/register dan /auth/login . Untuk mengetest fungsi tersebut stop aplikasi dengan menekan tombol Ctrl + C secara bersamaan dan restart kembali dengan

go run main.go

Gunakan curl untuk mengetest bagian dari aplikasi. Ganti placeholder dibawah dengan username atau password kalian.

curl -i -H "Content-Type: application/json" \
    -X POST \
    -d '{"username":"<<USERNAME>>", "password":"<<PASSWORD>>"}' \
    http://localhost:8000/auth/register

kamu akan melihat output seperti dibawah ini

HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Tue, 11 Oct 2022 03:37:08 GMT
Content-Length: 166

{"user":{"ID":1,"CreatedAt":"2022-10-11T04:37:08.041626+01:00","UpdatedAt":"2022-10-11T04:37:08.041626+01:00","DeletedAt":null,"username":"yemiwebby","Entries":null}}%

untuk login masih menggunakan curl, ganti placeholder username dengan password yang sudah kalian gunakan pada saat register

curl -i -H "Content-Type: application/json" \
    -X POST \
    -d '{"username":"<<USERNAME>>", "password":"<<PASSWORD>>"}' \
    http://localhost:8000/auth/login

dan kamu akan melihat output dibawah ini berupa jwt token

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 11 Oct 2022 03:43:08 GMT
Content-Length: 147

{"jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlYXQiOjE2NjU0NjE3ODgsImlhdCI6MTY2NTQ1OTc4OCwiaWQiOjF9.4agGQACwKSZpPCpHeXnoqXfc3WZqYtE8b0SFcoH40uo"}%

Mengimplementasikan middleware untuk menghandle request ke endpoints yang private

endpoint selanjutnya yang kalian akan akses harus menggunakan user yang sudah melakukan authentifikasi terlebih dahulu, dengan kata lain untuk mengakses endpoints akan membutuhkan sebuah bearer token dalam request header nya. Jika tidak maka sebuah pesan error akan muncul.

Untuk melakukan ini, kamu akan mengimplementasikan sebuah middleware. Middleware ini akan mengintercept request dan memastikan valid bearer token ada dalam sebuah request sebelum dieksekusi oleh endpoint target.

Sebelum membua sebuah middleware, kamu perlu menambahkan beberapa fungsi tambahan dalam file helper/jwt.go.

func ValidateJWT(context *gin.Context) error {
    token, err := getToken(context)
    if err != nil {
        return err
    }
    _, ok := token.Claims.(jwt.MapClaims)
    if ok && token.Valid {
        return nil
    }
    return errors.New("invalid token provided")
}

func CurrentUser(context *gin.Context) (model.User, error) {
    err := ValidateJWT(context)
    if err != nil {
        return model.User{}, err
    }
    token, _ := getToken(context)
    claims, _ := token.Claims.(jwt.MapClaims)
    userId := uint(claims["id"].(float64))

    user, err := model.FindUserById(userId)
    if err != nil {
        return model.User{}, err
    }
    return user, nil
}

func getToken(context *gin.Context) (*jwt.Token, error) {
    tokenString := getTokenFromRequest(context)
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
        }

        return privateKey, nil
    })
    return token, err
}

func getTokenFromRequest(context *gin.Context) string {
    bearerToken := context.Request.Header.Get("Authorization")
    splitToken := strings.Split(bearerToken, " ")
    if len(splitToken) == 2 {
        return splitToken[1]
    }
    return ""
}

dan pastikan juga kalian sudah mengimport beberapa statement dibawah ini

import (
    "diary_api/model"
    "errors"
    "fmt"
    "os"
    "strconv"
    "strings"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v4"
)

fungsi getTokenFromRequest() mengambil token bearer dari sebuah request header. Bearer token datang dengan format bearer <JWT>. Karena itu hasil dari proses pengambilan bearer token akan di split dan akan diambil JWT nya saja.

lalu fungsi getToken() digunakan untuk mengembalikan token string yang berisi JWT menggunakan private key yang sudah didefinisikan di file .env.

Menggunakan kedua fungsi tersebut, fungsi ValidateJWT() digunakan untuk memastikan request yang datang verisi valid token dalam sebuah request header. Fungsi ini digunakan oleh middleware untuk memastikan hanya user yang sah yang bisa mengakses endpoint-endpoint yang perlu authentifikasi.

Fungsi CurrentUser() digunakan untuk mendapatkan user yang berdasarkan JWT yang diberikan berdasarkan id kemudian mengambil data user dari database. Itu akan membuat referensi ke fungsi FindUserById() yang sebelumnya belum dibuat. Untuk itu buat sebuah fungsi baru didalam file model/user.go dan tambahkan fungsi berikut.

func FindUserById(id uint) (User, error) {
    var user User
    err := database.Database.Preload("Entries").Where("ID=?", id).Find(&user).Error
    if err != nil {
        return User{}, err
    }
    return user, nil
}

Bisa kita lihat pada potongan kode diatas, entries diasosiasikan dengan user oleh karena itu selain data user, data entry juga akan ikut terambil.

Untuk membuat middleware buatlah sebuah folder dengan nama middleware didalam root project dan tambahkan sebuah file jwtAuth.go dan tambahkan beberapa potongan kode berikut.

package middleware

import (
    "diary_api/helper"
    "github.com/gin-gonic/gin"
    "net/http"
)

func JWTAuthMiddleware() gin.HandlerFunc {
    return func(context *gin.Context) {
        err := helper.ValidateJWT(context)
        if err != nil {
            context.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
            context.Abort()
            return
        }
        context.Next()
    }
}

Fungsi JWTAuthMiddleware() mengembalikan sebuah Gin HandlerFunc() . Fungsi ini mengharapkan sebuah context untuk memvalidasi JWT didalam header. Jika tidak valid, maka sebuah response error akan dikembalikan. Jika tidak, fungsi Next() didalam context dipanggil dan akan meneruskan ke controller target.

Mengimplementasikan sebuah fitur untuk menambahkan entry baru

Sebelum kita membuat route baru ke dalam controller untuk endpoint ini, tambahkan sebuah fungsi dalam Entry struct yang mengijinkan kamu untuk menyimpan data entry baru ke dalam database. tambahkan beberapa potongan kode dibawah ini didalam file model/entry.go.

func (entry *Entry) Save() (*Entry, error) {
    err := database.Database.Create(&entry).Error
    if err != nil {
        return &Entry{}, err
    }
    return entry, nil
}

jangan lupa untuk menambahkan import statement nya

import (
    "diary_api/database"

    "gorm.io/gorm"
)

Next, dalam folder controller, buatlah sebuah file baru dengan nama entry.go dan tambahkan potongan kode dibawah ini

package controller

import (
    "diary_api/helper"
    "diary_api/model"
    "github.com/gin-gonic/gin"
    "net/http"
)

func AddEntry(context *gin.Context) {
    var input model.Entry
    if err := context.ShouldBindJSON(&input); err != nil {
        context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    user, err := helper.CurrentUser(context)

    if err != nil {
        context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    input.UserID = user.ID

    savedEntry, err := input.Save()

    if err != nil {
        context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    context.JSON(http.StatusCreated, gin.H{"data": savedEntry})
}

Fungsi AddEntry() membungkus payload dari request body dan mentransform ke dalam struct Entry setelah proses validasi authentifikasi selesai. Setelah itu menyimpan data entry baru ke dalam database dan mengembalikan json response dan entry yang tadi sudah disimpan.

Mengimplementasikan fitur Get All Entries

Didalam file controller/entry.go tambahkan potongan kode dibawah ini

func GetAllEntries(context *gin.Context) {
    user, err := helper.CurrentUser(context)

    if err != nil {
        context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    context.JSON(http.StatusOK, gin.H{"data": user.Entries})
}

Fungsi ini mengambil currentUser dan mengembalikan response json data entries

Menambahkan Protected Routes

Dalam file main.go update fungsi serveApplication tambahkan beberapa potongan kode berikut

func serveApplication() {
    router := gin.Default()

    publicRoutes := router.Group("/auth")
    publicRoutes.POST("/register", controller.Register)
    publicRoutes.POST("/login", controller.Login)

    protectedRoutes := router.Group("/api")
    protectedRoutes.Use(middleware.JWTAuthMiddleware())
    protectedRoutes.POST("/entry", controller.AddEntry)
    protectedRoutes.GET("/entry", controller.GetAllEntries)

    router.Run(":8000")
    fmt.Println("Server running on port 8000")
}

Dan seperti biasa, jangan lupa untuk mengimport statement yang diperlukan

import (
    "diary_api/controller"
    "diary_api/database"
    "diary_api/middleware"
    "diary_api/model"
    "fmt"
    "log"

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

Sekarang API sudah siap untuk dikonsumsi

Jalankan aplikasi menggunakan go run main.go dan tambahkan beberapa entry baru

Membuat sebuah Entry Baru

Untuk membuat sebuah entry, ganti placeholder <<JWT>> dibawah ini dengan JWT yang sudah kita dapatkan pada curl diatas.

curl -d '{"content":"A sample content"}' \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer <<JWT>>" \
    -X POST http://localhost:8000/api/entry

Kamu akan menerima output seperti dibawah ini.

{"data":{"ID":1,"CreatedAt":"2022-10-12T16:43:56.169216+01:00","UpdatedAt":"2022-10-12T16:43:56.169216+01:00","DeletedAt":null,"content":"A sample content","UserID":1}}%

Mengumpulkan List Entries

Gunalan curl untuk mendapatkan list dari entri yang sudah diinput.

curl -H "Content-Type: application/json" -H "Authorization: Bearer <<JWT>>" -X GET http://localhost:8000/api/entry

Kamu akan menerima output seperti dibawah ini

{"data":[{"ID":1,"CreatedAt":"2022-10-12T16:43:56.169216+01:00","UpdatedAt":"2022-10-12T16:43:56.169216+01:00","DeletedAt":null,"content":"A sample content","UserID":1}]}%

Kesimpulan

Dalam artikel ini kamu sudah belajar bagaimana membuat rest api menggunakan gin. Gin membuat proses routing dan proses marshalling payload ke dalam bentuk struct menjadi sangat mudah. Disamping itu, Gin juga memberikan kemampuan untuk membuat routing group untuk membedakan beberapa endpoint sesuai context domain nya.

Did you find this article valuable?

Support Teten Nugraha by becoming a sponsor. Any amount is appreciated!

ย