In modern web development, interfacing with relational database storage efficiently without cluttering application code with boilerplate SQL is a constant balance. In the Go ecosystem, GORM is the premier Object Relational Mapping (ORM) library, offering developer productivity, type-safe query composition, automated migrations, and connection pooling.
Combining Go Fiber with GORM yields an exceptionally fast, developer-friendly backend stack. In this article, we cover setting up database persistence, managing connection pools, and implementing the repository pattern for separation of concerns.
1. Initializing Database Connection & Connection Pools
Never initialize database connections inside individual HTTP request handlers. Instead, create a shared, thread-safe connection instance with tuned connection pooling parameters:
package database
import (
"fmt"
"log"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var DB *gorm.DB
func Connect(dsn string) (*gorm.DB, error) {
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
return nil, fmt.Errorf("failed to connect to database: %w", err)
}
// Configure connection pool under the hood
sqlDB, err := db.DB()
if err != nil {
return nil, err
}
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
log.Println("Database connection established with optimized pooling.")
DB = db
return db, nil
}
2. Model Definitions & Automated Migrations
GORM supports model struct tags for primary keys, indexes, nullability, and unique constraints:
package models
import (
"time"
"gorm.io/gorm"
)
type LanguageRecord struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"type:varchar(100);uniqueIndex;not null" json:"name"`
Year int `gorm:"not null" json:"year"`
Creator string `gorm:"type:varchar(100)" json:"creator"`
Description string `gorm:"type:text" json:"description"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
func Migrate(db *gorm.DB) error {
return db.AutoMigrate(&LanguageRecord{})
}
3. The Repository Pattern
Separating database queries from HTTP handlers makes unit testing straightforward and isolates ORM logic:
package repository
import (
"context"
"gorm.io/gorm"
"yourproject/internal/models"
)
type LanguageRepository interface {
Create(ctx context.Context, item *models.LanguageRecord) error
FindAll(ctx context.Context) ([]models.LanguageRecord, error)
FindByID(ctx context.Context, id uint) (*models.LanguageRecord, error)
}
type languageRepo struct {
db *gorm.DB
}
func NewLanguageRepository(db *gorm.DB) LanguageRepository {
return &languageRepo{db: db}
}
func (r *languageRepo) Create(ctx context.Context, item *models.LanguageRecord) error {
return r.db.WithContext(ctx).Create(item).Error
}
func (r *languageRepo) FindAll(ctx context.Context) ([]models.LanguageRecord, error) {
var records []models.LanguageRecord
err := r.db.WithContext(ctx).Find(&records).Error
return records, err
}
4. Connecting Handlers to the Repository
Inject the repository into your Fiber controller:
package handlers
import (
"strconv"
"github.com/gofiber/fiber/v2"
"yourproject/internal/models"
"yourproject/internal/repository"
)
type LanguageController struct {
repo repository.LanguageRepository
}
func NewLanguageController(repo repository.LanguageRepository) *LanguageController {
return &LanguageController{repo: repo}
}
func (ctrl *LanguageController) GetAll(c *fiber.Ctx) error {
records, err := ctrl.repo.FindAll(c.UserContext())
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to query database records",
})
}
return c.JSON(records)
}
5. Production Considerations
- Always pass Context: Use
c.UserContext()from Fiber to support database query cancellation when client requests disconnect. - Tune Connection Pool: Adjust
MaxOpenConnsbased on database CPU/RAM rather than using default unbounded limits. - Soft Deletes: Use
gorm.DeletedAtto protect against accidental data loss while keeping query performance optimal with index coverage.