Ir al contenido principal

Hablemos de GORM: una librería ORM para Go

GORM (Go ORM) es una librería que sirve para el mapeo objeto relacional.

Características principales:

  • ORM con todas las funciones Asociaciones (tiene uno, tiene muchos, pertenece a, muchos a muchos, polimorfismo, herencia de tabla única).
  • Ganchos (antes/después de crear/guardar/actualizar/eliminar/buscar).
  • Carga diligente con precarga, uniones.
  • Transacciones, transacciones anidadas, punto de guardado, revertir al punto guardado.
  • Contexto, modo de sentencia preparada, modo de ejecución en seco.
  • Inserción por lotes, búsqueda en lotes, búsqueda/creación con mapa, CRUD con SQL Expr y valorador de contexto.
  • Constructor de SQL, Upsert, bloqueo, sugerencias de optimizador/índice/comentario, argumento con nombre, subconsulta.
  • Clave primaria compuesta, índices, restricciones.
  • Migraciones automáticas.
  • Registrador API de complemento extensible y flexible: Resolvedor de bases de datos (múltiples bases de datos, división de lectura/escritura) / Prometheus… 
  • Cada función incluye pruebas.
  • Fácil de usar para desarrolladores.

Digamos que GORM es el Hibernate de los programadores Go.

Cómo empezar con GORM

Crearemos una API REST en Go que:

  • Use GORM como ORM. 
  • Se conecte a MariaDB usando el driver de MySQL (compatible). 
  • Trabaje contra tu tabla persons, ya existente. 
  • Exporte endpoints:
    • POST /persons
    • GET /persons
    • GET /persons/:id
    • PUT /persons/:id
    • DELETE /persons/:id

1. Crearemos una carpeta y nos ubicaremos en ella.

$ mkdir gorm-mariadb-crud
$ cd gorm-mariadb-crud

2. Inicializamos el módulo:

$ go mod init github.com/hironakamura/gorm-mariadb-crud

El directorio del proyecto debería quedar más o menos así:

gorm-mariadb-crud/ 
    go.mod

3. Instalamos gorm y las dependencias necesarias:

$ go get -u gorm.io/gorm
$ go get -u gorm.io/driver/mysql
$ go get -u github.com/gin-gonic/gin

Con esto hemos instalado GORM, el driver de MySQL (compatible con MariaDB) y Gin framework. Herramientas necesarias para el proyecto.

Ahora el directorio del proyecto lucirá así:

gorm-mariadb-crud/ 
    go.mod
    go.sum

Para el ejemplo usaremos una BD de MariaDB; GORM permite distintos SGBD para ello. Tenemos la tabla llamada ``persons`` con los siguientes campos:

+-----------+--------------+------+-----+---------+-------+
| Field     | Type         | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| PersonID  | int(11)      | YES  |     | NULL    |       |
| LastName  | varchar(255) | YES  |     | NULL    |       |
| FirstName | varchar(255) | YES  |     | NULL    |       |
| Address   | varchar(255) | YES  |     | NULL    |       |
| City      | varchar(255) | YES  |     | NULL

GORM necesita una primary key. Vamos a asumir que el campo PersonID lo es.

4. Crearemos una carpeta para nuestro modelo y dentro de esta un archivo llamado person.go

models/person.go

package models

type Person struct {
    PersonID  uint   `gorm:"primaryKey;column:PersonID"`
    LastName  string `gorm:"column:LastName"`
    FirstName string `gorm:"column:FirstName"`
    Address   string `gorm:"column:Address"`
    City      string `gorm:"column:City"`
}

// TableName fuerza a GORM a usar el nombre exacto de la tabla
func (Person) TableName() string {
    return "persons"
}

Nuestra conexión a la BD de MariaDB será esta:

root:password@tcp(host:3306)/cursomariadb?charset=utf8mb4&parseTime=True&loc=Local

5. Creamos módulo de conexión:

database/database.go

package database

import (
    "log"

    "github.com/hironakamura/gorm-mariadb-crud/models"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

var DB *gorm.DB

func Connect() {
    // Ajusta estos valores a tu entorno
    dsn := "root:password@tcp(127.0.0.1:3306)/cursomariadb?charset=utf8mb4&parseTime=True&loc=Local"

    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("Error al conectar a MariaDB: ", err)
    }

    DB = db

    // Migración automática (opcional si ya tienes la tabla)
    if err := DB.AutoMigrate(&models.Person{}); err != nil {
        log.Fatal("Error en AutoMigrate: ", err)
    }
}

5. En este punto usaremos Gin, específiamente el concepto de handlers. Crearemos una nueva carpeta y archivo:

handlers/person_handlers.go

package handlers

import (
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/hironakamura/gorm-mariadb-crud/database"
    "github.com/hironakamura/gorm-mariadb-crud/models"
)

Dentro de este archivo tendremos los métodos de POST, GET, PUT y DELETE; métodos básicos de petición HTTP.

POST/persons

func CreatePerson(c *gin.Context) {
    var person models.Person

    if err := c.ShouldBindJSON(&person); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    if err := database.DB.Create(&person).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al crear persona"})
        return
    }

    c.JSON(http.StatusOK, person)
}

GET/persons

func GetPersons(c *gin.Context) {
    var persons []models.Person

    if err := database.DB.Find(&persons).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al obtener personas"})
        return
    }

    c.JSON(http.StatusOK, persons)
}

GET/persons/:id

func GetPerson(c *gin.Context) {
    id := c.Param("id")
    var person models.Person

    if err := database.DB.First(&person, id).Error; err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "Persona no encontrada"})
        return
    }

    c.JSON(http.StatusOK, person)
}

PUT/persons/:id

func UpdatePerson(c *gin.Context) {
    id := c.Param("id")
    var person models.Person

    // Buscar registro existente
    if err := database.DB.First(&person, id).Error; err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "Persona no encontrada"})
        return
    }

    // Leer datos nuevos
    var input models.Person
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // Actualizar solo campos enviados
    if err := database.DB.Model(&person).Updates(input).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al actualizar persona"})
        return
    }

    c.JSON(http.StatusOK, person)
}

DELETE/persons/:id

func DeletePerson(c *gin.Context) {
    id := c.Param("id")
    var person models.Person

    if err := database.DB.First(&person, id).Error; err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "Persona no encontrada"})
        return
    }

    if err := database.DB.Delete(&person).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al eliminar persona"})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "Persona eliminada"})
}

Handler completo:

package handlers

import (
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/hironakamura/gorm-mariadb-crud/database"
    "github.com/hironakamura/gorm-mariadb-crud/models"
)

func CreatePerson(c *gin.Context) {
    var person models.Person

    if err := c.ShouldBindJSON(&person); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    if err := database.DB.Create(&person).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al crear persona"})
        return
    }

    c.JSON(http.StatusOK, person)
}



func GetPersons(c *gin.Context) {
    var persons []models.Person

    if err := database.DB.Find(&persons).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al obtener personas"})
        return
    }

    c.JSON(http.StatusOK, persons)
}


func GetPerson(c *gin.Context) {
    id := c.Param("id")
    var person models.Person

    if err := database.DB.First(&person, id).Error; err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "Persona no encontrada"})
        return
    }

    c.JSON(http.StatusOK, person)
}


func UpdatePerson(c *gin.Context) {
    id := c.Param("id")
    var person models.Person

    // Buscar registro existente
    if err := database.DB.First(&person, id).Error; err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "Persona no encontrada"})
        return
    }

    // Leer datos nuevos
    var input models.Person
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // Actualizar solo campos enviados
    if err := database.DB.Model(&person).Updates(input).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al actualizar persona"})
        return
    }

    c.JSON(http.StatusOK, person)
}


func DeletePerson(c *gin.Context) {
    id := c.Param("id")
    var person models.Person

    if err := database.DB.First(&person, id).Error; err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "Persona no encontrada"})
        return
    }

    if err := database.DB.Delete(&person).Error; err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Error al eliminar persona"})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "Persona eliminada"})
}

6. Creamos el archivo principal del proyecto.

main.go

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/hironakamura/gorm-mariadb-crud/database"
    "github.com/hironakamura/gorm-mariadb-crud/handlers"
)

func main() {
    // 1. Conectar a la base de datos
    database.Connect()

    // 2. Configurar router
    r := gin.Default()

    // 3. Definir rutas
    r.POST("/persons", handlers.CreatePerson)
    r.GET("/persons", handlers.GetPersons)
    r.GET("/persons/:id", handlers.GetPerson)
    r.PUT("/persons/:id", handlers.UpdatePerson)
    r.DELETE("/persons/:id", handlers.DeletePerson)

    // 4. Levantar servidor
    r.Run(":8080")
}

7. Ejecutamos el proyecto:

$ go run ./...

Si todo va bien veremos algo como esto en la terminal:

2025/12/14 18:41:11 [32mC:/Users/HP/Documents/pruebasGo/gorm-mariadb-crud/database/database.go:25 [33mSLOW SQL >= 200ms
[0m[31;1m[202.655ms] [33m[rows:-][35m SELECT * FROM `persons` LIMIT 1[0m

2025/12/14 18:41:13 [32mC:/Users/HP/Documents/pruebasGo/gorm-mariadb-crud/database/database.go:25 [33mSLOW SQL >= 200ms
[0m[31;1m[1795.554ms] [33m[rows:4][35m ALTER TABLE `persons` MODIFY COLUMN `LastName` longtext[0m

2025/12/14 18:41:13 [32mC:/Users/HP/Documents/pruebasGo/gorm-mariadb-crud/database/database.go:25 [33mSLOW SQL >= 200ms
[0m[31;1m[906.920ms] [33m[rows:4][35m ALTER TABLE `persons` MODIFY COLUMN `FirstName` longtext[0m

2025/12/14 18:41:14 [32mC:/Users/HP/Documents/pruebasGo/gorm-mariadb-crud/database/database.go:25 [33mSLOW SQL >= 200ms
[0m[31;1m[641.563ms] [33m[rows:4][35m ALTER TABLE `persons` MODIFY COLUMN `Address` longtext[0m

2025/12/14 18:41:15 [32mC:/Users/HP/Documents/pruebasGo/gorm-mariadb-crud/database/database.go:25 [33mSLOW SQL >= 200ms
[0m[31;1m[547.782ms] [33m[rows:4][35m ALTER TABLE `persons` MODIFY COLUMN `City` longtext[0m
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /persons                  --> github.com/hironakamura/gorm-mariadb-crud/handlers.CreatePerson (3 handlers)
[GIN-debug] GET    /persons                  --> github.com/hironakamura/gorm-mariadb-crud/handlers.GetPersons (3 handlers)
[GIN-debug] GET    /persons/:id              --> github.com/hironakamura/gorm-mariadb-crud/handlers.GetPerson (3 handlers)
[GIN-debug] PUT    /persons/:id              --> github.com/hironakamura/gorm-mariadb-crud/handlers.UpdatePerson (3 handlers)
[GIN-debug] DELETE /persons/:id              --> github.com/hironakamura/gorm-mariadb-crud/handlers.DeletePerson (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080

Para probarlo podemos crear scripts con Powershell:

create_person.ps1

$body = @{
    LastName  = "Juan"
    FirstName = "Perez"
    Address   = "123 Main St"
    City      = "CDMX"
} | ConvertTo-Json

Invoke-RestMethod -Uri "http://localhost:8080/persons" `
                  -Method Post `
                  -Body $body `
                  -ContentType "application/json"

Ejecución:

$ .\create_person.ps1

get_all.ps1

Invoke-RestMethod -Uri "http://localhost:8080/persons" -Method Get

Ejecución:

$ .\get_all.ps1

get_one.ps1

Invoke-RestMethod -Uri "http://localhost:8080/persons/1" -Method Get

Ejecución:

$ .\get_one.ps1

update_person.ps1

$update = @{
    LastName  = "DoeUpdated"
    FirstName = "Juan"
    Address   = "Nueva dirección"
    City      = "Monterrey"
} | ConvertTo-Json

Invoke-RestMethod -Uri "http://localhost:8080/persons/1" `
                  -Method Put `
                  -Body $update `
                  -ContentType "application/json"

Ejecución:

$ .\update_person.ps1

delete_person.ps1

Invoke-RestMethod -Uri "http://localhost:8080/persons/1" -Method Delete

Ejecución:

$ .\delete_person.ps1

Estos 4 scripts de Powershell nos ayudan a probar nuestra aplicación API Rest con GORM.

¡Hemos aprendido a usar GORM!

GORM es una herramienta bastante útil a la hora de trabajar con BD y Go.

Enlaces:

https://gorm.io/
https://medium.com/@feldyjudahk/database-connections-with-gorm-in-golang-opening-closing-and-connection-pooling-277043e1d568
https://medium.com/@itskenzylimon/getting-started-on-golang-gorm-af49381caf3f

Comentarios

Entradas populares de este blog

Programación Windows Batch (CMD) parte 3

Crear ciclos para efectuar operaciones tediosas nos ahorrará tiempo para realizar otras tareas. En está ocasión veremos ciclos con FOR . ¿Cuál es la síntaxis de bucle FOR en Windows Batch? Si está dentro de un archivo *.bat : FOR %%variable IN (seq) DO operaciones Si lo ejecutamos en una terminal: FOR %variable IN (seq) DO operaciones Ejemplo 1 . Recorrer una secuencia de números del 0 al 5: recorrer.bat @ echo off FOR %%i in ( 0 1 2 3 4 5 ) DO echo Hola no. %%i pause Nos imprimirá en pantalla: Hola no. 0 Hola no. 1 Hola no. 2 Hola no. 3 Hola no. 4 Hola no. 5 ¿Puedo usar contadores? Si, se pueden usar. Ejemplo 2 . Uso de contadores: contador.bat @ echo off set numeros = 1 2 3 4 5 6 7 8 9 10 set cont = 0 for %%a in ( %numeros% ) do ( echo Hola no. %%a :: Contador set /a cont+ = 1 ) echo Total: %cont% Este código nos imprimirá, además de los mensajes Hola no. 0 ..., el total de valores conta...

TIOBE index 2024

El índice TIOBE (o índice de la comunidad de programación TIOBE) mide la popularidad de los lenguajes de programación. Su índice no se basa en qué lenguaje de programación es mejor, si tiene mejor perfomance, si posee escalabilidad, si es más sencillo de aprender, de usar, de implementar, etc. Sólo se basa en la popularidad. En el número de referencias ya sea en blogs, foros de consulta, etc. No necesariamente si las empresas lo están usando en alguno de sus desarrollos. Este índice es útil para darse una idea qué lenguaje está cobrando más popularidad y prestigio. Enseguida una tabla con los primeros 5 lenguajes más populares. Índice de Noviembre 2024   Nov 2024  Nov 2023  Change  Programming Language  Ratings  Change  1  1    Python  22.85%  +8.69%  2  3  +  C++  10.64%  +0.29%  3  4  +  Java  9.60%  +1.26%  4 ...

Programación Windows Batch (CMD)

En esta serie de post aprenderemos cómo crear nuestros propios archivos Windows Batch. 1. ¿Por qué aprender Batch en Windows? Una de las razones es que, queramos o no, algún día tendremos que trabajar usando sistemas operativos Windows. Crear scripts nos puede servir para realizar tareas que parecen sencillas en un principio, pero que pueden llegar a ser tediosas (ej. crear múltiples carpetas, comprobar la existencia de un archivo, realizar respaldos, etc.). 2. ¿Cómo puedo crear un archivo Batch en Windows? Los archivos batch en Windows pueden ser escritos con las extensiones *.bat o *.cmd. Pueden contener una serie de comandos y secuencias de instrucciones para realizar diversas tareas. Ejemplo 1. Conocer  el nombre del usuario. usuario.bat @ echo off echo Hola %USERNAME% pause GOTO Comentario -- esto es un comentario multi línea que no se interpretará en MS-DOS -- :Comentario En el anterior ejemplo podemos ver que la variable USERNAME conti...