---
title: "GORM em Go: O ORM Mais Popular do Ecossistema Explicado"
url: "https://golang.com.br/blog/gorm-go-orm-guia-producao/"
markdown_url: "https://golang.com.br/blog/gorm-go-orm-guia-producao.MD"
description: "GORM em Go na prática: models, CRUD, relacionamentos, transações, AutoMigrate, N+1, hooks e quando usar GORM em vez de sqlc ou database/sql. Com exemplos."
date: "2026-07-13"
author: "Golang Brasil"
---

# GORM em Go: O ORM Mais Popular do Ecossistema Explicado

GORM em Go na prática: models, CRUD, relacionamentos, transações, AutoMigrate, N+1, hooks e quando usar GORM em vez de sqlc ou database/sql. Com exemplos.


Quem chega em Go vindo do Django, do Rails, do Laravel ou do Entity Framework sente falta de algo que essas stacks dão de graça: um ORM que mapeia structs para tabelas, cuida de relacionamentos e abstrai o SQL repetitivo. A biblioteca padrão de Go oferece `database/sql`, excelente e explícito, mas verboso. É aí que entra o **GORM**, o ORM mais popular do ecossistema Go.

Este guia mostra como usar GORM em produção sem cair nas armadilhas clássicas: models bem declarados, CRUD idiomático, relacionamentos, transações, migrações, o temido problema N+1 e a comparação honesta com abordagens mais explícitas. Ele complementa o [guia de database/sql em produção](/blog/database-sql-go-pool-conexoes-producao/), o [tutorial de sqlc para SQL type-safe](/blog/sqlc-go-postgresql-typesafe/), o [guia de transações e locks no PostgreSQL](/blog/postgresql-transacoes-go-locks-retry/) e o [guia de migrations versionadas](/blog/migrations-go-banco-dados-producao/). Se você está montando uma API completa, leia também o [guia de APIs REST em Go](/aprenda/api-rest-go/).

## O que é GORM (e o que não é)

GORM é um ORM (Object-Relational Mapper) para Go. A ideia é simples: você declara uma struct, o GORM mapeia essa struct para uma tabela, e em vez de escrever `INSERT INTO users (name, email) VALUES (...)` você escreve `db.Create(&user)`. O ganho é produtividade e menos código repetitivo; o custo é uma camada de abstração que esconde o SQL e usa reflection por baixo.

O importante é entender o que GORM **não** é. Ele não é a única forma de acessar banco em Go, nem a mais "correta". Go tem uma cultura de explicitude: muitos times preferem [sqlc](/blog/sqlc-go-postgresql-typesafe/), que gera código Go a partir de queries SQL reais, ou `database/sql` puro. GORM brilha em domínios com muitos relacionamentos, CRUD administrativo e times que valorizam velocidade de desenvolvimento. Em leituras de alta performance ou em domínios onde o SQL é o contrato, abordagens explícitas costumam ser melhores.

## Instalação e conexão

GORM é um módulo por cima do driver do banco. Para PostgreSQL, você combina o GORM com o driver `gorm.io/driver/postgres`:

```bash
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres
```

A conexão recebe uma string DSN e opções como pool máximo e tempo máximo de vida da conexão, exatamente como em `database/sql`:

```go
package main

import (
	"log"
	"time"

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

func mustOpenDB(dsn string) *gorm.DB {
	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Warn),
	})
	if err != nil {
		log.Fatalf("falha ao conectar no banco: %v", err)
	}

	sqlDB, err := db.DB()
	if err != nil {
		log.Fatalf("falha ao obter sqlDB: %v", err)
	}

	sqlDB.SetMaxOpenConns(25)
	sqlDB.SetMaxIdleConns(25)
	sqlDB.SetConnMaxLifetime(5 * time.Minute)

	return db
}
```

Duas observações: o `logger` do GORM é a sua melhor ferramenta em desenvolvimento — ele imprime cada SQL executado e o tempo gasto, o que ajuda a detectar problemas de performance e N+1. Em produção, suba para `logger.Error` para não vazar SQL em logs. E os limites do pool (`SetMaxOpenConns`, `SetConnMaxLifetime`) seguem as mesmas [regras de pool de conexões](/blog/database-sql-go-pool-conexoes-producao/) que você aplicaria em `database/sql`.

## Models, tags e convenções

Um model é uma struct Go com tags do GORM. Por convenção, o nome da struct vira o nome da tabela no plural (`User` → `users`), e campos como `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` têm comportamento automático:

```go
package model

import "time"

type User struct {
	ID        uint           `gorm:"primaryKey"`
	Name      string         `gorm:"size:100;not null"`
	Email     string         `gorm:"size:150;uniqueIndex;not null"`
	Age       int            `gorm:"-"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt gorm.DeletedAt `gorm:"index"`
}
```

Pontos-chave dessa declaração: `gorm:"size:100"` define o tamanho da coluna; `uniqueIndex` cria um índice único; `gorm:"-"` faz o GORM ignorar o campo (útil para dados calculados ou de transporte). O campo `DeletedAt` do tipo `gorm.DeletedAt` ativa o **soft delete**: chamadas de `Delete` não removem a linha, apenas preenchem essa coluna, e `Find` automaticamente filtra linhas "deletadas". É conveniente, mas você precisa estar ciente de que os dados continuam no banco.

Se quiser sobrescrever o nome da tabela, implemente a interface `Tabler`:

```go
func (User) TableName() string {
	return "app_users"
}
```

## CRUD sem mistério

As operações básicas seguem verbos naturais. Para **criar**:

```go
user := model.User{Name: "Ana Souza", Email: "ana@example.com"}
if err := db.Create(&user).Error; err != nil {
	return err
}
// user.ID agora está preenchido
```

Para **buscar** um registro por chave primária ou por condição:

```go
var u model.User
if err := db.First(&u, 42).Error; err != nil {
	// err == gorm.ErrRecordNotFound quando não existe
	return err
}

if err := db.Where("email = ?", "ana@example.com").First(&u).Error; err != nil {
	return err
}
```

Sempre passe valores como parâmetros (`?`), nunca concatenar strings — isso evita injeção de SQL, o mesmo princípio de segurança que vale para qualquer camada de banco. Para listar vários registros:

```go
var users []model.User
if err := db.Where("age >= ?", 18).Order("created_at desc").Limit(50).Find(&users).Error; err != nil {
	return err
}
```

Para **atualizar**, prefira selecionar os campos explicitamente para não sobrescrever colunas sem querer:

```go
if err := db.Model(&u).Select("Name", "Age").Updates(map[string]any{
	"Name": "Ana Souza Lima",
	"Age":  30,
}).Error; err != nil {
	return err
}
```

Para **deletar** (soft delete, por causa do `DeletedAt`):

```go
if err := db.Delete(&model.User{}, 42).Error; err != nil {
	return err
}
```

## Relacionamentos

GORM cuida de relacionamentos com tags declarativas. Um usuário com muitos posts e um post que pertence a um usuário:

```go
type Post struct {
	ID       uint `gorm:"primaryKey"`
	Title    string
	Body     string
	UserID   uint
	User     User      `gorm:"foreignKey:UserID"`
	Tags     []Tag     `gorm:"many2many:post_tags;"`
}

type Tag struct {
	ID   uint `gorm:"primaryKey"`
	Name string `gorm:"uniqueIndex"`
}
```

O ponto crítico aqui é o **carregamento**. Se você buscar um post e depois acessar `post.User` em cada item de uma lista, o GORM fará uma query extra por post — o famoso problema N+1. A solução é o `Preload`, que carrega o relacionamento em uma única query:

```go
var posts []Post
if err := db.Preload("User").Preload("Tags").Find(&posts).Error; err != nil {
	return err
}
```

O `Preload` é a diferença entre uma página que faz 1 query e uma que faz 101. Em desenvolvimento, com o logger ativo, você vê o N+1 acontecer em tempo real. Para detalhes avançados de [concorrência e cancelamento](/blog/context-timeout-cancelamento-go/), lembre-se que o GORM aceita um `context.Context` via `db.WithContext(ctx)`.

## Transações e atomicidade

Transações no GORM são explícitas e seguras, usando uma função que retorna erro. Se a função retornar erro, o GORM faz rollback; se retornar `nil`, faz commit:

```go
err := db.Transaction(func(tx *gorm.DB) error {
	if err := tx.Create(&order).Error; err != nil {
		return err
	}
	for _, item := range order.Items {
		if err := tx.Create(&item).Error; err != nil {
			return err // rollback de tudo
		}
	}
	return nil // commit
})
```

Isso é mais seguro que gerenciar `Begin`/`Commit`/`Rollback` manualmente, porque cobre também o caso de pânico. Para retries sob contenção ou [locks no PostgreSQL](/blog/postgresql-transacoes-go-locks-retry/), encapsule a chamada em um loop com backoff.

## AutoMigrate: útil, mas com cautela

O GORM oferece `db.AutoMigrate(&model.User{})`, que cria tabelas e adiciona colunas faltantes com base nas structs. É ótimo para protótipos e para subir um ambiente de desenvolvimento rápido:

```go
if err := db.AutoMigrate(&model.User{}, &model.Post{}, &model.Tag{}); err != nil {
	log.Fatalf("auto migrate falhou: %v", err)
}
```

Em produção, porém, o `AutoMigrate` **não é a forma recomendada de versionar o schema**. Ele só adiciona colunas, não remove nem renomeia, não rastreia estado entre ambientes e pode divergir entre máquinas. Para produção, use [migrations versionadas e explícitas](/blog/migrations-go-banco-dados-producao/), que garantem que laptop, CI, staging e produção saibam exatamente qual versão do banco está rodando. Pense no AutoMigrate como um atalho de desenvolvimento, não como estratégia de schema.

## Hooks e callbacks

GORM permite executar lógica antes ou depois de operações via métodos com nomes reservados, como `BeforeCreate`:

```go
func (u *User) BeforeCreate(tx *gorm.DB) error {
	if u.Email == "" {
		return errors.New("email é obrigatório")
	}
	return nil
}
```

Hooks são poderosos, mas cuidado: lógica escondida em hooks torna o fluxo difícil de seguir e de testar. Para validação de negócio, muitas vezes é mais claro validar explicitamente antes de chamar `db.Create`, mantendo o model como um contêiner de dados e a validação no domínio.

## GORM vs sqlc vs database/sql

A decisão de camada de banco depende do domínio e do time. A tabela resume quando cada abordagem brilha:

| Critério | GORM | sqlc | database/sql |
|---|---|---|---|
| Velocidade de desenvolvimento | Alta | Média | Baixa |
| Controle sobre o SQL | Baixo | Total | Total |
| Type safety de queries | Parcial | Total (gerada) | Manual |
| Relacionamentos complexos | Fácil | Verboso | Verboso |
| Reflexão / custo de runtime | Sim | Não | Não |
| Boa para CRUD administrativo | Sim | Aceitável | Aceitável |
| Boa para leituras críticas | Com cuidado | Sim | Sim |

Uma combinação comum e saudável é **sqlc para leituras críticas e GORM para CRUD administrativo**, dentro do mesmo projeto. Não existe "a forma certa" universal — existe a forma certa para o seu domínio. O pior erro é misturar as três sem critério e sem documentar a fronteira entre elas.

## Armadilhas comuns

Três problemas aparecem com frequência em projetos com GORM:

1. **N+1** — sempre que percorrer uma lista e acessar um relacionamento, use `Preload`. Confirme com o logger ligado.
2. **Soft delete inesperado** — o `DeletedAt` faz `Find` esconder linhas. Se precisar de uma contagem real ou de uma listagem de tudo, use `Unscoped().Find(...)`.
3. **Updates amplos demais** — `Save` atualiza todas as colunas; `Updates` com `Select` é mais seguro quando você quer mudar só alguns campos.

Para diagnosticar problemas de performance em produção, combine o logger do GORM com o [guia de pprof](/blog/pprof-go-producao/). Geralmente o gargalo não é o GORM em si, mas o padrão de queries que ele facilita.

Se você trabalha em times com várias linguagens, vale comparar com o jeito que o <a href="https://python.dev.br/blog/banco-de-dados-sqlalchemy/" target="_blank" rel="noopener noreferrer" onclick="umami.track(portfolio-site-click, { destination: python.dev.br })">Python Dev Brasil trata bancos de dados com SQLAlchemy</a>: os tradeoffs entre ORM e SQL explícito são os mesmos em qualquer stack.

## Perguntas frequentes

### GORM é a melhor escolha para todo projeto Go?

Não. GORM é excelente para produtividade em domínios com muitos relacionamentos e CRUD intensivo, mas em serviços de alta performance ou onde o SQL é o contrato, sqlc ou `database/sql` costumam ser melhores. Avalie por domínio e por time, não por hype.

### Como faço queries raw quando preciso?

Use `db.Raw("SELECT ... WHERE id = ?", id).Scan(&result)` para leituras e `db.Exec("UPDATE ... WHERE id = ?", id)` para escritas. Você mantém a conexão e o pool do GORM, mas escreve o SQL explicitamente quando a abstração atrapalha.

### GORM funciona com PostgreSQL, MySQL e SQLite?

Sim. GORM usa drivers separados (`gorm.io/driver/postgres`, `gorm.io/driver/mysql`, `gorm.io/driver/sqlite`). A API é a mesma; o que muda é o Dialector e pequenas diferenças de SQL específicas de cada banco. Para [PostgreSQL em produção com pgxpool](/blog/pgxpool-go-postgresql-producao/), o driver do GORM já usa pgx por baixo.

### Como testo código que usa GORM?

Use SQLite em memória (`sqlite.Open(":memory:")`) ou [Testcontainers](/blog/go-testcontainers-testes-integracao-containers/) para um banco real em testes de integração. Evite mockar o GORM — teste contra um banco de verdade para confiar no resultado. Veja o [guia de mocks, fakes e httptest](/blog/mocks-go-testify-gomock-fakes-httptest/) para a fronteira entre o que mockar e o que testar de verdade.

## Conclusão

GORM é a porta de entrada natural para quem quer produtividade de ORM em Go. Usado com disciplina — `Preload` para evitar N+1, `Select` para updates controlados, migrations versionadas em produção e queries raw quando a abstração atrapalha — ele acelera muito o desenvolvimento sem sacrificar a clareza. A chave é saber a fronteira entre GORM, [sqlc](/blog/sqlc-go-postgresql-typesafe/) e `database/sql`, e escolher cada um onde brilha.

Para continuar aprendendo, explore o [roadmap de Go para 2026](/aprenda/roadmap-go-2026/), confira [vagas de Go no Brasil](/vagas/) e entenda a faixa de [salários de desenvolvedor Go](/carreira/salarios-go-brasil/) no mercado brasileiro. O próximo passo depois de dominar a camada de banco é estruturar a aplicação inteira — leia o [guia de clean architecture em Go](/blog/clean-architecture-go-sem-overengineering/) para amarrar models, repositórios e casos de uso sem overengineering.
