# API RESTful en Go

## Introducción

[Fiber](https://gofiber.io/) es un framework web rápido y flexible, inspirado en Express.js, diseñado para facilitar la construcción de aplicaciones y APIs RESTful.

[GORM](https://gorm.io/) es una biblioteca ORM (Object Relational Mapping) que simplifica la interacción con bases de datos, proporcionando una interfaz elegante y eficiente para realizar operaciones de CRUD en bases de datos.

En este tutorial, exploraremos cómo integrar Fiber y GORM para construir una API RESTful en Go. Aprenderemos a configurar un servidor Fiber, establecer la conexión con una base de datos utilizando GORM y crear rutas y controladores para gestionar los recursos de nuestra API.

## Proyecto

Crearemos una API RESTful centrado en dos modelos: Author (autor) y Book (libro), con una relación uno a muchos, donde un autor puede tener muchos libros, y un libro pertenece a un autor.

La API nos permitirá gestionar todas las operaciones CRUD (Create, Read, Update, Delete) de ambos modelos para una base de datos MySQL.

## Instalación de paquetes

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

```bash
mkdir api_fiber
cd api_fiber
```

Llamé al directorio `api_fiber` , una vez dentro de la carpeta, inicializamos el módulo de GO.

```plaintext
go mod init api_fiber
```

Ahora realizamos la instalación de las dependencias de Fiber, GORM y el driver de MySQL.

```plaintext
go get -u github.com/gofiber/fiber/v2
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
```

## Base de datos

Abre tu gestor de base de datos y crea uno para nuestro proyecto. Yo lo llamé `api_fiber` .

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685229355738/2e79eb3c-2d8b-44c6-b3b5-b065f008beba.png align="center")

## Estructura de ficheros

Para modularizar el código del proyecto, usaremos la siguiente estructura de carpetas.

```plaintext
- main.go
- api/
    - routes.go
- models/
    - author.go
    - book.go
- database/
    - connection.go
- controllers/
    - author_controller.go
    - book_controller.go
```

El archivo `main.go` se encuentra en la raíz del proyecto y es el archivo principal que inicializa el servidor.

La carpeta `api` contiene el archivo `routes.go`, donde se definen las rutas de la API.

La carpeta `models` contiene los archivos `author.go` y `book.go`, donde se definen los modelos Author y Book.

La carpeta `database` contiene el archivo `connection.go`, donde se establece la conexión a la base de datos.

La carpeta `controllers` contiene los archivos `author_controller.go` y `book_controller.go`, donde se definen los controladores para cada modelo.

## Conexión a la base de datos

Abre el fichero `database/connection.go` y escribe el siguiente código:

```go
package database

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var DB *gorm.DB

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

	return DB
}
```

Lo que podemos destacar aquí es la definición de la variable `DB` de tipo `*gorm.DB` . Lo hacemos así para poder acceder a la instancia de la conexión desde otros módulos.

La función `ConnectDB` establece la conexión con la base de datos y retorna un objecto `*gorm.DB` el cuál se asigna a la variable `DB` .

## Modelos

Comenzamos con el fichero `models/author.go` para el modelo autor.

```go
package models

import "gorm.io/gorm"

type Author struct {
	gorm.Model

	Name     string `json:"name"`
	Lastname string `json:"lastname"`

	Books []Book `gorm:"foreignKey:AuthorID"`
}
```

Definimos la estructura `Author` tiene los siguientes campos:

* `gorm.Model`: Este campo incorpora los campos de `ID`, `CreatedAt`, `UpdatedAt` y `DeletedAt` proporcionados por GORM para realizar operaciones básicas de persistencia.
    
* `Name`: Representa el nombre del autor. Está etiquetado con `json:"name"`, lo que indica que este campo se serializará como `"name"` al interactuar con la API RESTful.
    
* `Lastname`: Representa el apellido del autor. Al igual que el campo `Name`, está etiquetado con `json:"lastname"` para la serialización adecuada.
    
* `Books`: Es un slice (una lista) de objetos `Book`. Esta relación uno a muchos se establece mediante la etiqueta `gorm:"foreignKey:AuthorID"`, que indica que el campo `AuthorID` en el modelo `Book` se utilizará como clave externa para la relación con `Author`. Esto significa que un autor puede tener muchos libros.
    

Ahora continuamos con el fichero `models/book.go` .

```go
package models

import "gorm.io/gorm"

type Book struct {
	gorm.Model

	Title       string `json:"title"`
	Description string `json:"description"`
	AuthorID    uint
	Author      Author `json:"author"`
}
```

La estructura `Book` tiene los siguientes campos:

* `gorm.Model`: Al igual que en el modelo `Author`, este campo incorpora los campos de `ID`, `CreatedAt`, `UpdatedAt` y `DeletedAt` proporcionados por GORM para realizar operaciones básicas de persistencia.
    
* `Title`: Representa el título del libro. Está etiquetado con `json:"title"`, lo que indica que este campo se serializará como `"title"` al interactuar con la API RESTful.
    
* `Description`: Representa la descripción del libro. Al igual que el campo `Title`, está etiquetado con `json:"description"` para la serialización adecuada.
    
* `AuthorID`: Es un campo de tipo `uint` que representa el ID del autor asociado al libro. Este campo se utilizará como clave externa para establecer la relación con el modelo `Author`.
    
* `Author`: Es un objeto del tipo `Author` que representa al autor del libro. Está etiquetado con `json:"author"` para la serialización adecuada al interactuar con la API RESTful.
    

## Configuración del servidor Fiber

Abrimos el fichero principal de nuestra aplicación `main.go` .

```go
package main

import (
	"github.com/gofiber/fiber/v2"

	"github.com/alegrecode/api_fiber/api"
	"github.com/alegrecode/api_fiber/database"
	"github.com/alegrecode/api_fiber/models"
)

func main() {
	app := fiber.New()

	db := database.ConnectDB()

	db.AutoMigrate(&models.Author{}, &models.Book{})

	api.Router(app)

	app.Listen(":3000")
}
```

Creamos una instancia de la aplicación Fiber con `fiber.New()` y lo guardamos en la variable `app` .

Realizamos la conexión a la base de datos con `database.ConnectDB()` y guardamos dicha conexión en la variable `db`.

Corremos la función `db.AutoMigrate()` de GORM para realizar las migraciones de los modelos `Author` y `Book` y crear las respectivas tablas en la base de datos.

La función `api.Router()` corresponde la enrutador de nuestra aplicación, que pasaremos a configurar más tarde. Recibe como parámetro la instancia de `Fiber` .

Finalmente, se inicia el servidor web utilizando `app.Listen(":3000")`, lo que indica que el servidor escuchará en el puerto 3000.

## Controladores

Abrimos el fichero `controllers/author_controller.go` y escribimos el siguiente código.

```go
package controllers

import (
	"github.com/gofiber/fiber/v2"

	"github.com/alegrecode/api_fiber/database"
	"github.com/alegrecode/api_fiber/models"
)

type AuthorController struct {
}

func (ac *AuthorController) GetAllAuthors(c *fiber.Ctx) error {
	var authors []models.Author
	database.DB.Preload("Books").Find(&authors)
	return c.JSON(authors)
}
```

Definimos la estructura `AuthorController` sin campos que actuará como contenedor de las funciones relacionadas con los autores. Luego le añadimos la función `GetAllAuthors` que se encargará de obtener todos los autores. Recibe un parámetro `c` de tipo `*fiber.Ctx`, que representa el contexto de la solicitud HTTP.

Declaramos una variable `authors` de tipo `[]`[`models.Author`](http://models.Author), que será utilizada para almacenar los autores obtenidos de la base de datos.

La función `database.DB.Preload("Books").Find(&authors)` recupera todos los autores de la base de datos. La función `Preload("Books")` se utiliza para cargar automáticamente los libros relacionados con cada autor en la consulta.

Finalmente, se utiliza `c.JSON(authors)` para enviar una respuesta JSON al cliente con los autores obtenidos de la base de datos.

Ahora pasamos a crear la función para obtener un autor por su `ID` .

```go
func (ac *AuthorController) GetAuthor(c *fiber.Ctx) error {
	id := c.Params("id")
	var author models.Author

	result := database.DB.Preload("Books").First(&author, id)

	if result.Error != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"message": "Author not found",
		})
	}

	return c.JSON(author)
}
```

Obtenemos el parámetro `id` de la URL utilizando `c.Params("id")`.

La variable `author` de tipo [`models.Author`](http://models.Author) se utilizará para almacenar el autor obtenido de la base de datos.

La función `database.DB.Preload("Books").First(&author, id)` realiza una consulta a la base de datos y obtiene el primer autor que coincida con el ID especificado.

Verificamos si hay algún error en la consulta utilizando `result.Error`. Si hay un error, se devuelve una respuesta JSON con un estado de `fiber.StatusNotFound` y un mensaje de "Author not found".

Si no hay errores, se devuelve una respuesta JSON con el autor obtenido utilizando `c.JSON(author)`.

Añadimos el código para crear un nuevo autor.

```go
func (ac *AuthorController) CreateAuthor(c *fiber.Ctx) error {

	author := new(models.Author)

	if err := c.BodyParser(author); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"message": "Could not create author",
			"error":   err.Error(),
		})
	}

	database.DB.Create(&author)
	return c.JSON(author)
}
```

La función `CreateAuthor` maneja la solicitud de crear un nuevo autor. Utiliza `c.BodyParser` para deserializar los datos JSON recibidos en la solicitud y asignarlos a una instancia del modelo `Author`. Luego, utiliza `database.DB.Create` para guardar el nuevo autor en la base de datos. Se envía una respuesta JSON con el autor creado si todo es exitoso, o una respuesta de error si hay algún problema en el análisis o en la creación del autor en la base de datos.

Ahora vamos con la función para actualizar un autor.

```go
func (ac *AuthorController) UpdateAuthor(c *fiber.Ctx) error {
	id := c.Params("id")
	var author models.Author

	result := database.DB.First(&author, id)

	if result.Error != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"message": "Author not found",
		})
	}

	if err := c.BodyParser(&author); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"message": "Could not update author",
			"error":   err.Error(),
		})
	}

	database.DB.Model(&author).Updates(&author)

	return c.JSON(author)
}
```

La función `UpdateAuthor` gestiona la actualización de un autor existente. Utiliza `database.DB.First` para obtener el autor existente de la base de datos por su ID. Luego, utiliza `c.BodyParser` para analizar los datos JSON recibidos en la solicitud y asignarlos a la instancia del autor existente. A continuación, utiliza `database.DB.Model(...).Updates(...)` para actualizar los campos del autor en la base de datos. Se envía una respuesta JSON con el autor actualizado si todo es exitoso, o una respuesta de error si el autor no se encuentra en la base de datos o hay algún problema en el análisis o en la actualización del autor.

Por último creamos la función para borrar un autor.

```go
func (ac *AuthorController) DeleteAuthor(c *fiber.Ctx) error {
	id := c.Params("id")
	var author models.Author

	result := database.DB.First(&author, id)

	if result.Error != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"message": "Author not found",
		})
	}

	database.DB.Delete(&author)

	return c.Status(fiber.StatusOK).JSON(fiber.Map{
		"message": "Author deleted",
	})
}
```

La función `DeleteAuthor` se encarga de eliminar un autor existente. Utiliza `database.DB.First` para obtener el autor existente de la base de datos por su ID. Luego, utiliza `database.DB.Delete` para eliminar el autor de la base de datos. Se envía una respuesta JSON con un mensaje de "Author deleted" si todo es exitoso, o una respuesta de error si el autor no se encuentra en la base de datos.

Con esto finalizamos todas las operaciones CRUD para los autores.

A continuación abrimos el fichero `controllers/book_controller.go` y añadimos el siguiente código.

```go
package controllers

import (
	"github.com/alegrecode/api_fiber/database"
	"github.com/alegrecode/api_fiber/models"
	"github.com/gofiber/fiber/v2"
)

type BookController struct {
}

func (bc *BookController) CreateBook(c *fiber.Ctx) error {
	var book models.Book
	var author models.Author

	if err := c.BodyParser(&book); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"message": "Could not create book",
			"error":   err.Error(),
		})
	}

	result := database.DB.First(&author, book.AuthorID)

	if result.Error != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"message": "Invalid author ID",
		})
	}

	book.Author = author
	database.DB.Create(&book)
	return c.JSON(book)
}

func (bc *BookController) GetAllBooks(c *fiber.Ctx) error {
	var books []models.Book
	database.DB.Preload("Author").Find(&books)
	return c.JSON(books)
}

func (bc *BookController) GetBook(c *fiber.Ctx) error {
	id := c.Params("id")
	var book models.Book
	result := database.DB.Preload("Author").First(&book, id)
	if result.Error != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"message": "Book not found",
		})
	}
	return c.JSON(book)
}

func (bc *BookController) UpdateBook(c *fiber.Ctx) error {
	id := c.Params("id")
	var book models.Book

	result := database.DB.First(&book, id)

	if result.Error != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"message": "Invalid book ID",
		})
	}

	if err := c.BodyParser(&book); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"message": "Could not update author",
			"error":   err.Error(),
		})
	}

	database.DB.Model(&book).Updates(&book)

	return c.JSON(book)
}

func (bc *BookController) DeleteBook(c *fiber.Ctx) error {
	id := c.Params("id")
	var book models.Book

	result := database.DB.First(&book, id)

	if result.Error != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"message": "Invalid book ID",
		})
	}

	database.DB.Delete(&book)
	return c.Status(fiber.StatusOK).JSON(fiber.Map{
		"message": "Book deleted",
	})
}
```

Como se puede observar las funciones para las operaciones CRUD de los libros son muy similares a las funciones del controlador para los autores, con excepción de la función `CreateBook` , el cual contiene la siguiente validación.

```go
result := database.DB.First(&author, book.AuthorID)

	if result.Error != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"message": "Invalid author ID",
		})
	}
```

Este fragmento de código verifica que el autor asociado al libro exista en la base de datos, si el autor no existe, retorna un mensaje de error.

Finalizamos con los controladores, ahora seguimos con el enrutador.

## Enrutador

Abrimos el fichero `api/router.go` y añadimos el siguiente código.

```go
package api

import (
	"github.com/alegrecode/api_fiber/controllers"
	"github.com/gofiber/fiber/v2"
)

func Router(app *fiber.App) {

	var authorController = &controllers.AuthorController{}
	var bookController = &controllers.BookController{}

	app.Get("/authors", authorController.GetAllAuthors)

	app.Post("/author", authorController.CreateAuthor)

	app.Get("/author/:id", authorController.GetAuthor)

	app.Put("/author/:id", authorController.UpdateAuthor)

	app.Delete("/author/:id", authorController.DeleteAuthor)

	app.Get("/books", bookController.GetAllBooks)

	app.Post("/book", bookController.CreateBook)

	app.Get("/book/:id", bookController.GetBook)

	app.Put("/book/:id", bookController.UpdateBook)

	app.Delete("/book/:id", bookController.DeleteBook)
}
```

La función `Router` se encarga de configurar las rutas de la API. Inicializamos los controladores `AuthorController` y `BookController` utilizando `&controllers.AuthorController{}` y `&controllers.BookController{}`, respectivamente.

Definimos las rutas para realizar las operaciones CRUD de autores y libros y los asociamos a su controlador correspondiente.

## Probando el API

Para correr el servidor ejecutamos el siguiente comando.

```plaintext
go run main.go
```

Si todo ha ido bien podrás ver las tablas en tu base de datos.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685568377705/0194468c-2987-41b2-8db0-47c359288ffa.jpeg align="center")

También se puede observar que la realción "uno a muchos" se ha establecido correctamete.

Para probar las operaciones vamos a usar Postman.

Comencemos agragando algunos autores. Voy a añadir dos autores: Agatha Christie y Jack London.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685570259563/035669fc-9611-4e9f-bfe1-95174119a98f.png align="center")

En la parte inferior, podemos ver la respueta JSON que indica que la operación se realizó correctamente.

Lo podemos verificar en la tabla authors.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685570456259/fdc55db7-0715-4a96-9455-c9531c9a2154.png align="center")

Ahora los libros, voy a añadir dos para Agatha Christie, "Asesinato en el Orient Express" y "Muerte en el Nilo", y uno para Jack London, "Colmillo Blanco".

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685914428084/bde358ec-3408-467e-9c04-4e577df3c5fa.png align="center")

Hay que destacar el campo authorID que se encarga de establecer el vínculo con el autor del libro.

Podemos ver la respuesta JSON que indica que la operación se realizó con éxito.

Lo verificamos en la base de datos.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685914633221/417b5378-b0d0-4d56-845c-c0e4444196d7.png align="center")

Observa el último campo `author_id` que apunta hacia el autor correspondiente del libro.

Ahora que tenemos los datos guardados correctamente, vamos a ver como funciona la operación GET.

Primero probamos obtener todos los libros. Realizamos un petición GET a la ruta `localhost:3000/books` . La respuesta JSON es la siguiente.

```json
[
    {
        "ID": 1,
        "CreatedAt": "2023-06-04T18:23:35.193-03:00",
        "UpdatedAt": "2023-06-04T18:23:35.193-03:00",
        "DeletedAt": null,
        "title": "Asesinato en el Orient Express",
        "description": "Misterio",
        "AuthorID": 1,
        "author": {
            "ID": 1,
            "CreatedAt": "2023-05-31T18:54:07.113-03:00",
            "UpdatedAt": "2023-05-31T18:54:07.113-03:00",
            "DeletedAt": null,
            "name": "Agatha",
            "lastname": "Christie",
            "Books": null
        }
    },
    {
        "ID": 2,
        "CreatedAt": "2023-06-04T18:27:31.671-03:00",
        "UpdatedAt": "2023-06-04T18:27:31.671-03:00",
        "DeletedAt": null,
        "title": "Muerte en el Nilo",
        "description": "Misterio",
        "AuthorID": 1,
        "author": {
            "ID": 1,
            "CreatedAt": "2023-05-31T18:54:07.113-03:00",
            "UpdatedAt": "2023-05-31T18:54:07.113-03:00",
            "DeletedAt": null,
            "name": "Agatha",
            "lastname": "Christie",
            "Books": null
        }
    },
    {
        "ID": 3,
        "CreatedAt": "2023-06-04T18:29:05.712-03:00",
        "UpdatedAt": "2023-06-04T18:29:05.712-03:00",
        "DeletedAt": null,
        "title": "Colmillo Blanco",
        "description": "Aventura",
        "AuthorID": 2,
        "author": {
            "ID": 2,
            "CreatedAt": "2023-05-31T18:58:59.357-03:00",
            "UpdatedAt": "2023-05-31T18:58:59.357-03:00",
            "DeletedAt": null,
            "name": "Jack",
            "lastname": "London",
            "Books": null
        }
    }
]
```

Se puede observar que se obtiene los datos de los libros y cada uno de los libros tiene un campo específico `author` , el cual es un objecto con los datos del autor.

Ahora probemos obtener todos los autores. Realizamos la operación GET `localhost:3000/authors` . Obtenemos la siguiente respuesta.

```json
[
    {
        "ID": 1,
        "CreatedAt": "2023-05-31T18:54:07.113-03:00",
        "UpdatedAt": "2023-05-31T18:54:07.113-03:00",
        "DeletedAt": null,
        "name": "Agatha",
        "lastname": "Christie",
        "Books": [
            {
                "ID": 1,
                "CreatedAt": "2023-06-04T18:23:35.193-03:00",
                "UpdatedAt": "2023-06-04T18:23:35.193-03:00",
                "DeletedAt": null,
                "title": "Asesinato en el Orient Express",
                "description": "Misterio",
                "AuthorID": 1,
                "author": {
                    "ID": 0,
                    "CreatedAt": "0001-01-01T00:00:00Z",
                    "UpdatedAt": "0001-01-01T00:00:00Z",
                    "DeletedAt": null,
                    "name": "",
                    "lastname": "",
                    "Books": null
                }
            },
            {
                "ID": 2,
                "CreatedAt": "2023-06-04T18:27:31.671-03:00",
                "UpdatedAt": "2023-06-04T18:27:31.671-03:00",
                "DeletedAt": null,
                "title": "Muerte en el Nilo",
                "description": "Misterio",
                "AuthorID": 1,
                "author": {
                    "ID": 0,
                    "CreatedAt": "0001-01-01T00:00:00Z",
                    "UpdatedAt": "0001-01-01T00:00:00Z",
                    "DeletedAt": null,
                    "name": "",
                    "lastname": "",
                    "Books": null
                }
            }
        ]
    },
    {
        "ID": 2,
        "CreatedAt": "2023-05-31T18:58:59.357-03:00",
        "UpdatedAt": "2023-05-31T18:58:59.357-03:00",
        "DeletedAt": null,
        "name": "Jack",
        "lastname": "London",
        "Books": [
            {
                "ID": 3,
                "CreatedAt": "2023-06-04T18:29:05.712-03:00",
                "UpdatedAt": "2023-06-04T18:29:05.712-03:00",
                "DeletedAt": null,
                "title": "Colmillo Blanco",
                "description": "Aventura",
                "AuthorID": 2,
                "author": {
                    "ID": 0,
                    "CreatedAt": "0001-01-01T00:00:00Z",
                    "UpdatedAt": "0001-01-01T00:00:00Z",
                    "DeletedAt": null,
                    "name": "",
                    "lastname": "",
                    "Books": null
                }
            }
        ]
    }
]
```

Observemos que obtenemos los autores y cada uno tiene un campo específico `Books` el cuál es un array de libros pertenecientes al autor.

Con esto verificamos que la relación entre los modelos Author y Book se configuró correctamente.

Puedes ir probando el resto de las operaciones CRUD, para comprobar que todo funciona de forma adecuada.

## Conclusión

Hemos finalizado con el proyecto. En este tutorial, hemos explorado cómo construir una API REST utilizando Fiber y Gorm en Go. Hemos aprendido a configurar el enrutador de Fiber y a utilizar Gorm como ORM para interactuar con la base de datos. Hemos creado modelos para representar los recursos 'Author' y 'Book', y hemos establecido una relación de uno a muchos entre ellos. Además, hemos implementado las operaciones CRUD básicas para cada recurso utilizando los controladores.

Puedes encontrar el proyecto completo en mi repositorio de GitHub haciendo clic [**AQUÍ**](https://github.com/AlegreCode/api_fiber).

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!👋😊
