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

Odin language: el lenguaje de programación orientado a datos para un desarrollo de software sensato

En esta serie sobre lenguajes de programación hablamos de Odin , un lenguaje orientado a datos. Un nuevo lenguaje de programación que nos podrá recordar a lenguajes como C++, Python, Golang o hasta Rust. Instalación (Windows): 1 2 3 4 $ git clone https://github.com/odin-lang/Odin $ cd Odin $ git pull $ build.bat release Debemos asegurarnos de crear la variable de entorno: Nombre: ODIN_HOME Valor: C:\Users\HP\Documents\pruebasOdin\Odin Ejemplo 1. Como primer programa tendremos al clásico "Hola, mundo". holamundo.odin package holamundo import " core:fmt " main : : proc ( ) { fmt . println ( " ¡Hola, mundo desde Odin! " ) } Construimos: $ odin build holamundo . odin - file Ejecutamos: $ holamundo .exe Recordar que si usamos Visual Studio Code podemos instalar el plugin de Odin Language: https://marketplace.visualstudio.com/items?itemName=DanielGavin.ols Ejemplo 2. Programa que muestre las variables que pod...

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...

z/OS, ¿Qué es? y ¿Cuáles son sus herramientas?

  En este post veremos las herramientas de z/OS de IBM. z/OS es un sistema operativo de mainframe desarrollado por IBM.  Es parte de la serie IBM z Systems (anteriormente conocida como System z), que se utiliza en los sistemas mainframe de IBM. z/OS es uno de los sistemas operativos más utilizados en el mundo empresarial para ejecutar aplicaciones críticas y procesar grandes volúmenes de datos. Características clave de z/OS: Escalabilidad : z/OS es conocido por su capacidad para manejar grandes cargas de trabajo y escalabilidad vertical, lo que permite agregar recursos de hardware para satisfacer las demandas de procesamiento. Confiabilidad y disponibilidad : z/OS está diseñado para ofrecer alta disponibilidad y confiabilidad. Incorpora características como redundancia y recuperación ante fallas para garantizar la continuidad de las operaciones. Seguridad : Proporciona una amplia gama de funciones de seguridad, como autenticación, autorización y auditoría, para proteger los...