• Contenu
  • Bas de page
logo ouidoulogo ouidoulogo ouidoulogo ouidou
  • Qui sommes-nous ?
  • Offres
    • 💻 Applications métier
    • 🤝 Collaboration des équipes
    • 🛡️ Sécurisation et optimisation du système d’information
    • 🔗 Transformation numérique
  • Expertises
    • 🖥️ Développement logiciel
    • ♾️ DevSecOps
    • ⚙️ Intégration de logiciels et négoce de licences
      • Atlassian : Jira, Confluence, Bitbucket…
      • Plateforme monday.com
      • GitLab
      • SonarQube
    • 📚​ Logiciel de CRM et de gestion
    • 🎨 UX/UI design
    • 🌐 Accessibilité Numérique
    • 🗂️​ Démarches simplifiées
    • 📝 Formations Atlassian
  • Références
  • Carrières
    • 🧐 Pourquoi rejoindre Ouidou ?
    • ✍🏻 Nous rejoindre
    • 👨‍💻 Rencontrer nos collaborateurs
    • 🚀 Grandir chez Ouidou
  • RSE
  • Ressources
    • 🗞️ Actualités
    • 🔍 Articles techniques
    • 📖 Livres blancs
    • 🎙️ Interviews Clients
Nous contacter
✕
Création d’un menu en accordéon avec CSS
Création d’un menu en accordéon avec CSS
4 juin 2015
Du bon usage des exceptions en Ruby
Du bon usage des exceptions en Ruby
27 octobre 2015
Ressources > Articles techniques > Créer une API simpliste en Go

Créer une API simpliste en Go

Écrit par Théo Delaune

Go commence actuellement à avoir la côte auprès de nombreux développeurs, il devient de plus en plus utilisé dans la création d’application back-end.

Ces derniers mois, beaucoup de développeurs migrent leurs APIs Ruby on Rails en Go. Aujourd’hui nous allons nous y intéresser également et découvrir ensemble comment mettre en place une API simple en Go.

Cet article n’a pas vocation à vous apprendre les bases du Go. Pour cela vous pouvez retrouver nos précédents articles sur les débuts en Go.

Notre application

Nous allons réaliser la même API que celle réalisée dans un précédent article API Ruby on Rails Grape.

Notre but ici est de concevoir cette API avec les packages de base de Go. Les seuls packages externes sont: – gorilla/mux qui permet de créer un routeur plus facilement que celui inclus dans les packages de Go. – lib/pq qui est le driver PostgreSQL

Architecture

L’architecture de notre application se décline comme ceci:

config/ database.go controllers/ cars.go models/ car.go main.go router.go
  • config/database.go permet la connexion à la base de données ainsi que la création de la table cars.
  • controllers/cars.go fait la relation entre les requêtes http et notre struct Car
  • models/car.go lie notre struct Car avec les actions en base de données.
  • main.go lance la connexion à notre base de données, ajoute un enregistrement à notre base de donnée et lance le serveur http.
  • router.go définit les routes de notre API

Création de notre fichier config/database.go

Ce fichier va nous permettre de pouvoir gérer la connexion à notre base de données, de partager cette instance de connexion et d’initialiser une table sur cette base de données.

Un pré-requis est obligatoire pour que tout fonctionne correctement, la base de donnée doit déjà être créée manuellement avant de s’y connecter avec notre API.

// Dans un terminal // Création d'une base de donnée sur PostgreSQL createdb cars -u username -h host 
package config import ( "database/sql" _ "github.com/lib/pq" "log" ) var db *sql.DB func DatabaseInit() { var err error db, err = sql.Open("postgres", "user=theodelaune dbname=goapi") if err !=nil { log.Fatal(err) } // Create Table cars if not exists createCarsTable() } func createCarsTable() { _, err := db.Exec("CREATE TABLE IF NOT EXISTS cars(id serial,manufacturer varchar(20), design varchar(20), style varchar(20), doors int, created_at timestamp default NULL, updated_at timestamp default NULL, constraint pk primary key(id))") if err !=nil { log.Fatal(err) } } // Getter for db varfunc Db() *sql.DB { return db }

Nous définissons tout notre code contenant la gestion de la base de données dans le package config, dans le but de séparer le code de gestion de la base de donnée de celui responsable du lancement du serveur.

Une importation du type _ "..." importe le package uniquement, elle évite le lancement de l’initialisation de ce package, donc de possibles effets de bords.

La variable db va contenir notre instance de connexion SQL à la base de données, pour rendre cette connexion disponible aux autres packages, nous la définissons comme un Getter public avec func Db().

Création de notre fichier models/car.go

Ce fichier fait la liaison entre notre contrôleur et la base de données.

Nous allons lui définir en premier lieu la struct Car qui va contenir toutes les informations d’une voiture.

Nous mappons sur chaque champ du struct sa représentation en json, sous la forme json:"field..."

Puis nous définissons un type Cars qui est une Slice(tableau plus flexible) de Car, qui va permettre de contenir un ensemble de voitures.

package models import ( "github.com/synbioz/go_api/config" "log" "time" ) type Car struct { Id int `json:"id"` Manufacturer string `json:"manufacturer"` Design string `json:"design"` Style string `json:"style"` Doors uint8 `json:"doors"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type Cars []Car

Nous avons maintenant la structure de base, il nous faut maintenant créer une fonction permettant de stocker en base de donnée notre ‘objet’ Car.

Pour ce faire nous allons créer une nouvelle fonction NewCar(c *Car) qui prend comme paramètre un pointeur de type Car.

config.Db() est l’instance de la connexion à la base de données issue du fichier config/database.go.

Cette fonction nous permet de persister notre variable c de type Car dans la base de données.

Scan() nous permet de récupérer le retour de la requête SQL et l’affecter sur un champ de notre voiture c.

func NewCar(c *Car) { if c ==nil { log.Fatal(c) } c.CreatedAt = time.Now() c.UpdatedAt = time.Now() err := config.Db().QueryRow("INSERT INTO cars (manufacturer, design, style, doors, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6) RETURNING id;", c.Manufacturer, c.Design, c.Style, c.Doors, c.CreatedAt, c.UpdatedAt).Scan(&c.Id) if err !=nil { log.Fatal(err) } }

Nous avons besoin également d’une fonction nous permettant de récupérer depuis la base de données un enregistrement par son id.

Comme précédemment nous exécutons une requête sur la base de données et insérons le résultat dans les champs d’une variable de type Car puis nous retournons l’adresse du pointeur de cette variable.

func FindCarById(id int) *Car { var car Car row := config.Db().QueryRow("SELECT * FROM cars WHERE id = $1;", id) err := row.Scan(&car.Id, &car.Manufacturer, &car.Design, &car.Style, &car.Doors, &car.CreatedAt, &car.UpdatedAt) if err !=nil { log.Fatal(err) } return&car }

Pour obtenir toutes les voitures depuis la base de données nous exécutons notre requête SQL qui n’est rien d’autre qu’un SELECT * .

Nous itérons sur le retour de la requête, pour récupérer chaque ligne retournée.

Comme vu juste avant, nous récupérons les valeurs retournées de chaque ligne grâce à Scan() et ajoutons chaque voiture récupérée dans la variable de type Cars.

func AllCars() *Cars { var cars Cars rows, err := config.Db().Query("SELECT * FROM cars") if err !=nil { log.Fatal(err) } // Close rows after all readeddefer rows.Close() for rows.Next() { var c Car err := rows.Scan(&c.Id, &c.Manufacturer, &c.Design, &c.Style, &c.Doors, &c.CreatedAt, &c.UpdatedAt) if err !=nil { log.Fatal(err) } cars = append(cars, c) } return&cars }

La mise à jour d’un enregistrement est réalisée en pur SQL. Nous utilisons Prepare() pour créer la requête et Exec() pour la lancer.

func UpdateCar(car *Car) { car.UpdatedAt = time.Now() stmt, err := config.Db().Prepare("UPDATE cars SET manufacturer=$1, design=$2, style=$3, doors=$4, updated_at=$5 WHERE id=$6;") if err !=nil { log.Fatal(err) } _, err = stmt.Exec(car.Manufacturer, car.Design, car.Style, car.Doors, car.UpdatedAt, car.id) if err !=nil { log.Fatal(err) } }

La suppression d’un enregistrement en base est très simple à mettre en pratique :

func DeleteCarById(id int) error { stmt, err := config.Db().Prepare("DELETE FROM cars WHERE id=$1;") if err !=nil { log.Fatal(err) } _, err = stmt.Exec(id) return err }

Création de notre fichier controllers/cars.go

Nous devons à présent gérer la relation entre les requêtes http et notre struct Car. Nous créons pour cela des fonctions permettant de gérer ses requêtes.

La fonction CarsIndex va nous permettre d’envoyer toutes les voitures présentes dans notre base de données.

Nous mettons en place des headers spécifiques, car nous souhaitons envoyer seulement du json et répondre avec un status 200.

Nous précisons par le biais de json.NewEncoder(w) que la réponse est encodée au format json, puis nous passons les données à encoder avec la fonction Encode().

Comme nous l’avons spécifié dans notre fichier models/car.go, la fonction models.AllCars() nous retourne une variable de type Cars(qui est un Slice de Car).

package controllers import ( "encoding/json" "github.com/gorilla/mux" "github.com/synbioz/go_api/models" "io/ioutil" "log" "net/http" "strconv" ) func CarsIndex(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-type", "application/json;charset=UTF-8") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(models.AllCars()) }

Pour la création d’une nouvelle voiture, nous devons récupérer l’objet json passé dans le corps de notre requête.

Nous devons la récupérer à partir du champ Body et nous l’assignons à la variable body.

json.Unmarshal() parse l’objet json contenu dans la variable body et l’affecte à la variable car.

Il ne reste plus qu’à persister cette variable grâce à notre fonction models.NewCar().

Nous renvoyons à l’utilisateur sa voiture qui a entre-temps était persistée en base au format json.

func CarsCreate(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-type", "application/json;charset=UTF-8") w.WriteHeader(http.StatusOK) body, err := ioutil.ReadAll(r.Body) if err !=nil { log.Fatal(err) } var car models.Car err = json.Unmarshal(body, &car) if err !=nil { log.Fatal(err) } models.NewCar(&car) json.NewEncoder(w).Encode(car) }

Pour obtenir une voiture spécifique depuis son id, nous récupérons l’id passé dans l’url de la requête (que nous définirons dans la suite de cet article).

Nous utilisons pour se faire Vars() du package gorilla/mux, cette fonction nous permet de récupérer tous les paramètres passés dans l’url.

Pour convertir cet id au format string en int, nous utilisons le raccourci de ParseInt() qui est Atoi() issu du package strconv.

func CarsShow(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-type", "application/json;charset=UTF-8") w.WriteHeader(http.StatusOK) vars := mux.Vars(r) id, err := strconv.Atoi(vars["id"]) if err !=nil { log.Fatal(err) } car := models.FindCarById(id) json.NewEncoder(w).Encode(car) }

Dans le cas de la mise à jour et de la suppression d’un enregistrement nous ne repasserons pas dessus, car ces fonctions utilisent des composants vus dans la fonction CarShow() et CarIndex().

func CarsUpdate(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-type", "application/json;charset=UTF-8") w.WriteHeader(http.StatusOK) vars := mux.Vars(r) id, err := strconv.Atoi(vars["id"]) if err !=nil { log.Fatal(err) } body, err := ioutil.ReadAll(r.Body) if err !=nil { log.Fatal(err) } car := models.FindCarById(id) err = json.Unmarshal(body, &car) models.UpdateCar(car) json.NewEncoder(w).Encode(car) } func CarsDelete(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-type", "application/json;charset=UTF-8") w.WriteHeader(http.StatusOK) vars := mux.Vars(r) // strconv.Atoi is shorthand for ParseInt id, err := strconv.Atoi(vars["id"]) if err !=nil { log.Fatal(err) } err = models.DeleteCarById(id) }

Création de notre fichier router.go

Dans ce fichier, nous allons gérer toutes les routes de notre application.

Le routeur de notre application est géré avec le routeur du package gorilla/mux.

StrictSlash() lorsqu’il est à true redirige les routes du type /cars/ vers /cars.

Pour définir une route, nous utilisons plusieurs fonctions : –Methods() définit la méthode gérée par notre route (GET, POST, PUT, …). –Path() correspond à la route à laquelle on veut faire correspondre notre action. –Name() est le nom que l’on souhaite donner à notre route. –HandlerFunc() spécifie la fonction à laquelle cette route est liée.

package main import ( "github.com/gorilla/mux" "github.com/synbioz/go_api/controllers" ) func InitializeRouter() *mux.Router { // StrictSlash is true => redirect /cars/ to /cars router := mux.NewRouter().StrictSlash(true) router.Methods("GET").Path("/cars").Name("Index").HandlerFunc(controllers.CarsIndex) router.Methods("POST").Path("/cars").Name("Create").HandlerFunc(controllers.CarsCreate) router.Methods("GET").Path("/cars/{id}").Name("Show").HandlerFunc(controllers.CarsShow) router.Methods("PUT").Path("/cars/{id}").Name("Update").HandlerFunc(controllers.CarsUpdate) router.Methods("DELETE").Path("/cars/{id}").Name("DELETE").HandlerFunc(controllers.CarsDelete) return router }

Création de notre fichier main.go

Ce fichier est le point central de notre application, il est responsable du lancement de la connexion à la base de données ainsi que du lancement du serveur http.

Nous lançons la fonction DatabaseInit() écrite précédemment, cette fonction va créer la table cars si elle n’existe pas et ouvrir une connexion à la base de données.

Nous initialisons par la suite les routes de notre application, nous permettant de définir quelles sont les routes pour lesquelles le serveur doit répondre.

Dans le cas de cet article, nous créons à chaque lancement du serveur une nouvelle voiture en base de données.

Puis, nous lançons le serveur avec http.ListenAndServe() en lui spécifiant le port et le routeur utilisé.

package main import ( "github.com/synbioz/go_api/config" "github.com/synbioz/go_api/models" "log" "net/http" ) func main() { config.DatabaseInit() router := InitializeRouter() // Populate database models.NewCar(&models.Car{Manufacturer: "citroen", Design: "ds3", Style: "sport", Doors: 4}) log.Fatal(http.ListenAndServe(":8080", router)) }

Conclusion

Nous avons pu approcher avec cet article la création d’une API simpliste en Go.

Néanmoins, il reste beaucoup de choses qui ne sont pas encore gérées, comme la gestion des erreurs et de leur retour au client.

Je vous invite également à effectuer un refactoring du code, ainsi que de mettre en place un log sur le routeur qui permettra d’afficher dans la console les routes utilisées pour chaque appel.

Les sources de cet article sont disponibles sur GitHub.

À lire aussi

Fresque numérique miniature image
16 avril 2025

Fresque du Numérique

Lire la suite

intelligence artificielle Ouicommit miniature image
17 mars 2025

Ouicommit – L’intelligence artificielle en entreprise, on y est ! 

Lire la suite

Image miniature Hackathon Women in Tech
13 mars 2025

Hackathon Women in Tech :  un engagement pour une tech plus inclusive 

Lire la suite

image miniature les nouveautés Atlassian
26 février 2025

Les nouveautés Atlassian en 2025

Lire la suite

Articles associés

Fresque numérique miniature image
16 avril 2025

Fresque du Numérique


Lire la suite
intelligence artificielle Ouicommit miniature image
17 mars 2025

Ouicommit – L’intelligence artificielle en entreprise, on y est ! 


Lire la suite
Image miniature Hackathon Women in Tech
13 mars 2025

Hackathon Women in Tech :  un engagement pour une tech plus inclusive 


Lire la suite

À propos

  • Qui sommes-nous ?
  • Références
  • RSE
  • Ressources

Offres

  • Applications métier
  • Collaboration des équipes
  • Sécurisation et optimisation du système d’information
  • Transformation numérique

Expertises

  • Développement logiciel
  • DevSecOps
  • Intégration de logiciels et négoce de licences
  • Logiciel de CRM et de gestion
  • UX/UI design
  • Accessibilité Numérique
  • Démarches simplifiées
  • Formations Atlassian

Carrières

  • Pourquoi rejoindre Ouidou ?
  • Nous rejoindre
  • Rencontrer nos collaborateurs
  • Grandir chez Ouidou

SIEGE SOCIAL
70-74 boulevard Garibaldi, 75015 Paris

Ouidou Nord
165 Avenue de Bretagne, 59000 Lille

Ouidou Rhône-Alpes
4 place Amédée Bonnet, 69002 Lyon

Ouidou Grand-Ouest
2 rue Crucy, 44000 Nantes

Ouidou Grand-Est
7 cour des Cigarières, 67000 Strasbourg

  • Linkedin Ouidou
  • GitHub Ouidou
  • Youtube Ouidou
© 2024 Ouidou | Tous droits réservés | Plan du site | Mentions légales | Déclaration d'accessibilité
    Nous contacter