Comparaison des ORMs Go pour PostgreSQL : GORM vs Ent vs Bun vs sqlc
Un aperçu pratique et axé sur le code des ORMs en GO
Les ORM les plus réputés pour GO sont GORM, Ent, Bun et sqlc. Voici une petite comparaison d’entre eux avec des exemples d’opérations CRUD en pure GO.

TL;DR
- GORM : fonctionnalités nombreuses et pratiques ; le plus facile à « simplement déployer », mais avec un surcoût d’exécution plus important.
- Ent : schéma en code avec des API générées et typées ; idéal pour les grands projets et les refactorings.
- Bun : léger, générateur de requêtes SQL-first/ORM ; rapide avec de bonnes fonctionnalités pour Postgres, explicite par conception.
- sqlc (non un ORM au sens strict mais tout de même) : écrivez du SQL, obtenez des fonctions Go typées ; meilleure performance brute et contrôle, pas de magie d’exécution.
Critères de sélection et comparaison rapide
Mes critères sont :
- Performance : latence/throughput, surcoût évitable, opérations par lots.
- 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çage).
- Ensemble de fonctionnalités : relations, chargement anticipé, migrations, hooks, sorties SQL brutes.
| Outil | Paradigme | Sécurité des types | Relations | Migrations | Ergonomie du SQL brut | Cas d’utilisation typique |
|---|---|---|---|---|---|---|
| GORM | ORM de type Active Record | Moyenne (runtime) | Oui (tags, Preload/Joins) | Auto-migrate (optionnel) | db.Raw(...) |
Livraison rapide, fonctionnalités riches, applications CRUD conventionnelles |
| Ent | Schéma → génération de code → API fluide | Élevée (compile-time) | Première classe (edges) | SQL généré (étape séparée) | entsql, SQL personnalisé |
Grandes bases de code, équipes en phase de refactor, typage strict |
| Bun | Générateur de requêtes SQL-first/ORM | Moyenne–Élevée | Explicite (Relation) |
Package séparé pour les migrations | Naturel (constructeur + brut) | Services sensibles à la performance, fonctionnalités Postgres |
| sqlc | SQL → génération de fonctions (non un ORM) | Élevée (compile-time) | Via les jointures SQL | Outil externe (ex. golang-migrate) | C’est le SQL | Contrôle et vitesse maximum ; équipes amicales aux DBA |
CRUD par exemple
Configuration (PostgreSQL)
Utilisez pgx ou le pilote natif PG de l’outil. Exemple de DSN :
export DATABASE_URL='postgres://user:pass@localhost:5432/app?sslmode=disable'
Importations (communes à tous les ORMs)
Au début de chaque fichier avec go code exemple ajoutez :
import (
"context"
"os"
)
Nous modéliserons une table simple users :
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 ; faites attention 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. - Helper 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 du 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 de choix.
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
- Jointures/explicit loading avec
.Relation("..."). - Package séparé
bun/migratepour 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 générateur.
Notes sur les performances
- GORM : pratique mais ajoute un surcoût d’abstraction via la réflexion. Bon pour les CRUD classiques ; faites attention aux requêtes N+1 (privilégiez
Joinsou unPreloadsé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 d’exécution.
- Bun : mince sur
database/sql; rapide, explicite, idéal pour les opérations par lots et les grands ensembles de résultats. - sqlc : performance brute du SQL avec une sécurité au moment de la compilation.
Conseils généraux
- Utilisez pgx comme pilote (v5) et context partout.
- Privilégiez le regroupement (
COPY,INSERTmulti-lignes) pour un throughput élevé. - Profilage 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 du développeur et écosystème
- GORM : communauté la plus grande, nombreux exemples/plugins ; courbe d’apprentissage plus raide pour les modèles avancés.
- Ent : documentation excellente ; étape de génération de code est le principal changement de mentalité ; très amical pour les refactorings.
- Bun : requêtes lisibles et prévisibles ; communauté plus petite mais active ; excellentes fonctionnalités Postgres.
- sqlc : dépendances minimales en temps d’exécution ; s’intègre bien avec les outils de migration et le CI ; idéal pour les équipes habituées au SQL.
Points forts des fonctionnalités
- Relations & chargement anticipé : tous gèrent les relations ; GORM (balises +
Preload/Joins), Ent (arêtes +.With...()), Bun (Relation(...)), sqlc (vous écrivez les jointures). - Migrations : GORM (auto-migrate ; faites attention en production), 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 similaires aux middleware, SQL brut facile), sqlc (composez dans votre couche d’application).
- JSON/Arrays (Postgres) : Bun et GORM ont des helpers pratiques ; Ent/sqlc gèrent via des types personnalisés ou SQL.
Quand choisir quoi
- Choisissez GORM si vous souhaitez une commodité maximale, des fonctionnalités riches et une prototypage rapide pour les services CRUD conventionnels.
- Choisissez Ent si vous valorisez la sécurité au moment de la compilation, des schémas explicites et une maintenance à long terme dans des équipes plus importantes.
- Choisissez Bun si vous souhaitez une performance et des requêtes SQL explicites avec les commodités ORM là où cela aide.
- Choisissez sqlc si vous (et votre équipe) préférez le SQL pur avec des liaisons Go typées et aucun surcoût en temps d’exécution.
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 bibliothèques ORM en GO
Liens utiles
- Feuille de triche Golang
- Performance d’AWS Lambda : JavaScript vs Python vs Golang
- Résoudre l’erreur GORM AutoMigrate PostgreSQL
- Réordonner des documents textuels avec Ollama et modèle d’embedding Qwen3 - en Go
- Réordonner des documents textuels avec Ollama et modèle de réordonnancement Qwen3 - en Go