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

Rocket: un framework web para Rust

Rocket Framework es: Un framework web escrito en Rust.   Inspirado en Rails, Flask, Bottle y Yesod.  Admite el manejo de solicitudes HTTP, Web Sockets, JSON, plantillas y más. Creando un proyecto con Rocket Framework En primer lugar, verificamos la instalación de Rust en nuestras máquinas: rustc - - version Continuamos con la configuración de Rust nightly como nuestra cadena de herramientas predeterminada ejecutando el comando: rustup default nightly Si lo prefieres, una vez que configuremos un directorio de proyecto en la siguiente sección, puedes usar anulaciones por directorio para usar la versión nocturna solo para tu proyecto Rocket ejecutando el siguiente comando en el directorio: rustup override set nightly Creamos nuevo proyecto `` hola-rocket `` y nos ubicamos en el directorio creado: cargo new hola - rocket - - bin cd hola - rocket Con Cargo, agregamos la dependencia de rocket: cargo add rocket Abrimos el archivo `` Cargo.toml `` Ca...

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