API REST en GO
Introduction à l’API REST et à Go
Qu’est-ce qu’une API REST ?
-
API : interface pour communiquer avec un service ou une application de manière automatisée.
-
REST : style d’architecture web, où l’on utilise généralement HTTP pour effectuer des opérations CRUD (Create, Read, Update, Delete).
Pourquoi utiliser Go pour une API REST ?
Go est langage rapide et compilé. Il est ainsi idéal pour des services performants.
Création d'un serveur HTTP minimal
Créer le serveur
Nous avons appris à créer un serveur web dans la leçon précédente.
Nous allons en créer un autre pour notre API.
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Bienvenue sur mon API Go !")
})
fmt.Println("Le port 8080 est utilisé pour lancer l'API Go !")
http.ListenAndServe(":8080", nil)
}
Tester le serveur
go run main.go
Dans le navigateur de votre choix, si vous tapez l'url localhost:8080, vous devriez voir :
Manipuler des données (struct, JSON)
Imaginons qu’on veuille manipuler des "items". On définit un type Item grâce à struct:
type Item struct {
ID int `json:"id"`
Name string `json:"name"`
}
Les tags json:"..." indiquent comment Go encode/décode cette struct en JSON.
On stocke ensuite les items dans une slice.
var items = []Item{
{ID: 1, Name: "Item A"},
{ID: 2, Name: "Item B"},
}
On aura 2️⃣ items.
Gérer des endpoints
Qu'est-ce qu'un endpoints ?
Un endpoint est un point d’accès à une API auquel est associé une méthode HTTP (GET, POST, PUT, DELETE, etc...) et une "URL", afin que le client puisse envoyer une requête précise (par exemple GET /items) pour réaliser une action donnée.
GET : Récupérer la liste des items
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type Item struct {
ID int `json:"id"`
Name string `json:"name"`
}
var items = []Item{
{ID: 1, Name: "Item A"},
{ID: 2, Name: "Item B"},
}
func itemsHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// On renvoie la liste en JSON
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(items)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte("Méthode non autorisée"))
}
}
func main() {
http.HandleFunc("/", itemsHandler)
fmt.Println("Le port 8080 est utilisé pour lancer l'API Go !")
http.ListenAndServe(":8080", nil)
}
Voici comment comprendre le code (ligne 19 à 27).
r.method permet de vérifier la méthode : GET, POST, ...
json.NewEncoder(w).Encode(items) encode items en JSON et l’envoie dans la réponse.
w.Header().Set("Content-Type", ...) : indique que la réponse est au format JSON.
Rappelez-vous que w correspond à la réponse que nous envoyons.
Lorsque vous tapez l'url localhost:8080 dans votre navigateur, le résultat suivant apparaîtra :
[
{
"id":1,
"name":"Item A"
},
{
"id":2,
"name":"Item B"
}
]
Le serveur vous envoie des données sous forme JSON.
POST : Ajouter un nouvel item
On va maintenant utiliser utiliser la méthode POST pour ajouter un élément.
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type Item struct {
ID int `json:"id"`
Name string `json:"name"`
}
var items = []Item{
{ID: 1, Name: "Item A"},
{ID: 2, Name: "Item B"},
}
func itemsHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
w.Header().Set("Content-Type", "application/json")
var newItem Item
err := json.NewDecoder(r.Body).Decode(&newItem)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]string{"error": "Données invalides"})
return
}
// Générer un ID (simplement la longueur + 1, par exemple)
newItem.ID = len(items) + 1
items = append(items, newItem)
// Répondre avec l'item créé
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newItem)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte("Méthode non autorisée"))
}
}
func main() {
http.HandleFunc("/", itemsHandler)
fmt.Println("Le port 8080 est utilisé pour lancer l'API Go !")
http.ListenAndServe(":8080", nil)
}
Nous allons expliquer ce code.
Ligne 24 ► var newItem Item : Nous déclarons la variable newItem de type Item. Ce type, généralement une struct, va nous permettre de stocker les informations que nous allons extraire du corps (body) de la requête. Cela nous permettra de récupérer la nouvelle donnée.
Ligne 25 ► err := json.NewDecoder(r.Body).Decode(&newItem) : Nous créons ensuite un décodeur JSON basé sur r.Body, qui contient le contenu de la requête HTTP au format JSON. Avec la méthode Decode(&newItem), nous demandons à ce décodeur de convertir le JSON reçu en un objet Item. Le résultat de cette opération peut produire une erreur (par exemple si le JSON est mal formé), c’est pourquoi nous stockons cette éventuelle erreur dans la variable err.
Ligne 26 ► if err != nil { ... } : Nous vérifions si la variable err n’est pas nulle, ce qui signifierait que la phase de décodage a échoué. Si err != nil, alors quelque chose s’est mal passé, soit parce que le JSON n’est pas valide, soit parce qu’il ne correspond pas à la structure Item.
Ligne 27 ► w.WriteHeader(http.StatusBadRequest) : Si l’erreur err indique effectivement un problème, nous renvoyons au client un code HTTP 400 (Bad Request). Cela signifie que la requête était invalide ou mal formulée, ce qui donne une indication claire au client de la nature de l’erreur.
Ligne 28 ► json.NewEncoder(w).Encode(map[string]string{"error": "Données invalides"}) : Après avoir spécifié le code d’erreur, nous répondons également avec un petit JSON qui contient une clé "error" et la valeur "Données invalides". Cette réponse est encodée à l’aide de json.NewEncoder(w).Encode(...), ce qui permet au client (le navigateur) de comprendre la raison de l’échec.
Ligne 29 ► return : Nous utilisons return pour sortir immédiatement de la fonction handler, car nous ne voulons pas poursuivre le traitement si les données envoyées par le client ne sont pas conformes.
