← Back to Cecep Azhar
Cecep Azhar #golang#gorm#rest-api#database#postgresql

Building REST API dengan Go dan GORM Step by Step

Cecep Saeful Azhar • 16 Oct 2026
Building REST API dengan Go dan GORM Step by Step
Tutorial lengkap membangun REST API dengan Go dan GORM. CRUD operations, relationships, dan best practices.

Go dan GORM: Kombinasi yang Produktif

GORM adalah ORM paling populer di ekosistem Go. Dengan fitur yang lengkap auto migration, associations, hooks, dan caching GORM mempercepat development tanpa mengorbankan fleksibilitas. Saya sudah pakai GORM di lebih dari 10 proyek production.

Setup Database Connection

package database

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

var DB *gorm.DB

func Connect() error {
    dsn := fmt.Sprintf(
        "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable",
        os.Getenv("DB_HOST"), os.Getenv("DB_USER"),
        os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"),
        os.Getenv("DB_PORT"),
    )
    var err error
    DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })
    if err != nil {
        return fmt.Errorf("failed to connect database: %w", err)
    }
    return nil
}

Define Models

package models

import "gorm.io/gorm"

type User struct {
    gorm.Model
    Name     string    `json:"name" gorm:"size:100;not null"`
    Email    string    `json:"email" gorm:"uniqueIndex;size:255;not null"`
    Password string    `json:"-" gorm:"not null"`
    Projects []Project `json:"projects" gorm:"foreignKey:UserID"`
}

type Project struct {
    gorm.Model
    Name        string `json:"name" gorm:"size:200;not null"`
    Description string `json:"description" gorm:"type:text"`
    UserID      uint   `json:"user_id" gorm:"index;not null"`
    Tasks       []Task `json:"tasks" gorm:"foreignKey:ProjectID"`
}

type Task struct {
    gorm.Model
    Title     string `json:"title" gorm:"size:200;not null"`
    Done      bool   `json:"done" gorm:"default:false"`
    ProjectID uint   `json:"project_id" gorm:"index;not null"`
}

CRUD Handlers

func ListProjects(c *fiber.Ctx) error {
    var projects []models.Project
    page, _ := strconv.Atoi(c.Query("page", "1"))
    limit, _ := strconv.Atoi(c.Query("limit", "10"))
    offset := (page - 1) * limit
    database.DB.Preload("Tasks").Offset(offset).Limit(limit).Find(&projects)
    return c.JSON(projects)
}

func CreateProject(c *fiber.Ctx) error {
    project := new(models.Project)
    if err := c.BodyParser(project); err != nil {
        return c.Status(400).JSON(fiber.Map{"error": "Invalid request body"})
    }
    database.DB.Create(&project)
    return c.Status(201).JSON(project)
}

func GetProject(c *fiber.Ctx) error {
    id := c.Params("id")
    var project models.Project
    if err := database.DB.Preload("Tasks").First(&project, id).Error; err != nil {
        return c.Status(404).JSON(fiber.Map{"error": "Project not found"})
    }
    return c.JSON(project)
}

Best Practices yang Wajib Diterapkan

GORM memang powerful, tapi ingat: ORM bukan silver bullet. Untuk query yang kompleks, jangan takut pakai raw SQL.

GORM Hooks untuk Business Logic

GORM hooks memungkinkan Anda menjalankan kode sebelum atau sesudah operasi database. Ini powerful untuk implementasi business logic yang terkait dengan data lifecycle:

// Hook: before create user, hash password
func (u *User) BeforeCreate(tx *gorm.DB) error {
    if u.Password != "" {
        hashed, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
        if err != nil {
            return err
        }
        u.Password = string(hashed)
    }
    return nil
}

// Hook: after create project, send welcome notification
func (p *Project) AfterCreate(tx *gorm.DB) error {
    return notifyUser(p.UserID, "Project "+p.Name+" created!")
}

Hooks membuat code lebih clean karena business logic terkait data lifecycle terpusat di model, bukan tersebar di berbagai handler. Tapi hati-hati: jangan terlalu banyak pakai hooks karena bisa membuat flow tidak predictable.

Performance Optimization untuk Query Kompleks

Untuk query yang sangat kompleks dan butuh performa optimal, jangan ragu pakai raw SQL. GORM support hybrid approach:

func GetRevenueReport(db *gorm.DB, startDate, endDate time.Time) ([]RevenueRow, error) {
    var results []RevenueRow
    err := db.Raw(`
        SELECT DATE_TRUNC('month', created_at) as month,
               COUNT(*) as total_subscriptions,
               SUM(amount) as total_revenue
        FROM subscriptions
        WHERE created_at BETWEEN ? AND ? AND status = ?
        GROUP BY DATE_TRUNC('month', created_at)
        ORDER BY month
    `, startDate, endDate, "active").Scan(&results).Error
    return results, err
}
← More articles from Cecep Azhar Central Content Hub Home →