Ir a la gestión de libros de idiomas Práctica de desarrollo de API RESTful

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, srces 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.goarchivo 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 srcdel módulo .bookstore

En la función main(), ejecutaremos srcla Start()función del paquete.

main.goEsta 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.goe 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.goes parte del paquete src, que contiene las funciones que main.gousamos en el archivo Start().

También importamos dos módulos externos, de los que debemos depender en nuestro muxprograma handlers.

Ejecuta el siguiente comando en tu terminal:

ve a github.com/gorilla/handlers 
ve a github.com/gorilla/mux

Su go.modarchivo 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 Muxvariable de enrutador que será responsable de enrutar y manejar las solicitudes para toda la API.

Luego, decimos Muxque 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/123con un método (o cualquier otro número) en la ruta .PUT

Luego pasará la solicitud a la updateBookfunción del controlador para su posterior procesamiento.

book_idLa 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-TypeEsto 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.goel Start()método contiene esta línea:

enrutador.Uso (Middleware común)

Abramos nuestro middlewares.goarchivo 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á commonMiddlewarelos 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 srcel paquete y data.gocoloque 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.goinserte 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) 
}

removeBookLa función itera Booky 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).

checkDuplicateBookIdLa función devolverá un valor bool (verdadero o falso) dependiendo de si la identificación dada existe en Book.

JSONResponseCustomResponseLa 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.goy 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 Foundun 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 Foundun 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 removeBookpara eliminar el libro del segmento de estructura del Libro. Si no existe, devolveremos 404: Not Foundun 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

Supongo que te gusta

Origin blog.csdn.net/jakpopc/article/details/123024018
Recomendado
Clasificación