Go (Golang) es un lenguaje de programación relativamente nuevo que ha ganado popularidad recientemente.
Es pequeño y estable, fácil de usar y aprender, rápido, compilado (código nativo) y muy utilizado en herramientas y servicios en la nube (Docker, Kubernetes...).
Teniendo en cuenta todos los beneficios que ofrece, no hay razón para no probarlo.
En este tutorial, crearemos una API REST de librería simple.
Tienda del tesoro del programador : https://github.com/Jackpopc/CS-Books-Store
1. Preparativos
Antes de comenzar, debemos hacer algunos preparativos con anticipación:
-
navegador
-
gorila/manejadores
-
gorila/mux
Con estos preparativos en su lugar, ¡podemos comenzar nuestro viaje Go!
2. Estructura de la aplicación
Ahora debería tener Go instalado y listo para funcionar.
Abra su IDE favorito (Visual Studio Code, GoLand, ...) y cree un nuevo proyecto.
Como mencioné anteriormente, la idea era crear una API REST simple para la administración de librerías mediante el uso de Mux.
Una vez que haya creado su proyecto en blanco, cree la siguiente estructura en él:
├── main.go └── src ├── app.go ├── data.go ├── handlers.go ├── helpers.go └── middlewares.go
Ir a kits de herramientas y módulos
Comencemos mirando los módulos y paquetes de Go, si está familiarizado con Python, puede tener una idea de estas cosas porque funcionan de manera similar.
La mejor manera de describir un paquete de Go es que es una colección de archivos fuente en el mismo directorio, compilados en una unidad reutilizable.
Esto significa que todos los archivos con un propósito similar deben colocarse en un paquete.
Siguiendo nuestra estructura anterior, src
es uno de nuestros paquetes.
Un módulo de Go es una colección de paquetes de Go y sus dependencias, lo que significa que un módulo puede constar de varios paquetes.
Para facilitar la comprensión, puede pensar en toda nuestra aplicación como un módulo Go.
Ejecutemos este comando en el directorio raíz del proyecto para crear nuestro módulo.
go mod init librería
Debería ver un nuevo archivo en su directorio raíz llamado go.mod
.
3. Cree la API
Ahora es el momento de comenzar a construir nuestra aplicación.
Abra su main.go
archivo e inserte el siguiente código en él.
paquete importación principal"librería/src" función principal() { src.Start() }
Declaramos nuestro paquete Go principal ( package main
) e importamos nuestro paquete con el prefijo src
del módulo .bookstore
En la función main()
, ejecutaremos src
la Start()
función del paquete.
main.go
Esta es responsabilidad exclusiva de nuestro archivo de entrada ( ) - para iniciar la API.
rutas y controladores
Ahora necesitamos crear nuestro enrutador API ( Mux
) y configurarlo creando algunos puntos finales y sus controladores.
Abra su paquete src app.go
e inserte el siguiente código en él.
paquete src importar ( "github.com/gorilla/handlers" "github.com/gorilla/mux" "log" "net/http" "os" ) func Start() { enrutador := mux.NewRouter() enrutador .Use(commonMiddleware) enrutador.HandleFunc("/libro", getAllBooks).Métodos(http.MethodGet) enrutador.HandleFunc("/libro", addBook).Métodos(http.MethodPost) enrutador.HandleFunc("/libro/{ book_id:[0-9]+}", getBook).Methods(http.MethodGet) router.HandleFunc("/book/{book_id:[0-9]+}", updateBook).Methods(http.MethodPut) router .HandleFunc("/libro/{id_libro:[0-9]+}",eliminarLibro).Métodos(http.MethodDelete) log.Fatal(http.ListenAndServe("localhost:5000", handlers.LoggingHandler(os.Stdout, enrutador)))manejadores.LoggingHandler(os.Stdout, enrutador))) }
Como puede ver, nuestra declaración app.go
es parte del paquete src, que contiene las funciones que main.go
usamos en el archivo Start()
.
También importamos dos módulos externos, de los que debemos depender en nuestro mux
programa handlers
.
Ejecuta el siguiente comando en tu terminal:
ve a github.com/gorilla/handlers ve a github.com/gorilla/mux
Su go.mod
archivo también debería estar sincronizado, ahora debería verse así:
módulo bookstore go1.17 require ( github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 ) require github.com/felixge/httpsnoop v1.0.1// indirecto
Echemos un vistazo más profundo a nuestra Start()
función.
Primero, declaramos una nueva Mux
variable de enrutador que será responsable de enrutar y manejar las solicitudes para toda la API.
Luego, decimos Mux
que vamos a utilizar un middleware que ejecutará la siguiente línea en cada solicitud que llegue a nuestra API:
enrutador.Uso (Middleware común)
Más sobre el middleware más adelante.
Continuando con el análisis de nuestro código, podemos ver dónde creamos puntos finales junto con controladores (funciones de devolución de llamada) y algunas validaciones primitivas como:
router.HandleFunc("/book/{book_id:[0-9]+}", updateBook).Methods(http.MethodPut)
Este punto final comienza tan pronto como el usuario llega a nuestro servidor /book/123
con un método (o cualquier otro número) en la ruta .PUT
Luego pasará la solicitud a la updateBook
función del controlador para su posterior procesamiento.
book_id
La variable debe ser un número porque especificamos una validación simple después de la declaración del nombre de la variable.
Finalmente, ejecutaremos nuestro servidor en una combinación específica de host y puerto y haremos que registre todo en nuestra terminal.
software intermedio
Todos sabemos que las API REST utilizan principalmente JSON al aceptar solicitudes y devolver respuestas.
Content-Type
Esto se comunica a nuestro navegador/cliente HTTP mediante encabezados.
Dado que nuestra API solo usará datos representados por JSON, podemos usar un middleware para garantizar que nuestro tipo de contenido siempre esté configurado en JSON.
Como se mencionó, app.go
el Start()
método contiene esta línea:
enrutador.Uso (Middleware común)
Abramos nuestro middlewares.go
archivo y creemos las funciones requeridas:
package src import "net/http" func commonMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set(" tipo de contenido", "aplicación/json; charset=utf-8") w.Header().Set("x-content-type-options", "nosniff") next.ServeHTTP(w, r) }) }
Una vez que el usuario llega Start()
a cualquiera de los puntos finales que registramos con el enrutador Mux en la función, el middleware interceptará la solicitud y agregará commonMiddleware
los dos encabezados que especificamos en la función.
Luego pasará la solicitud modificada a la función de controlador del punto final solicitado u otro middleware.
datos estáticos
Dado que no usaremos ningún servicio de almacenamiento de datos (bases de datos, cachés...), necesitamos tener algún tipo de datos estáticos.
Además, crearemos un tipo de datos para la respuesta personalizada, que explicaré más adelante.
Abra src
el paquete y data.go
coloque el siguiente contenido en él.
package src type Book struct { Id int`json:"id"` Título string`json:"title"` Author string`json:"author"` Género string`json:"genre"` } var booksDB = [] Libro{ {Id: 123, Título: "El Hobbit", Autor: "JRR Tolkien", Género: "Fantasía"}, {Id: 456, Título: "Harry Potter y la piedra filosofal", Autor: "JK Rowling" , Género: "Fantasía"}, {Id: 789, Título: "El Principito", Autor: "Antoine de Saint-Exupéry", Género: "Novela"}, }
Acabamos de crear una estructura de datos que contendrá la información necesaria para un libro en nuestra API.
También creé una etiqueta json que traducirá el nombre del campo a una representación JSON si el tipo de datos se pasará en JSON. Además, se crea un sistema primitivo de almacenamiento de libros (en memoria) y algunos datos de libros iniciales (booksDB).
Agregue este código debajo de la tabla anterior:
type CustomResponse struct { Code int `json:"code"` Message string `json:"message"` Description string `json:"description,omitempty"` } var responseCodes = map[int]string { 400: "Bad Request" , 401: "No autorizado", 403: "Prohibido", 404: "No encontrado", 409: "Conflicto", 422: "Error de validación", 429: "Demasiadas solicitudes", 500: "Error interno del servidor", }
Acabamos de crear una nueva estructura de datos que unificará los errores/respuestas que devolverá nuestra API, más sobre esto más adelante.
herramienta auxiliar
Necesitaremos algunas herramientas de ayuda para aprovechar al máximo nuestra API. Por ejemplo, tendremos que verificar si existe un libro con una ID determinada (agregar un libro nuevo, modificar un libro existente), eliminar un libro con una ID determinada (eliminar un libro), devolver un auto para un Código de estado HTTP dado Respuesta JSON definida.
Abra el paquete src e helpers.go
inserte lo siguiente en él:
package src import ( "encoding/json" "net/http" ) func removeBook(s []Book, i int) []Book { if i != len(s)-1 { s[i] = s[ len(s)-1] } return s[:len(s)-1] } func checkDuplicateBookId(s []Book, id int) bool { for _, book := range s { if book.Id == id { return true } } return false } func JSONResponse(w http.ResponseWriter, code int, desc string) { w.WriteHeader(code) mensaje, ok := responseCodes[code] if !ok { mensaje = "Indefinido" } r := CustomResponse{ Código: código, Mensaje: mensaje, Descripción: desc, } _ = json.NewEncoder(w).Encode(r) }
removeBook
La función itera Book
y si no es el último elemento del fragmento, lo moverá al final del fragmento y devolverá un nuevo fragmento sin él (evitando el último elemento).
checkDuplicateBookId
La función devolverá un valor bool (verdadero o falso) dependiendo de si la identificación dada existe en Book
.
JSONResponse
CustomResponse
La función es responsable de usar la suma que creamos anteriormente responseCodes
. Devolverá una representación JSON de CustomResponse con el código de estado y el mensaje que proporcionarán los códigos de respuesta.
Así evitaremos tener diferentes mensajes en nuestra API para el mismo código de estado HTTP.
manipulador
Ahora viene el paso final, juntar los controladores de punto final.
Abre el tuyo handlers.go
y vamos a poner algo de código en él:
paquete src import ( "codificación/json" "github.com/gorilla/mux" "net/http" "strconv" )
Obtener un solo libro
func getBook(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) bookId, _ := strconv.Atoi(vars["book_id"]) for _, book := range booksDB { if book.Id == bookId { _ = json.NewEncoder(w).Encode(book) return } } JSONResponse(w, http.StatusNotFound, "") }
Obtenemos la variable pasada del enrutador Mux y la convertimos de una cadena a un valor int. Luego, recorra nuestra base de datos de libros en busca de ID de libros coincidentes. Si existe, devuélvelo; si no, devolvemos 404: Not Found
un error.
Obtener todos los libros
func getAllBooks(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(booksDB) }
¿No es sencillo? Convierta booksDB a JSON y devuélvalo al usuario.
Agregar un nuevo libro
func addBook(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) var b Book err := decoder.Decode(&b) if err != nil { JSONResponse(w, http. StatusBadRequest, "") return } if checkDuplicateBookId(booksDB, b.Id) { JSONResponse(w, http.StatusConflict, "") return } booksDB = append(booksDB, b) w.WriteHeader(201) _ = json.NewEncoder( w).Codificar(b) }
Dado que esto se activa en el método POST, el usuario debe proporcionar datos JSON en el cuerpo de la solicitud que se ajuste a la estructura del libro.
{ "id": 999, "título": "AlgúnTítulo", "autor": "AlgúnAutor", "género": "AlgúnGénero" }
Una vez que decodifiquemos y validemos el cuerpo JSON contra nuestra estructura de libro (que devolveremos si falla 400: Bad Request error
), debemos verificar si ya existe un libro con la misma ID. Si es así, volveremos 409: Conflict error back
. En su lugar, nuestra base de datos de libros se agregará con los libros proporcionados por el usuario y su representación JSON se devolverá al usuario.
Actualizar libros existentes
func updateBook(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) bookId, _ := strconv.Atoi(vars["book_id"]) decodificador := json.NewDecoder(r.Body ) var b Book err := decoder.Decode(&b) if err != nil { JSONResponse(w, http.StatusBadRequest, "") return } for i, book := range booksDB { if book.Id == bookId { booksDB [i] = b _ = json.NewEncoder(w).Codificar(b) return } } JSONResponse(w, http.StatusNotFound, "") }
Casi lo mismo que el controlador de la función addBook, con una gran diferencia.
Para actualizar el libro, ya debe existir (el ID debe estar en booksDB).
Si existe, actualizaremos el valor del libro existente, de lo contrario, devolveremos 404: Not Found
un error.
eliminar libros existentes
func deleteBook(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) bookId, _ := strconv.Atoi(vars["book_id"]) for i, book := range booksDB { if book.Id == bookId { booksDB = removeBook(booksDB, i) _ = json.NewEncoder(w).Encode(book) return } } JSONResponse(w, http.StatusNotFound, "") }
Después de obtener el valor entero de la variable book_id, iteramos a través de booksDB para encontrar el libro que el usuario desea eliminar.
Si existe, usamos nuestra función de ayuda removeBook
para eliminar el libro del segmento de estructura del Libro. Si no existe, devolveremos 404: Not Found
un error.
4. Ejecute y pruebe la API
Ahora que nuestra API está completa, ejecútela y ejecute este programa en su terminal:
ve a ejecutar main.go
Inicie su cliente HTTP favorito (Insomnia, Postman, ...) y pruebe algunas de las interfaces que hemos creado