Comparaison des ORMs Go pour PostgreSQL : GORM vs Ent vs Bun vs sqlc
Un regard pratique et axé sur le code sur les ORM en Go
Les ORMs pour GO les plus connus sont GORM, Ent, Bun et sqlc. Voici une petite comparaison de ceux-ci avec des exemples d’opérations CRUD en GO pur.

En résumé
- GORM : complet et pratique ; le plus simple pour « sortir » rapidement, mais avec un surcoût d’exécution plus important.
- Ent : schéma en code avec des APIs générées et typées ; excellent pour les grandes bases de code et les refactorings.
- Bun : constructeur de requêtes SQL/ORM léger ; rapide avec d’excellentes fonctionnalités Postgres, explicite par conception.
- sqlc (pas un ORM à proprement parler, mais néanmoins) : écrivez du SQL, obtenez du Go typé ; meilleures performances et contrôle brut, sans magie à l’exécution.
Critères de sélection et comparaison rapide
Mes critères sont :
- Performance : latence/débit, surcoûts évitables, opérations par lots.
- Expérience développeur (DX) : courbe d’apprentissage, sécurité des types, débogabilité, friction de la génération de code.
- Écosystème : documentation, exemples, activité, intégrations (migrations, traçabilité).
- Ensemble de fonctionnalités : relations, chargement eager (préchargement), migrations, hooks, échappatoires vers le SQL brut.
| Outil | Paradigme | Sécurité des types | Relations | Migrations | Ergonomie du SQL brut | Cas d’utilisation typique |
|---|---|---|---|---|---|---|
| GORM | ORM de style Active Record | Moyenne (à l’exécution) | Oui (balises, Preload/Joins) | Auto-migrate (optionnel) | db.Raw(...) |
Livraison rapide, fonctionnalités riches, applications CRUD conventionnelles |
| Ent | Schéma → codegen → API fluide | Élevée (à la compilation) | De première classe (arêtes) | SQL généré (étape séparée) | entsql, SQL personnalisé |
Grandes bases de code, équipes avec beaucoup de refactoring, typage strict |
| Bun | Constructeur de requêtes/ORM orienté SQL | Moyenne–Élevée | Explicite (Relation) |
Package de migration séparé | Naturelle (constructeur + brut) | Services sensibles aux performances, fonctionnalités Postgres |
| sqlc | SQL → fonctions générées (pas un ORM) | Élevée (à la compilation) | Via des jointures SQL | Outil externe (ex., golang-migrate) | C’est du SQL | Contrôle et vitesse maximum ; équipes amies des DBA |
CRUD par exemple
Mise en place (PostgreSQL)
Utilisez pgx ou le pilote PG natif de l’outil. Exemple de DSN :
export DATABASE_URL='postgres://user:pass@localhost:5432/app?sslmode=disable'
Imports (communs à tous les ORMs)
Au début de chaque fichier contenant des exemples de code go, ajoutez :
import (
"context"
"os"
)
Nous allons modéliser une table users simple :
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
GORM
Initialisation
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 (optionnel ; soyez prudent en production)
func migrate(db *gorm.DB) error { return db.AutoMigrate(&User{}) }
CRUD
func gormCRUD(ctx context.Context, db *gorm.DB) error {
// Créer
u := User{Name: "Alice", Email: "alice@example.com"}
if err := db.WithContext(ctx).Create(&u).Error; err != nil { return err }
// Lire
var got User
if err := db.WithContext(ctx).First(&got, u.ID).Error; err != nil { return err }
// Mettre à jour
if err := db.WithContext(ctx).Model(&got).
Update("email", "alice+1@example.com").Error; err != nil { return err }
// Supprimer
if err := db.WithContext(ctx).Delete(&User{}, got.ID).Error; err != nil { return err }
return nil
}
Notes
- Relations via les balises de structure +
Preload/Joins. - Assistant de transaction :
db.Transaction(func(tx *gorm.DB) error { ... }).
Ent
Définition du schéma (dans 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(),
}
}
Générer le code
go run entgo.io/ent/cmd/ent generate ./ent/schema
Initialisation
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 {
// Créer
u, err := client.User.Create().
SetName("Alice").
SetEmail("alice@example.com").
Save(ctx)
if err != nil { return err }
// Lire
got, err := client.User.Get(ctx, u.ID)
if err != nil { return err }
// Mettre à jour
if _, err := client.User.UpdateOneID(got.ID).
SetEmail("alice+1@example.com").
Save(ctx); err != nil { return err }
// Supprimer
if err := client.User.DeleteOneID(got.ID).Exec(ctx); err != nil { return err }
return nil
}
Notes
- Typage fort de bout en bout ; arêtes pour les relations.
- Migrations générées ou utilisez votre outil de migration préféré.
Bun
Initialisation
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 {
// Créer
u := &User{Name: "Alice", Email: "alice@example.com"}
if _, err := db.NewInsert().Model(u).Exec(ctx); err != nil { return err }
// Lire
var got User
if err := db.NewSelect().Model(&got).
Where("id = ?", u.ID).
Scan(ctx); err != nil { return err }
// Mettre à jour
if _, err := db.NewUpdate().Model(&got).
Set("email = ?", "alice+1@example.com").
WherePK().
Exec(ctx); err != nil { return err }
// Supprimer
if _, err := db.NewDelete().Model(&got).WherePK().Exec(ctx); err != nil { return err }
return nil
}
Notes
- Joins/chargement eager explicites avec
.Relation("..."). - Package
bun/migrateséparé pour les migrations.
sqlc
sqlc n’est techniquement pas un ORM. Vous écrivez du SQL ; il génère des méthodes Go typées.
sqlc.yaml
version: "2"
sql:
- engine: postgresql
queries: db/queries
schema: db/migrations
gen:
go:
package: db
out: internal/db
sql_package: "database/sql" # ou "github.com/jackc/pgx/v5"
Requêtes (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;
Générer
sqlc generate
Utilisation
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)
// Créer
u, err := q.CreateUser(ctx, db.CreateUserParams{
Name: "Alice", Email: "alice@example.com",
})
if err != nil { return err }
// Lire
got, err := q.GetUser(ctx, u.ID)
if err != nil { return err }
// Mettre à jour
up, err := q.UpdateUserEmail(ctx, db.UpdateUserEmailParams{
ID: got.ID, Email: "alice+1@example.com",
})
if err != nil { return err }
// Supprimer
if err := q.DeleteUser(ctx, up.ID); err != nil { return err }
return nil
}
Notes
- Apportez vos propres migrations (ex.,
golang-migrate). - Pour les requêtes dynamiques : écrivez plusieurs variantes SQL ou combinez avec un petit constructeur.
Notes sur les performances
- GORM : pratique mais ajoute un surcoût de réflexion/abstraction. Correct pour le CRUD typique ; attention aux requêtes N+1 (privilégiez
JoinsouPreloadsélectif). - Ent : le code généré évite la réflexion ; bon pour les schémas complexes. Souvent plus rapide que les ORMs lourds avec magie à l’exécution.
- Bun : mince couche sur
database/sql; rapide, explicite, excellent pour les opérations par lots et les grands ensembles de résultats. - sqlc : essentiellement des performances SQL brutes avec sécurité à la compilation.
Conseils généraux
- Utilisez pgx comme pilote (v5) et context partout.
- Privilégiez le batching (
COPY,INSERTmulti-lignes) pour un haut débit. - Profilez le SQL :
EXPLAIN ANALYZE, index, index couvrants, évitez les allers-retours inutiles. - Réutilisez les connexions ; ajustez la taille du pool en fonction de la charge.
Expérience développeur et écosystème
- GORM : plus grande communauté, nombreux exemples/plugins ; courbe d’apprentissage plus raide pour les patterns avancés.
- Ent : excellente documentation ; l’étape de codegen est le principal changement de modèle mental ; super friendly au refactoring.
- Bun : requêtes lisibles et prévisibles ; communauté plus petite mais active ; excellentes fonctionnalités Postgres.
- sqlc : dépendances d’exécution minimales ; s’intègre bien avec les outils de migration et CI ; superbe pour les équipes à l’aise avec le SQL.
Points forts fonctionnels
- Relations & chargement eager : tous gèrent les relations ; GORM (balises +
Preload/Joins), Ent (arêtes +.With...()), Bun (Relation(...)), sqlc (vous écrivez les jointures). - Migrations : GORM (auto-migrate ; prudence en prod), Ent (SQL généré/diff), Bun (
bun/migrate), sqlc (outils externes). - Hooks/Extensibilité : GORM (callbacks/plugins), Ent (hooks/middleware + template/codegen), Bun (hooks de requête style middleware, SQL brut facile), sqlc (composition dans votre couche applicative).
- JSON/Arrays (Postgres) : Bun et GORM ont de beaux assistants ; Ent/sqlc gèrent via des types personnalisés ou du SQL.
Quand choisir quoi
- Choisissez GORM si vous voulez un confort maximum, des fonctionnalités riches et un prototypage rapide pour des services CRUD conventionnels.
- Choisissez Ent si vous valorisez la sécurité à la compilation, des schémas explicites et la maintenabilité à long terme dans de grandes équipes.
- Choisissez Bun si vous voulez des performances et des requêtes SQL explicites avec le confort de l’ORM là où cela aide.
- Choisissez sqlc si vous (et votre équipe) préférez le SQL pur avec des bindings Go typés et zéro surcoût d’exécution. sqlc est également un choix naturel pour le côté modèle de lecture d’une architecture CQRS en Go, où les requêtes sont façonnées pour les appelants plutôt que pour les entités du domaine et le SQL explicite vous donne un contrôle total sur la projection.
Si vous équilibrez encore ce choix d’ORM entre style d’intégration et frontières de services, cet aperçu de l’architecture applicative aide à placer la décision dans un contexte de production plus large.
docker-compose.yml minimal pour PostgreSQL local
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
Packages et libs ORM en GO
Autres liens utiles
- Golang Cheat Sheet
- Performance AWS lambda : JavaScript vs Python vs Golang
- Correction de l’erreur AutoMigrate postgresql Golang GORM
- Rééchantillonnage de documents texte avec Ollama et le modèle d’embedding Qwen3 - en Go
- Rééchantillonnage de documents texte avec Ollama et le modèle Reranker Qwen3 - en Go