Jämförelse av Go-ORM:er för PostgreSQL: GORM vs Ent vs Bun vs sqlc
En praktisk, kodintensiv titt på ORM:er i Go
De mest framträdande ORM:erna för Go är GORM, Ent, Bun och sqlc. Här är en liten jämförelse av dem med exempel på CRUD-operationer i ren Go.

TL;DR
- GORM: Funktionssam och bekväm; enklast att “bara släppa”, men har mer körtidsoverhead.
- Ent: Schema-as-code med genererade, typsäkra API:er; utmärkt för stora kodbasar och refaktorer.
- Bun: Lättviktig, SQL-first query builder/ORM; snabb med utmärkta Postgres-funktioner, explicit by design.
- sqlc (inte en ORM per se, men ändå): Skriv SQL, få typsäkert Go; bäst rå prestanda och kontroll, ingen magi vid körtid.
Urvalskriterier och snabb jämförelse
Mina kriterier är:
- Prestanda: Latens/genomströmning, undvikbar overhead, batchoperationer.
- Utvecklarupplevelse (DX): Inlärningskurva, typsäkerhet, felsäkerhet, kodgenereringsfriktion.
- Ekosystem: Dokumentation, exempel, aktivitet, integrationer (migrationer, tracing).
- Funktionsset: Relationer, eager loading, migrationer, hooks, escape-hatch för raw SQL.
| Verktyg | Paradigm | Typsäkerhet | Relationer | Migrationer | Ergonomi för raw SQL | Typiskt användningsfall |
|---|---|---|---|---|---|---|
| GORM | Active Record-stilad ORM | Medium (vid körtid) | Ja (tags, Preload/Joins) | Auto-migrate (opt-in) | db.Raw(...) |
Snabb leverans, rika funktioner, konventionella CRUD-appar |
| Ent | Schema → kodgenerering → flödes-API | Hög (vid kompilering) | First-class (edges) | Genererad SQL (separat steg) | entsql, anpassad SQL |
Stora kodbasar, team som refaktorerar mycket, strikt typning |
| Bun | SQL-first query builder/ORM | Medium–Hög | Explicit (Relation) |
Separat migrate-paket | Naturlig (builder + raw) | Prestandabevätna tjänster, Postgres-funktioner |
| sqlc | SQL → kodgenererade funktioner (inte en ORM) | Hög (vid kompilering) | Via SQL-joiner | Externt verktyg (t.ex., golang-migrate) | Det är SQL | Maximal kontroll & hastighet; DBA-vänliga team |
CRUD med exempel
Uppstart (PostgreSQL)
Använd pgx eller verktygets inbyggda PG-drivrutin. Exempel DSN:
export DATABASE_URL='postgres://user:pass@localhost:5432/app?sslmode=disable'
Importeringar (gemensamma för alla ORM:er)
I början av varje fil med go-kod exempel lägg till:
import (
"context"
"os"
)
Vi modellerar en enkel users-tabell:
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
GORM
Init
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type User struct {
ID int64 `gorm:"primaryKey"`
Name string
Email string `gorm:"uniqueIndex"`
}
func newGorm() (*gorm.DB, error) {
dsn := os.Getenv("DATABASE_URL")
return gorm.Open(postgres.Open(dsn), &gorm.Config{})
}
// Auto-migrate (valfritt; var försiktig i produktion)
func migrate(db *gorm.DB) error { return db.AutoMigrate(&User{}) }
CRUD
func gormCRUD(ctx context.Context, db *gorm.DB) error {
// Skapa
u := User{Name: "Alice", Email: "alice@example.com"}
if err := db.WithContext(ctx).Create(&u).Error; err != nil { return err }
// Läs
var got User
if err := db.WithContext(ctx).First(&got, u.ID).Error; err != nil { return err }
// Uppdatera
if err := db.WithContext(ctx).Model(&got).
Update("email", "alice+1@example.com").Error; err != nil { return err }
// Radera
if err := db.WithContext(ctx).Delete(&User{}, got.ID).Error; err != nil { return err }
return nil
}
Noter
- Relationer via struct-tagg +
Preload/Joins. - Transaktionshjälp:
db.Transaction(func(tx *gorm.DB) error { ... }).
Ent
Schemadefinition (i ent/schema/user.go):
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
type User struct {
ent.Schema
}
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int64("id").Unique().Immutable(),
field.String("name"),
field.String("email").Unique(),
}
}
Generera kod
go run entgo.io/ent/cmd/ent generate ./ent/schema
Init
import (
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
_ "github.com/jackc/pgx/v5/stdlib"
"your/module/ent"
)
func newEnt() (*ent.Client, error) {
dsn := os.Getenv("DATABASE_URL")
drv, err := sql.Open(dialect.Postgres, dsn)
if err != nil { return nil, err }
return ent.NewClient(ent.Driver(drv)), nil
}
CRUD
func entCRUD(ctx context.Context, client *ent.Client) error {
// Skapa
u, err := client.User.Create().
SetName("Alice").
SetEmail("alice@example.com").
Save(ctx)
if err != nil { return err }
// Läs
got, err := client.User.Get(ctx, u.ID)
if err != nil { return err }
// Uppdatera
if _, err := client.User.UpdateOneID(got.ID).
SetEmail("alice+1@example.com").
Save(ctx); err != nil { return err }
// Radera
if err := client.User.DeleteOneID(got.ID).Exec(ctx); err != nil { return err }
return nil
}
Noter
- Stark typning end-to-end; edges för relationer.
- Genererade migrationer eller använd ditt valda migrationsverktyg.
Bun
Init
import (
"database/sql"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
_ "github.com/jackc/pgx/v5/stdlib"
)
type User struct {
bun.BaseModel `bun:"table:users"`
ID int64 `bun:",pk,autoincrement"`
Name string `bun:",notnull"`
Email string `bun:",unique,notnull"`
}
func newBun() (*bun.DB, error) {
dsn := os.Getenv("DATABASE_URL")
sqldb, err := sql.Open("pgx", dsn)
if err != nil { return nil, err }
return bun.NewDB(sqldb, pgdialect.New()), nil
}
CRUD
func bunCRUD(ctx context.Context, db *bun.DB) error {
// Skapa
u := &User{Name: "Alice", Email: "alice@example.com"}
if _, err := db.NewInsert().Model(u).Exec(ctx); err != nil { return err }
// Läs
var got User
if err := db.NewSelect().Model(&got).
Where("id = ?", u.ID).
Scan(ctx); err != nil { return err }
// Uppdatera
if _, err := db.NewUpdate().Model(&got).
Set("email = ?", "alice+1@example.com").
WherePK().
Exec(ctx); err != nil { return err }
// Radera
if _, err := db.NewDelete().Model(&got).WherePK().Exec(ctx); err != nil { return err }
return nil
}
Noter
- Explicita joiner/eager loading med
.Relation("..."). - Separat
bun/migrate-paket för migrationer.
sqlc
sqlc är tekniskt sett inte en ORM. Du skriver SQL; den genererar typsäkra Go-metoder.
sqlc.yaml
version: "2"
sql:
- engine: postgresql
queries: db/queries
schema: db/migrations
gen:
go:
package: db
out: internal/db
sql_package: "database/sql" # eller "github.com/jackc/pgx/v5"
Frågor (db/queries/users.sql)
-- name: CreateUser :one
INSERT INTO users (name, email)
VALUES ($1, $2)
RETURNING id, name, email;
-- name: GetUser :one
SELECT id, name, email FROM users WHERE id = $1;
-- name: UpdateUserEmail :one
UPDATE users SET email = $2 WHERE id = $1
RETURNING id, name, email;
-- name: DeleteUser :exec
DELETE FROM users WHERE id = $1;
Generera
sqlc generate
Användning
import (
"database/sql"
_ "github.com/jackc/pgx/v5/stdlib"
"your/module/internal/db"
)
func sqlcCRUD(ctx context.Context) error {
dsn := os.Getenv("DATABASE_URL")
sqldb, err := sql.Open("pgx", dsn)
if err != nil { return err }
q := db.New(sqldb)
// Skapa
u, err := q.CreateUser(ctx, db.CreateUserParams{
Name: "Alice", Email: "alice@example.com",
})
if err != nil { return err }
// Läs
got, err := q.GetUser(ctx, u.ID)
if err != nil { return err }
// Uppdatera
up, err := q.UpdateUserEmail(ctx, db.UpdateUserEmailParams{
ID: got.ID, Email: "alice+1@example.com",
})
if err != nil { return err }
// Radera
if err := q.DeleteUser(ctx, up.ID); err != nil { return err }
return nil
}
Noter
- Ta med dina egna migrationer (t.ex.,
golang-migrate). - För dynamiska frågor: skriv flera SQL-varianten eller kombinera med en liten builder.
Prestandanoter
- GORM: Bekväm men lägger till reflektion/abstraktionsoverhead. Passar bra för typisk CRUD; se upp efter N+1-frågor (föredra
Joinseller selektivPreload). - Ent: Genererad kod undviker reflektion; bra för komplexa schema. Ofta snabbare än tunga, körtidsmagiska ORM:er.
- Bun: Tunna över
database/sql; snabb, explicit, bra för batchoperationer och stora resultatset. - sqlc: Essentiellt sett raw SQL-prestanda med typsäkerhet vid kompilering.
Allmänna tips
- Använd pgx som drivrutin (v5) och context överallt.
- Föredra batchning (
COPY, multi-rowINSERT) för hög genomströmning. - Profiler SQL:
EXPLAIN ANALYZE, index, covering index, undvik onödiga roundtrips. - Återanvänd connectioner; justera poolstorlek baserat på arbetsbelastning.
Utvecklarupplevelse och ekosystem
- GORM: Största community, många exempel/plugins; brantare inlärningskurva för avancerade mönster.
- Ent: Bra dokumentation; kodgenereringssteget är den huvudsakliga mentala modellförändringen; super refactor-vänlig.
- Bun: Läsbara, förutsägbara frågor; mindre men aktivt community; utmärkta Postgres-niceties.
- sqlc: Minimala körtidsberoenden; integreras bra med migrationsverktyg och CI; superb för team som är bekväma med SQL.
Funktionshöjdpunkter
- Relationer & eager loading: Alla hanterar relationer; GORM (tags +
Preload/Joins), Ent (edges +.With...()), Bun (Relation(...)), sqlc (du skriver joinerna). - Migrationer: GORM (auto-migrate; var försiktig i produktion), Ent (genererad/diff SQL), Bun (
bun/migrate), sqlc (externa verktyg). - Hooks/Extensibilitet: GORM (callbacks/plugins), Ent (hooks/middleware + template/kodgenerering), Bun (middleware-liknande query hooks, enkel raw SQL), sqlc (komposera i din app-lager).
- JSON/Arrays (Postgres): Bun och GORM har fina hjälpmedel; Ent/sqlc hanterar via anpassade typer eller SQL.
När du väljer vad
- Välj GORM om du vill ha maximal bekvämlighet, rika funktioner och snabb prototypering för konventionella CRUD-tjänster.
- Välj Ent om du värdesätter typsäkerhet vid kompilering, explicita schema och långsiktig underhållbarhet i större team.
- Välj Bun om du vill ha prestanda och explicita SQL-formade frågor med ORM-bekvämligheter där det hjälper.
- Välj sqlc om du (och ditt team) föredrar ren SQL med typsäkra Go-bindningar och noll körtidsoverhead. sqlc är också en naturlig passform för lässidan av en CQRS-arkitektur i Go, där frågor formades för anropare snarare än domänentiteter och explicit SQL ger dig full kontroll över projectionen.
Om du fortfarande väger detta ORM-val mot integrationsstil och tjänstegransser, den här app-arkitekturöversikten hjälper till att placera beslutet i ett bredare produktionskontext.
Minimal docker-compose.yml för lokal PostgreSQL
version: "3.8"
services:
db:
image: postgres:16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
ports: ["5432:5432"]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d app"]
interval: 5s
timeout: 3s
retries: 5