📚 Series: Belajar Golang dari Nol sampai Deploy
← Part 6 Part 7: Database & GORM Part 8: Deploy →

Install Dependencies

# GORM core
go get gorm.io/gorm

# Driver PostgreSQL
go get gorm.io/driver/postgres

# Untuk load .env file
go get github.com/joho/godotenv

Setup .env

Buat file .env di root project:

DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=secret
DB_NAME=todo_db

Jangan lupa tambahkan .env ke .gitignore!

Koneksi Database

Buat file database/db.go:

package database

import (
    "fmt"
    "log"
    "os"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

var DB *gorm.DB

func Connect() {
    dsn := fmt.Sprintf(
        "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable TimeZone=Asia/Jakarta",
        os.Getenv("DB_HOST"),
        os.Getenv("DB_PORT"),
        os.Getenv("DB_USER"),
        os.Getenv("DB_PASSWORD"),
        os.Getenv("DB_NAME"),
    )

    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })
    if err != nil {
        log.Fatal("Gagal konek database:", err)
    }

    DB = db
    log.Println("Database terhubung!")
}

Model dengan GORM

Update models/task.go:

package models

import "gorm.io/gorm"

type Task struct {
    gorm.Model                       // embed: ID, CreatedAt, UpdatedAt, DeletedAt (soft delete!)
    Title     string `json:"title" gorm:"not null;size:200"`
    Completed bool   `json:"completed" gorm:"default:false"`
    UserID    uint   `json:"user_id"`
}

// gorm.Model sudah include:
// ID        uint
// CreatedAt time.Time
// UpdatedAt time.Time
// DeletedAt gorm.DeletedAt (soft delete)

Auto Migration

// Di main.go
func main() {
    godotenv.Load()
    database.Connect()

    // Auto migrate — buat tabel jika belum ada, update kolom yang berubah
    database.DB.AutoMigrate(&models.Task{})

    // ... setup router
}

CRUD dengan GORM

Update handlers/task.go:

package handlers

import (
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/username/todo-api/database"
    "github.com/username/todo-api/models"
)

func GetTasks(c *gin.Context) {
    var tasks []models.Task
    result := database.DB.Find(&tasks)
    if result.Error != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Gagal ambil data"})
        return
    }
    c.JSON(http.StatusOK, gin.H{"data": tasks, "total": len(tasks)})
}

func GetTask(c *gin.Context) {
    var task models.Task
    if err := database.DB.First(&task, c.Param("id")).Error; err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "Task tidak ditemukan"})
        return
    }
    c.JSON(http.StatusOK, gin.H{"data": task})
}

func CreateTask(c *gin.Context) {
    var input struct {
        Title string `json:"title" binding:"required,min=1,max=200"`
    }
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    task := models.Task{Title: input.Title}
    if err := database.DB.Create(&task).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Gagal buat task"})
        return
    }
    c.JSON(http.StatusCreated, gin.H{"data": task})
}

func UpdateTask(c *gin.Context) {
    var task models.Task
    if err := database.DB.First(&task, c.Param("id")).Error; err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "Task tidak ditemukan"})
        return
    }

    var input struct {
        Title     *string `json:"title"`
        Completed *bool   `json:"completed"`
    }
    c.ShouldBindJSON(&input)

    if input.Title != nil {
        task.Title = *input.Title
    }
    if input.Completed != nil {
        task.Completed = *input.Completed
    }

    database.DB.Save(&task)
    c.JSON(http.StatusOK, gin.H{"data": task})
}

func DeleteTask(c *gin.Context) {
    var task models.Task
    if err := database.DB.First(&task, c.Param("id")).Error; err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "Task tidak ditemukan"})
        return
    }
    database.DB.Delete(&task) // soft delete!
    c.JSON(http.StatusOK, gin.H{"message": "Task berhasil dihapus"})
}

Query Lanjutan GORM

// Filter
database.DB.Where("completed = ?", false).Find(&tasks)

// Order
database.DB.Order("created_at desc").Find(&tasks)

// Limit & Offset (pagination)
page := 1
pageSize := 10
database.DB.Offset((page-1)*pageSize).Limit(pageSize).Find(&tasks)

// Count
var total int64
database.DB.Model(&models.Task{}).Count(&total)

// Search
keyword := "belajar"
database.DB.Where("title ILIKE ?", "%"+keyword+"%").Find(&tasks)

// Transaksi
database.DB.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&task1).Error; err != nil {
        return err // auto rollback
    }
    if err := tx.Create(&task2).Error; err != nil {
        return err
    }
    return nil // commit
})

Checklist Progress

← Part 6 Part 8: Deploy →