Configuring and Using Different Database Dialects with GORM: A Complete Guide

You can configure multiple database dialects in a GORM-based Go application by exposing a dialect field in your configuration, importing the necessary driver packages, and using a switch statement in your connection factory to build dialect-specific DSN strings.

The manakuro/golang-clean-architecture repository demonstrates Clean Architecture principles in Go, but its database layer currently hard-codes MySQL support. When configuring and using different database dialects with GORM, you need to modify only the infrastructure layer while keeping domain logic and use cases completely untouched.

Understanding the Current Database Implementation

The project creates database connections in pkg/infrastructure/datastore/db.go through the NewDB() function. Currently, this function hard-codes the MySQL dialect and driver:

func NewDB() *gorm.DB {
    DBMS := "mysql"
    mySqlConfig := &mysql.Config{ … }
    db, err := gorm.Open(DBMS, mySqlConfig.FormatDSN())
    // …
}

The configuration values (user, password, host) are loaded from pkg/config/config.go, which uses Viper to parse YAML files. However, the dialect itself is not exposed as a configurable parameter, requiring code changes to switch databases.

Extending the Configuration Structure

To support multiple dialects, first add a Dialect field to the Database configuration struct in pkg/config/config.go:

type config struct {
    Database struct {
        Dialect  string // "mysql", "postgres", "sqlite3", "mssql"
        User     string
        Password string
        Net      string
        Addr     string
        DBName   string
        AllowNativePasswords bool
        Params   struct {
            ParseTime string
        }
    }
    // … Server configuration
}

Update your config/config.yml to include the dialect selection:

database:
  dialect: postgres          # Options: mysql, postgres, sqlite3, mssql

  user:     "myuser"
  password: "secret"
  net:      "tcp"
  addr:     "localhost:5432"
  dbname:   "mydb"
  allowNativePasswords: true
  params:
    parseTime: "True"

Importing Multiple GORM Drivers

GORM uses side-effect imports to register database drivers. Update pkg/infrastructure/datastore/db.go to import all dialects you intend to support:

import (
    "log"
    "fmt"

    "golang-clean-architecture/pkg/config"

    // Driver side-effect registrations
    _ "github.com/jinzhu/gorm/dialects/mysql"
    _ "github.com/jinzhu/gorm/dialects/postgres"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
    _ "github.com/jinzhu/gorm/dialects/mssql"

    "github.com/jinzhu/gorm"
)

These blank imports invoke init() functions that register each driver with GORM's dialect registry, making them available at runtime.

Implementing Dialect-Aware Connection Logic

Replace the hard-coded NewDB() implementation with a switch statement that constructs the appropriate DSN for each supported database:

func NewDB() *gorm.DB {
    dialect := config.C.Database.Dialect
    if dialect == "" {
        dialect = "mysql"
    }

    var dsn string
    switch dialect {
    case "mysql":
        myCfg := &mysql.Config{
            User:                 config.C.Database.User,
            Passwd:               config.C.Database.Password,
            Net:                  config.C.Database.Net,
            Addr:                 config.C.Database.Addr,
            DBName:               config.C.Database.DBName,
            AllowNativePasswords: config.C.Database.AllowNativePasswords,
            Params: map[string]string{
                "parseTime": config.C.Database.Params.ParseTime,
            },
        }
        dsn = myCfg.FormatDSN()

    case "postgres":
        // Using lib/pq style DSN
        dsn = fmt.Sprintf(
            "host=%s port=5432 user=%s password=%s dbname=%s sslmode=disable",
            config.C.Database.Addr,
            config.C.Database.User,
            config.C.Database.Password,
            config.C.Database.DBName,
        )

    case "sqlite3":
        // SQLite uses file path as DSN
        dsn = config.C.Database.DBName

    case "mssql":
        // SQL Server connection string
        dsn = fmt.Sprintf(
            "sqlserver://%s:%s@%s?database=%s",
            config.C.Database.User,
            config.C.Database.Password,
            config.C.Database.Addr,
            config.C.Database.DBName,
        )

    default:
        log.Fatalf("unsupported DB dialect: %s", dialect)
    }

    db, err := gorm.Open(dialect, dsn)
    if err != nil {
        log.Fatalln(err)
    }
    return db
}

Each dialect requires its own DSN format: MySQL uses mysql.Config, PostgreSQL uses key-value pairs, SQLite expects a file path, and SQL Server uses URL-style connection strings.

Maintaining Clean Architecture Boundaries

The architectural flow remains unchanged when adding multi-dialect support:


config → datastore.NewDB → GORM DB → repositories → use-cases → controllers

The *gorm.DB instance created by NewDB() is injected into the registry in cmd/app/main.go, then passed to repositories via pkg/registry/registry.go. Because the database handle is an interface, repositories and use cases remain agnostic to the underlying dialect. Only NewDB() and the configuration structure require modification, preserving the dependency rule that keeps business logic independent of infrastructure concerns.

Summary

  • Expose the dialect in pkg/config/config.go to make database selection configurable without code changes.
  • Import all required drivers in pkg/infrastructure/datastore/db.go using blank imports to register them with GORM.
  • Construct dialect-specific DSNs using a switch statement in NewDB() to handle MySQL, PostgreSQL, SQLite, and SQL Server connection formats.
  • Preserve Clean Architecture by keeping the *gorm.DB injection unchanged; no modifications are needed in repositories, use cases, or controllers.

Frequently Asked Questions

How do I add support for an additional GORM dialect not shown in the examples?

Add a new blank import for the driver (such as _ "github.com/jinzhu/gorm/dialects/foundation"), then add a new case in the NewDB() switch statement with the correct DSN construction logic. Ensure your configuration struct includes any dialect-specific parameters.

Can I switch between PostgreSQL and MySQL without rebuilding the application?

Yes. By externalizing the dialect into config/config.yml, you can change the dialect value and restart the application. The NewDB() factory reads the configuration at runtime, so no recompilation is necessary unless you need to add a completely new database type.

Which GORM dialects are supported by this approach?

GORM v1 supports MySQL, PostgreSQL, SQLite3, and SQL Server (MSSQL) through official dialect packages. The same pattern works for community-maintained dialects (such as ClickHouse or TiDB) by importing their respective driver packages and constructing their required DSN formats.

Should I use build tags instead of runtime configuration for dialect selection?

Runtime configuration via the method described above offers more flexibility for deployments, while build tags reduce binary size by compiling only the needed driver. For most applications, the runtime approach with the switch statement provides the best balance of flexibility and maintainability, adding only minimal overhead from unused driver imports.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →