sqlc vs gorm
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.