GraphQL en Go

GraphQL en Go

Integración GqlGen y Gorm

Introducción

gqlgen es un generador de código GraphQL para Go que simplifica el proceso de construcción de servidores GraphQL. gqlgen se basa en el enfoque "schema first", por lo que puedes definir tu esquema GraphQL en SDL (Schema Definition Language) y luego dejar que la herramienta genere automáticamente el código necesario para resolver consultas, mutaciones y suscripciones.

Gorm es un popular ORM (Object Relational Mapping) para Go, diseñado para simplificar las operaciones de base de datos y mejorar la eficiencia del desarrollo. Proporciona una interfaz sencilla y potente para interactuar con bases de datos relacionales, permitiendo a los desarrolladores centrarse en la lógica de la aplicación en lugar de preocuparse por detalles de bajo nivel.

En este tutorial, combinaremos la potencia de gqlgen para la capa GraphQL y Gorm para la persistencia de datos, creando así una API GraphQL robusta y eficiente para gestionar tareas.

Consideraciones

Este tutorial es completamente práctico, por lo que no se abordará conceptos teóricos; por esta razón, para sacar mayor provecho al tutorial, es conveniente tener alguna experiancia previa en el desarrollo con Go, algún conocimiento básico de GraphQL (queries, mutations, SDL, etc) y conocimiento básico de base de datos relacionales.

Proyecto

El proyecto consiste en un API Graphql para guardar, consultar, modificar y eliminar datos sobre usuarios y posts. Estableceremos una relación "one to many" de forma que un usuario puede tener muchos posts, y un post pertenece a un usuario. Generaremos las queries de forma que al consultar los usuarios podamos acceder a los posts que tienen y, al consultar los posts poder ver los usuarios al que pertenecen. Las mutations nos permitirán añadir usuarios y posts, actualizarlos y eliminarlos.

Instalación y configuración de GQLGEN

Comenzamos creando el directorio e inicializamos el módulo para nuestro proyecto.

mkdir graphql-api-users-posts
cd graphql-api-users-posts

Llamé al directorio graphql-api-users-posts , una vez dentro de la carpeta, inicializamos el módulo de GO.

go mod init github.com/[tu-nombre-de-usuario]/graphql-api-users-posts

Siguiendo la documentación de gqlgen, debemos crear un fichero tools.go en la raíz del proyecto con el siguiente código:

//go:build tools
// +build tools

package tools

import (
    _ "github.com/99designs/gqlgen"
)

A continuación corremos el comando para installar la dependencia.

go mod tidy

Por defecto, instalará la última versión de gqlgen. Ahora corremos el comando para generar el esqueleto del proyecto.

go run github.com/99designs/gqlgen init

Este comando generará el siguiente árbol de ficheros.

├── go.mod
├── go.sum
├── gqlgen.yml               - The gqlgen config file, knobs for controlling the generated code.
├── graph
│   ├── generated            - A package that only contains the generated runtime
│   │   └── generated.go
│   ├── model                - A package for all your graph models, generated or otherwise
│   │   └── models_gen.go
│   ├── resolver.go          - The root graph resolver type. This file wont get regenerated
│   ├── schema.graphqls      - Some schema. You can split the schema into as many graphql files as you like
│   └── schema.resolvers.go  - the resolver implementation for schema.graphql
└── server.go

El fichero graph/schema.graphqls es donde se definen los tipos de datos para nuestros proyecto. Por defecto genera los esquemas para un ToDo. Vamos a modificarlos con los tipos de datos para nuestro proyecto.

Definición de tipos de datos

Abre el fichero graph/schema.graphqls y reemplaza el código por defecto con el siguiente:

type User {
  id: Int!
  name: String!
  lastname: String!
  posts: [Post!]!
}

type Post {
  id: Int!
  title: String!
  text: String!
  user: User!
}

input inputUser {
  name: String!
  lastname: String!
}

input inputPost {
  title: String!
  text: String!
  userId: Int!
}

type Query {
  users: [User!]!
  user(id: Int!): User!

  posts: [Post!]!
  post(id: Int!): Post!
}

type Mutation {
  createUser(user: inputUser!): User!
  updateUser(id: Int!, user: inputUser!): User!
  deleteUser(id: Int!): User!

  createPost(post: inputPost!): Post!
  updatePost(id: Int!, post: inputPost!): Post!
  deletePost(id: Int!): Post!
}

Este código define un esquema GraphQL básico con dos tipos principales, User y Post, y las operaciones de consulta (Query) y mutación (Mutation) asociadas. Además, se definen dos tipos de entrada (inputUser e inputPost) que se utilizan en las mutaciones para proporcionar datos al crear o actualizar usuarios y posts.

A continuación debemos correr el comando para que gqlgen genere los resolvers para cada una de las operaciones definidas en el fichero.

Antes de ejecutar el comando, es conveniente que abras el fichero graph/schema.resolvers.go y borres todo el contenido, ya que puede producir algunos problemas a la hora de generar los resolvers.

go run github.com/99designs/gqlgen generate

Si todo va bien, al abrir el fichero graph/schema.resolvers.go deberías ver el siguiente código:

package graph

// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
// Code generated by github.com/99designs/gqlgen version v0.17.40

import (
    "context"
    "fmt"

    "github.com/alegrecode/graphql-api-users-posts/graph/model"
)

// CreateUser is the resolver for the createUser field.
func (r *mutationResolver) CreateUser(ctx context.Context, user model.InputUser) (*model.User, error) {
    panic(fmt.Errorf("not implemented: CreateUser - createUser"))
}

// UpdateUser is the resolver for the updateUser field.
func (r *mutationResolver) UpdateUser(ctx context.Context, id int, user model.InputUser) (*model.User, error) {
    panic(fmt.Errorf("not implemented: UpdateUser - updateUser"))
}

// DeleteUser is the resolver for the deleteUser field.
func (r *mutationResolver) DeleteUser(ctx context.Context, id int) (*model.User, error) {
    panic(fmt.Errorf("not implemented: DeleteUser - deleteUser"))
}

// CreatePost is the resolver for the createPost field.
func (r *mutationResolver) CreatePost(ctx context.Context, post model.InputPost) (*model.Post, error) {
    panic(fmt.Errorf("not implemented: CreatePost - createPost"))
}

// UpdatePost is the resolver for the updatePost field.
func (r *mutationResolver) UpdatePost(ctx context.Context, id int, post model.InputPost) (*model.Post, error) {
    panic(fmt.Errorf("not implemented: UpdatePost - updatePost"))
}

// DeletePost is the resolver for the deletePost field.
func (r *mutationResolver) DeletePost(ctx context.Context, id int) (*model.Post, error) {
    panic(fmt.Errorf("not implemented: DeletePost - deletePost"))
}

// Users is the resolver for the users field.
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
    panic(fmt.Errorf("not implemented: Users - users"))
}

// User is the resolver for the user field.
func (r *queryResolver) User(ctx context.Context, id int) (*model.User, error) {
    panic(fmt.Errorf("not implemented: User - user"))
}

// Posts is the resolver for the posts field.
func (r *queryResolver) Posts(ctx context.Context) ([]*model.Post, error) {
    panic(fmt.Errorf("not implemented: Posts - posts"))
}

// Post is the resolver for the post field.
func (r *queryResolver) Post(ctx context.Context, id int) (*model.Post, error) {
    panic(fmt.Errorf("not implemented: Post - post"))
}

// Mutation returns MutationResolver implementation.
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }

// Query returns QueryResolver implementation.
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }

type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }

Este código proporciona la estructura inicial para tus resolvers, generada por gqlgen. Deberemos completar la lógica dentro de cada resolver para que realice las operaciones reales, como crear, actualizar o eliminar datos en nuestra aplicación según lo definido en el esquema GraphQL.

Si abres el fichero graph/model/models_gen.go podrás ver el siguiente código generado:

// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.

package model

type Post struct {
    ID    int    `json:"id"`
    Title string `json:"title"`
    Text  string `json:"text"`
    User  *User  `json:"user"`
}

type User struct {
    ID       int     `json:"id"`
    Name     string  `json:"name"`
    Lastname string  `json:"lastname"`
    Posts    []*Post `json:"posts"`
}

type InputPost struct {
    Title  string `json:"title"`
    Text   string `json:"text"`
    UserID int    `json:"userId"`
}

type InputUser struct {
    Name     string `json:"name"`
    Lastname string `json:"lastname"`
}

Este código define los modelos de datos utilizados en nuestra aplicación GraphQL generada por gqlgen. Estos modelos representan los tipos de datos que se utilizan en las operaciones GraphQL de la aplicación.

Es importante destacar que este código es generado automáticamente por gqlgen y no debes editarlo directamente.

Sin embargo, esto no es conveniente ya que más adelante, debemos modificar los modelos para crear la relación "one to many" cuando utilicemos GORM.

Para resolver esto, vamos a crear nuestros modelos en ficheros a parte y luego le diremos a gqlgen que utilice esos modelos.

Creación de los modelos

Comencemos creando el modelo User . Crea un fichero graph/model/user.go y copiemos el código del type User generado en graph/model/models_gen.go .

package model

type User struct {
    ID       int     `json:"id"`
    Name     string  `json:"name"`
    Lastname string  `json:"lastname"`
    Posts    []*Post `json:"posts"`
}

Ahora creamos el modelo Post con el mismo procedimiento. Crea un fichero graph/model/post.go .

package model

type Post struct {
    ID    int    `json:"id"`
    Title string `json:"title"`
    Text  string `json:"text"`
    User  *User  `json:"user"`
}

Ya tenemos nuestros modelos, ahora debemos decirle a gqlgen que utilice estos modelos en lugar de que los genere autmáticamente.

Para ello abre el fichero gqlgen.yml y tienes que descomentar la siguiente línea:

autobind:
 - "github.com/[tu-nombre-de-usuario]/graphql-api-users-posts/graph/model"

La directiva autobind especifica el paquete o módulo que contiene los modelos de datos (estructuras Go) que serán utilizados por gqlgen para generar automáticamente código GraphQL y resolvers.

Luego, en la sección "models", añade las siguientes líneas:

models:
  Post:
    model: github.com/[tu-nombre-de-usuario]/graphql-api-users-posts/graph/model.Post
  User:
    model: github.com/[tu-nombre-de-usuario]/graphql-api-users-posts/graph/model.User

La sección models especifica las configuraciones para cada tipo de modelo en la aplicación GraphQL. En este caso, se mencionan los dos modelos: Post y User. La clave model indica la ubicación del modelo en el código fuente del proyecto.

Finalmente, entra al fichero graph/schema.resolvers.go , borra el contenido y vuelve a correr el comando para generar los resolvers.

go run github.com/99designs/gqlgen generate

Si todo sale bien, al abrir el fichero graph/model/models_gen.go podrás ver el siguiente código:

// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.

package model

type InputPost struct {
    Title  string `json:"title"`
    Text   string `json:"text"`
    UserID int    `json:"userId"`
}

type InputUser struct {
    Name     string `json:"name"`
    Lastname string `json:"lastname"`
}

Como puedes ver, sólo se generaron los tipos de entrada de datos para los posts y los usuarios; esto significa que gqlgen está utilizando los modelos que creamos para generar los resolvers.

Esto es importante ya que podremos realizar las modificaciones en los modelos para crear la relación "one to many" cuando creemos la conexión a la base de datos con GORM.

Conexión a la base de datos

Comenzamos creando la base de datos. Entra a tu gestor y crea uno para nuestro proyecto.

Lo llamé graphql_users_posts y el cotejamiento, como lo recomienda la documentación de GORM, utf8mb4 .

Ahora instalamos el paquete de GORM y driver para MySQL.

go get -u gorm.io/gorm

go get gorm.io/driver/mysql

Seguimos creando el fichero database/database.go para establecer la conexión a la base de datos.

package database

import (
    "github.com/alegrecode/graphql-api-users-posts/graph/model"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

var DB *gorm.DB

func Conexion() {
    dsn := "root:@tcp(127.0.0.1:3306)/graphql_users_posts?charset=utf8mb4&parseTime=True&loc=Local"
    DB, _ = gorm.Open(mysql.Open(dsn), &gorm.Config{})

    DB.AutoMigrate(&model.User{}, &model.Post{})

}

Este código establece la conexión a la base de datos y corre las migraciones para crear las tablas en la base de datos. Por defecto, creará las tablas users y posts.

La variable global llamada DB *gorm.DB almacenará la conexión a la base de datos. Al ser global, podremos importalo en otros ficheros.

Relación One To Many

Abre el fichero graph/model/post.go y agrega la línea UserID int `json:"userId"` para establecer la relación one to many. Debe quedar así.

package model

type Post struct {
    ID     int    `json:"id"`
    Title  string `json:"title"`
    Text   string `json:"text"`
    User   *User  `json:"user"`
    UserID int    `json:"userId"`
}

Ahora abre el fichero graph/model/user.go y realiza la siguiente modificación para especificar que al actualizar o eliminar la entidad principal (User), las operaciones deben realizarse en cascada en los posts asociados en la base de datos. Debe quedar como sigue.

package model

type User struct {
    ID       int     `json:"id"`
    Name     string  `json:"name"`
    Lastname string  `json:"lastname"`
    Posts    []*Post `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"posts"`
}

gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;": Esta etiqueta se utiliza para especificar las restricciones de la base de datos para esta relación. En particular, indica que cuando se actualiza o elimina la entidad principal, se deben realizar operaciones en cascada en los posts asociados.

  • OnUpdate:CASCADE: Indica que cuando la entidad principal se actualiza, las actualizaciones deben propagarse en cascada a los posts asociados.

  • OnDelete:CASCADE: Indica que cuando la entidad principal se elimina, la eliminación debe propagarse en cascada a los posts asociados.

Esta etiqueta es importante porque si al intentar borrar un usuario, y este usuario tiene registros asociados en la tabla posts, se generará un error de integridad de datos, ya que los posts asociados no pueden quedar huérfanos. Al definir OnDelete:CASCADE indicamos que si el registro de usuario es borrado, deben borrarse de forma automática todos los registros asociados en la tabla posts.

Ahora abre el fichero server.go y agrega la llamada a la función para establecer la conexión y correr las migraciones.

package main

import (
    "log"
    "net/http"
    "os"

    "github.com/99designs/gqlgen/graphql/handler"
    "github.com/99designs/gqlgen/graphql/playground"
    "github.com/alegrecode/graphql-api-users-posts/database"
    "github.com/alegrecode/graphql-api-users-posts/graph"
)

const defaultPort = "8080"

func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = defaultPort
    }

    // Realiza la conexión a la BD y corre las migraciones
    database.Conexion()

    srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}}))

    http.Handle("/", playground.Handler("GraphQL playground", "/query"))
    http.Handle("/query", srv)

    log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
    log.Fatal(http.ListenAndServe(":"+port, nil))
}

Ahora es un buen momento para probar si se realiza la conexión a la base de datos de forma correcta, y corren las migraciones de las tablas y se establece la relación "one to many" entre las tablas. Ejecuta el siguiente comando.

go run server.go

Si sale todo bien, al abrir su gestor de base de datos debe ver las tablas users y posts, con su correspondiente relación.

Ahora debemos empezar a crear las implementaciones en cada uno de los resolvers, pero primero debemos hacer que la conexión a la base de datos esté disponible en los resolvers. Para ello abre el fichero graph/resolver.go y añade un campo DB *gorm.DB .

package graph

import "gorm.io/gorm"

type Resolver struct {
    DB *gorm.DB
}

Ahora abre el fichero server.go y le pasamos la conexión a la base de datos al resolver como sigue.

srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{ 
        DB: database.DB, // Pasamos la conexión al resolver
    }}))

Ya tenemos todo listo para comenzar a crear las implementaciones.

Crear un usuario

Para crear un nuevo usuario en la función CreateUser agrega el siguiente código.

func (r *mutationResolver) CreateUser(ctx context.Context, user model.InputUser) (*model.User, error) {
    newUser := model.User{
        Name:     user.Name,
        Lastname: user.Lastname,
    }

    r.DB.Create(&newUser)
    return &newUser, nil
}

Este código representa la lógica para crear un nuevo usuario en la base de datos utilizando Gorm en respuesta a una mutación GraphQL. La estructura InputUser proporciona los datos necesarios para la creación del usuario, y el nuevo usuario creado se retorna como parte de la respuesta de la mutación.

Si ejecutas la aplicación, puedes probarlo de la siguiente manera.

Actualizar un usuario

Para actualizar un usuario existente copia en la función UpdateUser el siguiente código.

func (r *mutationResolver) UpdateUser(ctx context.Context, id int, user model.InputUser) (*model.User, error) {
    updatedUser := model.User{
        ID:       id,
        Name:     user.Name,
        Lastname: user.Lastname,
    }

    r.DB.Save(&updatedUser)
    return &updatedUser, nil
}

El ID del usuario y los nuevos valores se toman de los argumentos de la mutación, y el usuario actualizado se retorna como parte de la respuesta de la mutación.

Podemos probar actualizar un usuario como sigue.

Continuamos con la mutación para crear un nuevo post.

Crear un nuevo post

Para crear un post nuevo, en la función CreatePost ingresa el siguiente código.

func (r *mutationResolver) CreatePost(ctx context.Context, post model.InputPost) (*model.Post, error) {
    newPost := model.Post{
        Title:  post.Title,
        Text:   post.Text,
        UserID: post.UserID,
    }
    r.DB.Create(&newPost)
    return &newPost, nil
}

Se crea una nueva instancia del modelo Post (newPost). Los valores de la nueva publicación se toman de los campos de la estructura InputPost pasada como argumento.

Se asignan valores a los campos Title, Text, y UserID para establecer la relación con el ID del usuario.

Se utiliza el objeto r.DB para realizar la operación de creación en la base de datos. El puntero &newPost se pasa como argumento para indicar que se debe crear un nuevo registro en la base de datos con los valores proporcionados en newPost.

La publicación recién creada se retorna como parte de la respuesta de la mutación.

Podemos probarlo como sigue.

Continuamos con la actualización de un post.

Actualizar un post

Para actualizar un post, en la función UpdatePost añade el siguiente código.

func (r *mutationResolver) UpdatePost(ctx context.Context, id int, post model.InputPost) (*model.Post, error) {
    updatedPost := model.Post{
        ID:     id,
        Title:  post.Title,
        Text:   post.Text,
        UserID: post.UserID,
    }

    r.DB.Save(&updatedPost)
    return &updatedPost, nil
}

Se crea una nueva instancia del modelo Post (updatedPost). Se asigna el ID de la publicación que se desea actualizar y se actualizan los campos Title, Text, y UserID con los valores proporcionados en la estructura InputPost pasada como argumento.

Se utiliza el objeto r.DB para realizar la operación de actualización en la base de datos. El puntero &updatedPost se pasa como argumento para indicar que se debe actualizar el registro en la base de datos con los nuevos valores proporcionados en updatedPost.

La publicación actualizada se retorna como parte de la respuesta de la mutación.

Lo probamos como sigue.

Seguimos con las consultas para obtener todo los usuarios y un usuario por su ID.

Recuperar todos los usuarios

Para obtener todos lo usuarios de la base de datos, en la función Users añade el siguiente código.

func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
    var users []*model.User
    r.DB.Preload("Posts").Find(&users)
    return users, nil
}

Se declara una variable llamada users que será una slice de punteros a objetos model.User. Esta slice almacenará los resultados de la consulta.

Se utiliza el objeto r.DB para realizar la operación de consulta en la base de datos. Preload("Posts") se utiliza para realizar la precarga de los posts asociados a cada usuario, evitando así la necesidad de realizar una consulta separada por cada usuario para obtener sus posts. Find(&users) ejecuta la consulta y almacena los resultados en la slice users.

Puedes probarlo como sigue.

Recuperamos todos los usuarios y los post que están asociados a cada uno.

Recuperar un usuario por su ID

Para obtener un usuario por su ID, en la función User agrega el siguiente código.

func (r *queryResolver) User(ctx context.Context, id int) (*model.User, error) {
    user := model.User{}
    r.DB.Preload("Posts").First(&user, id)
    return &user, nil
}

Se declara una variable llamada user que será un objeto model.User. Esta variable almacenará el resultado de la consulta.

Se utiliza el objeto r.DB para realizar la operación de consulta en la base de datos. Preload("Posts") se utiliza para realizar la precarga de los posts asociados al usuario.

First(&user, id) ejecuta la consulta para obtener el primer usuario con el ID proporcionado y almacena el resultado en la variable user.

Se retorna un puntero al usuario &user, que contiene la información del usuario con sus posts precargados.

Probamos la consulta como sigue.

Recuperamos el usuario por su ID, y los posts que tiene asociados.

Seguimos con los posts.

Recuperar todos los posts

Para obtener todos los posts, en la función Posts agrega el siguiente código.

func (r *queryResolver) Posts(ctx context.Context) ([]*model.Post, error) {
    var posts []*model.Post
    r.DB.Preload("User").Find(&posts)
    return posts, nil
}

Se declara una variable llamada posts que será una slice de punteros a objetos model.Post. Esta slice almacenará los resultados de la consulta.

Se utiliza el objeto r.DB para realizar la operación de consulta en la base de datos. Preload("User") se utiliza para realizar la precarga de los usuarios asociados a cada post, evitando así la necesidad de realizar una consulta separada por cada publicación para obtener sus usuarios. Find(&posts) ejecuta la consulta y almacena los resultados en la slice posts.

Puedes probar la consulta como sigue.

Recuperamos todos los posts y al usuario al que pertenece.

Recuperar un post por su ID

Para obtener un post por su ID, en la función Post agrega el siguiente código.

func (r *queryResolver) Post(ctx context.Context, id int) (*model.Post, error) {
    post := model.Post{}
    r.DB.Preload("User").First(&post, id)
    return &post, nil
}

Se declara una variable llamada post que será un objeto model.Post. Esta variable almacenará el resultado de la consulta.

Se utiliza el objeto r.DB para realizar la operación de consulta en la base de datos. Preload("User") se utiliza para realizar la precarga del usuario asociado al post. First(&post, id) ejecuta la consulta para obtener el primera post con el ID proporcionado y almacena el resultado en la variable post.

Se retorna un puntero al post &post, que contiene la información del post con su usuario precargado.

Puedes probarlo como sigue.

Recuperamos un post por su ID y el usuario asociado.

Continuamos con la eliminación de usarios y post.

Eliminación de un post

Para borrar un post de la base de datos, en la función DeletePost agrega el siguiente código.

func (r *mutationResolver) DeletePost(ctx context.Context, id int) (*model.Post, error) {
    deletedPost := model.Post{}
    r.DB.First(&deletedPost, id)
    r.DB.Delete(&deletedPost)
    return &deletedPost, nil
}

Se declara una variable llamada deletedPost que será un objeto model.Post. Esta variable se utilizará para almacenar el post que será eliminado.

Se utiliza el objeto r.DB para realizar la operación de consulta en la base de datos. First(&deletedPost, id) ejecuta la consulta para obtener el primer post con el ID proporcionado y almacena el resultado en la variable deletedPost.

Se utiliza el objeto r.DB nuevamente para realizar la operación de eliminación en la base de datos. Delete(&deletedPost) elimina el registro correspondiente al post almacenado en deletedPost.

Se retorna un puntero al post &deletedPost, que contiene la información del post que fue eliminado.

Puedes probarlo como sigue.

Logramos borrar un registro de la tabla posts por medio de su ID.

Eliminación de un usuario

Para borrar un usuario de la base de datos, en la función DeleteUser agrega el siguiente código.

func (r *mutationResolver) DeleteUser(ctx context.Context, id int) (*model.User, error) {
    deletedUser := model.User{}
    r.DB.First(&deletedUser, id)
    r.DB.Delete(&deletedUser)
    return &deletedUser, nil
}

Se declara una variable llamada deletedUser que será un objeto model.User. Esta variable se utilizará para almacenar el usuario que será eliminado.

Se utiliza el objeto r.DB para realizar la operación de consulta en la base de datos. First(&deletedUser, id) ejecuta la consulta para obtener el primer usuario con el ID proporcionado y almacena el resultado en la variable deletedUser.

Se utiliza el objeto r.DB nuevamente para realizar la operación de eliminación en la base de datos. Delete(&deletedUser) elimina el registro correspondiente al usuario almacenado en deletedUser.

Se retorna un puntero al usuario &deletedUser, que contiene la información del usuario que fue eliminado.

Puedes probarlo como sigue.

Logramos borrar un registro de la tabla users por medio de su ID.

Conclusión

En este tutorial, hemos explorado cómo construir una API GraphQL en Go utilizando gqlgen para definir el esquema GraphQL y Gorm para interactuar con una base de datos MySQL. A lo largo de este proceso, hemos cubierto los conceptos fundamentales de GraphQL, la configuración de modelos de datos con Gorm, y la implementación de operaciones CRUD para usuarios y publicaciones.

Algunos aspectos destacados de este tutorial incluyen la creación de modelos GraphQL con gqlgen, la implementación de resolvers para manejar operaciones CRUD, y la integración de Gorm para persistencia de datos. Además, hemos explorado la importancia de las etiquetas gorm para personalizar el mapeo de estructuras de Go a tablas de base de datos y para definir restricciones importantes.

Espero que este tutorial haya sido útil y te haya proporcionado una base sólida para construir aplicaciones GraphQL robustas en Go.

Puedes encontrar el proyecto completo en mi repositorio de GitHub haciendo clic AQUÍ.

Espero que lo hayas encontrado entretenido, instructivo y claro. Si tienes alguna duda, puedes hacérmelo saber en los comentarios. Pronto estaré subiendo más tutoriales.

Nos vemos en la próxima. Saludos!👋😊