At some point when starting a Go web app, you’ll probably need to decide which ORM (or no ORM) to use. Let’s assume you are using Postgres. Out of all the libraries offered in the Go community, I’ve narrowed it down to sqlc and gorm.

There are a few considerations to think about before jumping into either. Personally I would choose sqlc with the pgx driver.

Benefits of gorm

gorm makes querying your database easy. You don’t need to worry about SQL syntax. You simply write the same server language, Go, to query the DB.

gorm allows you to define associations in your model struct. So if you define your Go structs to have db relations, gorm automatically handles it for you.

// `User` belongs to `Company`, `CompanyID` is the foreign key
type User struct {
  gorm.Model
  Name      string
  CompanyID int
  Company   company.Company
}

// from company pkg
type Company struct {
  ID   int
  Name string
}

gorm allows you to follow a domain driven file directory, which typically looks like this:

- user
  - model.go
  - handler.go
- company
  - model.go
  - handler.go

Another benefit is, if your Go struct has a gorm.DeletedAt attribute, Gorm automatically supports soft delete for you. You may find that useful if there’re data you would want to retain.

Benefits of sqlc

sqlc on the other hand, is a different approach to querying your db from your web application. If you believe writing raw SQL is the purest way to interact with your postgres database, then you might like this option. In the event you need to make a production query where psql is your only tool, the context switch you need to make is minimal.

If you did not already check out sqlc, it automatically generates your models and query functions from your db schema and the query.sql that you write. This is different from gorm where you define the models manually.

What’s even better is sqlc translates postgres data type to Go data type effortlessly.

Let’s take this migration as an example:

-- 01_create_authors.sql
CREATE TABLE authors (
  id   BIGSERIAL PRIMARY KEY,
  name text      NOT NULL,
  bio  text,
  age smallint,
  email varchar(255)
);

Run sqlc generate and it will generate:

// Code generated by sqlc. DO NOT EDIT.
// versions:
//   sqlc v1.19.0

package db

import (
  "github.com/jackc/pgx/v5/pgtype"
)

type Author struct {
  ID    int64 // non-nullable
  Name  string // non-nullable
  Bio   pgtype.Text // nullable
  Age   pgtype.Int2 // nullable
  Email pgtype.Text // nullable
}

Notice the data types generated in Author struct. The biggest benefit of using sqlc is type safety. The generated struct clearly tells me which attribute is nullable and which isn’t. If it’s non-nullable, it will auto map to Go native data type. Otherwise, to a pgtype.

Using gorm requires extra effort to ensure the data type accuracy between your Go struct and postgres table column types. Sqlc ensures the same state between your database schema and your Go code.

More often than not, in a typical Go struct using gorm, you do not know if the corresponding pg column is nullable or not. Why is this important?

If you’re going to create an Author record using gorm,

package main

import (
  "fmt"
  "os"

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

func main() {
  // ... omitted db setup for brevity
  author := Author{Email: "[email protected]" }
  result := db.Create(&author)

  fmt.Printf("%+v\n", result)
  // &{Config:0x140001d2510 Error:<nil> RowsAffected:1 Statement:0x140002a2380 clone:0}
}

Notice gorm does not throw a violate null constraint error on Name column. This is because it sets Name as an empty string by default.

In fact, for all the fields that are not filled in, gorm will set default values automtically. Contrast this to the manually inserted record ID 6.

triciatzy=# select * from authors;
 id | name  |  bio   |  age   |      email
----+-------+--------+--------+------------------
  1 | john  |        |      0 | john@example.com
  2 |       |        |      0 | john@example.com
  6 | susan | <null> | <null> | <null>
(3 rows)

Unless you can somehow ensure data consistency for your database, e.g. no manual SQL inserts, only through gorm, or if you’re inserting record with SQL, ensure that null values have a default value.

Either way that seems terribly hard to maintain. Would not recommend.

One caveat to note if you’re choosing sqlc is, it does not support domain driven file directory concept. It generates all your models in one file. It will most likely reside in your db folder. You can however, define which output package you want the generated files to be in.

---
version: '2'
sql:
- engine: postgresql
  queries: query.sql
  schema: db/migration
  gen:
    go:
      package: db
      sql_package: pgx/v5
      out: db

Side note - For both options, you’ll need a separate golang migration tool. I will not recommend using gorm’s auto-migrate feature unless you’re building a small hobby application. Even then, it’s much safer to version your database schema.