Go language book management RESTful API development practice

Go (Golang) est un langage de programmation relativement nouveau qui a récemment gagné en popularité.

Il est petit et stable, facile à utiliser et à apprendre, rapide, compilé (code natif) et largement utilisé dans les outils et services cloud (Docker, Kubernetes...).

Compte tenu de tous les avantages qu'il offre, il n'y a aucune raison de ne pas l'essayer.

Dans ce didacticiel, nous allons créer une API REST simple pour une librairie.

Magasin du trésor du programmeur : https://github.com/Jackpopc/CS-Books-Store

1. Préparatifs

Avant de commencer, nous devons faire quelques préparatifs à l'avance :

  • navigateur

  • gorille/manieurs

  • gorille/mux

Avec ces préparatifs en place, nous pouvons commencer notre voyage Go !

2. Structure de candidature

Vous devriez maintenant avoir Go installé et prêt à fonctionner.

Ouvrez votre IDE préféré (Visual Studio Code, GoLand, ...) et créez un nouveau projet.

Comme je l'ai mentionné précédemment, l'idée était de créer une API REST simple pour la gestion des librairies en utilisant Mux.

Une fois que vous avez créé votre projet vierge, créez-y la structure suivante :

├── main.go 
└── src 
    ├── app.go 
    ├── data.go ├── handlers.go 
    ├── 
    helpers.go 
    └── middlewares.go

Boîtes à outils et modules Go

Commençons par regarder les modules et packages Go, si vous êtes familier avec Python, vous avez peut-être une idée de ces choses car elles fonctionnent de manière similaire.

La meilleure façon de décrire un package Go est qu'il s'agit d'une collection de fichiers source dans le même répertoire, compilés dans une unité réutilisable.

Cela signifie que tous les fichiers ayant un objectif similaire doivent être placés dans un seul package.

Suite à notre structure ci-dessus, srcest l'un de nos forfaits.

Un module Go est une collection de packages Go et de leurs dépendances, ce qui signifie qu'un module peut être composé de plusieurs packages.

Pour faciliter la compréhension, vous pouvez considérer l'ensemble de notre application comme un module Go.

Exécutons cette commande dans le répertoire racine du projet pour créer notre module.

go mod init librairie

Vous devriez voir un nouveau fichier dans votre répertoire racine appelé go.mod.

3. Créez l'API

Il est maintenant temps de commencer à créer notre application.

Ouvrez votre main.gofichier et insérez-y le code suivant.

package main 
​import
"bookstore/src" 
​func
main() { 
    src.Start() 
}

Nous déclarons notre package Go principal ( package main) et importons notre package avec le préfixe srcdu module .bookstore

Dans la fonction main(), nous allons exécuter srcla Start()fonction du package.

main.goC'est la seule responsabilité de notre fichier d'entrée ( ) - pour démarrer l'API.

itinéraires et gestionnaires

Nous devons maintenant créer notre routeur API ( Mux) et le configurer en créant des points de terminaison et leurs gestionnaires.

Ouvrez dans votre package src app.goet insérez-y le code suivant.

package src 
​import
( 
    "github.com/gorilla/handlers" 
    "github.com/gorilla/mux" 
    "log" 
    "net/http" 
    "os" 
) 
​func
Start() { 
    router := mux.NewRouter() 
    router .Use(commonMiddleware) 
    router.HandleFunc("/book", getAllBooks).Methods(http.MethodGet) 
    router.HandleFunc("/book", addBook).Methods(http.MethodPost) 
    router.HandleFunc("/book/{ book_id:[0-9]+}", getBook).Methods(http.MethodGet) 
    router.HandleFunc("/book/{book_id:[0-9]+}", updateBook).Methods(http.MethodPut)handlers.LoggingHandler(os.Stdout, routeur))) 
} 
    router .HandleFunc("/book/{book_id :[0-9]+}",deleteBook).Methods(http.MethodDelete)
    log.Fatal(http.ListenAndServe("localhost:5000", handlers.LoggingHandler(os.Stdout, routeur)))

Comme vous pouvez le voir, notre déclaration app.gofait partie du package src, qui contient les fonctions que nous main.goutilisons dans le fichier Start().

Nous importons également deux modules externes, dont nous devons dépendre dans notre muxprogramme handlers.

Exécutez la commande suivante dans votre terminal :

allez chercher github.com/gorilla/handlers 
allez chercher github.com/gorilla/mux

Votre go.modfichier devrait également être synchronisé, maintenant il devrait ressembler à ceci :

module librairie 
​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// indirect

Examinons plus en détail notre Start()fonction.

Tout d'abord, nous déclarons une nouvelle Muxvariable de routeur qui sera responsable du routage et de la gestion des requêtes pour l'ensemble de l'API.

Ensuite, nous disons Muxque nous allons utiliser un middleware qui exécutera la ligne suivante à chaque requête qui arrivera à notre API :

router.Use(commonMiddleware)

Plus d'informations sur le middleware plus tard.

En continuant à analyser notre code, nous pouvons voir où nous créons des points de terminaison avec des gestionnaires (fonctions de rappel) et certaines validations primitives comme :

router.HandleFunc("/book/{book_id:[0-9]+}", updateBook).Methods(http.MethodPut)

Ce point de terminaison démarre dès que l'utilisateur accède à notre serveur /book/123avec une méthode (ou tout autre nombre) sur le chemin .PUT

Il transmettra ensuite la demande à la updateBookfonction de gestionnaire pour un traitement ultérieur.

book_idLa variable doit être un nombre car nous spécifions une simple validation après la déclaration du nom de la variable.

Enfin, nous exécuterons notre serveur sur une combinaison spécifique d'hôte et de port et lui ferons tout enregistrer sur notre terminal.

middleware

Nous savons tous que les API REST utilisent principalement JSON lors de l'acceptation des requêtes et du retour des réponses.

Content-TypeCeci est communiqué à notre navigateur/client HTTP à l' aide d' en-têtes.

Étant donné que notre API n'utilisera que des données représentées par JSON, nous pouvons utiliser un middleware pour nous assurer que notre type de contenu est toujours défini sur JSON.

Comme mentionné, app.gola Start()méthode contient cette ligne :

router.Use(commonMiddleware)

Ouvrons notre middlewares.gofichier et créons les fonctions requises :

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(" content-type", "application/json; charset=utf-8") 
        w.Header().Set("x-content-type-options", "nosniff") 
        next.ServeHTTP(w, r) 
    }) 
}

Une fois que l'utilisateur atteint Start()l'un des points de terminaison que nous avons enregistrés avec le routeur Mux dans la fonction, le middleware interceptera la demande et ajoutera commonMiddlewareles deux en-têtes que nous avons spécifiés dans la fonction.

Il transmettra ensuite la demande modifiée à la fonction de gestionnaire du point de terminaison demandé ou à un autre middleware.

données statiques

Puisque nous n'utiliserons aucun service de stockage de données (bases de données, caches...), nous avons besoin d'une sorte de données statiques.

De plus, nous allons créer un type de données pour la réponse personnalisée, que j'expliquerai plus tard.

Ouvrez srcle paquet et data.gomettez-y le contenu suivant.

package src 
​type
Book struct { 
    Id int`json:"id"` 
    Title string`json:"title"` 
    Author string`json:"author"` 
    Genre string`json:"genre"` 
} 
​var
booksDB = [] Livre{ 
    {Id : 123, Titre : "Le Hobbit", Auteur : "JRR Tolkien", Genre : "Fantasy"}, 
    {Id : 456, Titre : "Harry Potter et la pierre philosophale", Auteur : "JK Rowling" , Genre : "Fantasy"}, 
    {Id : 789, Titre : "Le Petit Prince", Auteur : "Antoine de Saint-Exupéry", Genre : "Novella"}, 
}

Nous venons de créer une structure de données qui contiendra les informations nécessaires pour un livre dans notre API.

J'ai également créé une balise json qui traduira le nom du champ en représentation JSON si le type de données sera passé en JSON. En outre, un système de stockage de livre primitif (en mémoire) et certaines données de livre initiales (booksDB) sont créés.

Ajoutez ce code sous le tableau ci-dessus :

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 : "Non autorisé", 
    403 : "Interdit", 
    404 : "Non trouvé", 
    409 : "Conflit", 
    422 : "Erreur de validation", 
    429 : "Trop de requêtes", 
    500 : "Erreur de serveur interne", 
}

Nous venons de créer une nouvelle structure de données qui unifiera les erreurs/réponses que notre API renverra, nous en reparlerons plus tard.

Outil auxiliaire

Nous aurons besoin d'outils d'aide pour tirer le meilleur parti de notre API. Par exemple, nous aurons besoin de vérifier si un livre avec un identifiant donné existe (ajouter un nouveau livre, modifier un livre existant), besoin de supprimer un livre avec un identifiant donné (supprimer un livre), besoin de retourner un self pour un code d'état HTTP donné Réponse JSON définie.

Ouvrez le package src et helpers.goinsérez-y les éléments suivants :

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) 
    message, ok := responseCodes[code] 
    if !ok {
        message = "Undefined" 
    } 
    r := CustomResponse{ 
        Code : code, 
        Message : message, 
        Description : desc, 
    } 
    _ = json.NewEncoder(w).Encode(r) 
}

removeBookLa fonction itère Booket si ce n'est pas le dernier élément du fragment, elle le déplacera à la fin du fragment et renverra un nouveau fragment sans lui (en évitant le dernier élément).

checkDuplicateBookIdLa fonction renverra une valeur booléenne (true ou false) selon que l'identifiant donné existe dans Book.

JSONResponseCustomResponseLa fonction est responsable de l'utilisation de la somme que nous avons créée précédemment responseCodes. Il renverra une représentation JSON de CustomResponse avec le code d'état et le message que responseCodes fournira.

De cette façon, nous éviterons d'avoir des messages différents dans notre API pour le même code de statut HTTP.

gestionnaire

Vient maintenant la dernière étape, la mise en place des gestionnaires de points de terminaison.

Ouvrez le vôtre handlers.goet insérons-y du code :

package src 
​import
( 
    "encoding/json" 
    "github.com/gorilla/mux" 
    "net/http" 
    "strconv" 
)

Obtenez un seul livre

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, "") 
}

Nous obtenons la variable passée du routeur Mux et la convertissons d'une chaîne en une valeur int. Ensuite, parcourez notre booksDB à la recherche des ID de livre correspondants. S'il existe, renvoyez-le - sinon, nous renvoyons 404: Not Foundune erreur.

Obtenir tous les livres

func getAllBooks(w http.ResponseWriter, r *http.Request) { 
    _ = json.NewEncoder(w).Encode(booksDB) 
}

N'est-ce pas simple? Convertissez booksDB en JSON et renvoyez-le à l'utilisateur.

Ajouter un nouveau livre

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).Encoder(b) 
}

Comme cela est déclenché sur la méthode POST, l'utilisateur doit fournir des données JSON dans le corps de la requête qui sont conformes à la structure Book.

{ 
    "id": 999, 
    "title": "UnTitre", 
    "author": "UnAuteur", 
    "genre": "UnGenre" 
}

Une fois que nous avons décodé et validé le corps JSON par rapport à notre structure de livre (que nous renverrons en cas d'échec 400: Bad Request error), nous devons vérifier si un livre avec le même ID existe déjà. Si c'est le cas, nous reviendrons 409: Conflict error back. Au lieu de cela, notre booksDB sera ajouté aux livres fournis par l'utilisateur et sa représentation JSON sera renvoyée à l'utilisateur.

Mettre à jour les livres existants

func updateBook(w http.ResponseWriter, r *http.Request) { 
    vars := mux.Vars(r) 
    bookId, _ := strconv.Atoi(vars["book_id"]) 
    decoder := 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).Encode(b) 
            return 
        } 
    } 
    JSONResponse(w, http.StatusNotFound, "") 
}

Presque le même que le gestionnaire de fonction addBook, avec une différence majeure.

Pour mettre à jour le livre, il doit déjà exister (l'ID doit être dans booksDB).

S'il existe, nous mettrons à jour la valeur du livre existant, sinon, nous renverrons 404: Not Foundune erreur.

supprimer des livres existants

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, "") 
}

Après avoir obtenu la valeur entière de la variable book_id, nous parcourons la base de données booksDB pour trouver le livre que l'utilisateur souhaite supprimer.

S'il existe, nous utilisons notre fonction d'assistance removeBookpour supprimer le livre de la tranche Structure du livre. S'il n'existe pas, nous retournerons 404: Not Foundune erreur.

4. Exécutez et testez l'API

Maintenant que notre API est terminée, lançons-la et exécutons ce programme dans votre terminal :

allez courir main.go

Lancez votre client HTTP préféré (Insomnia, Postman, ...) et essayez certaines des interfaces que nous avons créées

Je suppose que tu aimes

Origine blog.csdn.net/jakpopc/article/details/123024018
conseillé
Classement