
Développement d'une application web complète
Apprenez à développer une application web complète en Go : architecture MVC, routage, handlers, templates, base de données, tests, déploiement et bonnes pratiques pour des applications web Go professionnelles.
Introduction au développement d'une application web complète : De la théorie à la pratique
Après avoir exploré en profondeur les différents aspects du développement web en Go (HTTP, templates, bases de données, etc.), il est temps de mettre en pratique ces connaissances et de se lancer dans le développement d'une application web complète en Go. Ce chapitre vous guidera à travers les étapes clés de la création d'une application web fonctionnelle, de la conception de l'architecture à l'implémentation des fonctionnalités, en passant par la gestion des données, le rendu côté serveur, les tests, et le déploiement.
L'objectif de ce chapitre est de vous fournir un exemple concret et pédagogique de développement d'une application web Go de A à Z, en mettant en oeuvre les concepts et les techniques abordés dans les chapitres précédents. Nous allons construire une application web simple mais complète, en suivant une architecture MVC (Modèle-Vue-Contrôleur), en utilisant un framework web populaire (Gin) pour simplifier le routage et le middleware, en intégrant une base de données SQL (SQLite) pour la persistance des données, en utilisant des templates Go pour le rendu côté serveur, et en mettant en place des tests unitaires pour garantir la qualité du code. Ce projet pratique vous permettra de consolider vos acquis, de développer vos compétences en développement web Go, et de vous préparer à la construction d'applications web Go plus complexes et plus ambitieuses.
Conception de l'application web : Architecture MVC et fonctionnalités
Avant de commencer à coder, il est essentiel de définir l'architecture et les fonctionnalités de notre application web complète. Nous allons adopter une architecture MVC (Modèle-Vue-Contrôleur), un pattern architectural classique et éprouvé pour les applications web, qui permet de séparer clairement les différentes responsabilités et de modulariser le code.
Architecture MVC (Modèle-Vue-Contrôleur) :
- Modèle (Model) : Responsable de la gestion des données, de la logique métier, et de l'accès à la base de données. Le modèle encapsule les données de l'application, les règles métier, et les opérations de manipulation des données (création, lecture, mise à jour, suppression - CRUD). Dans notre application web Go, le modèle sera implémenté avec des structs Go (pour représenter les entités de données) et du code Go (fonctions, méthodes) pour la logique métier et l'accès à la base de données (via
database/sqlou un ORM). - Vue (View) : Responsable de la présentation des données à l'utilisateur. La vue génère l'interface utilisateur (UI) de l'application web, généralement sous forme de pages web HTML. La vue reçoit les données du contrôleur et les affiche à l'utilisateur. Dans notre application web Go, la vue sera implémentée avec des templates Go (
html/template) pour le rendu côté serveur des pages HTML. - Contrôleur (Controller) : Responsable de la gestion des requêtes HTTP entrantes, de l'interaction avec le modèle pour récupérer ou modifier les données, et du choix de la vue à afficher à l'utilisateur. Le contrôleur agit comme un intermédiaire entre le modèle et la vue. Il reçoit les requêtes HTTP du client, délègue les opérations de manipulation des données au modèle, et choisit la vue appropriée pour afficher les données à l'utilisateur. Dans notre application web Go, le contrôleur sera implémenté avec des handlers HTTP Gin (fonctions avec la signature
gin.HandlerFunc) qui gèrent les routes de l'application et mettent en oeuvre la logique de contrôle.
Fonctionnalités de l'application web : Gestion d'une liste de tâches (TODO List)
Nous allons développer une application web simple de gestion de liste de tâches (TODO List), avec les fonctionnalités suivantes :
- Affichage de la liste des tâches : L'utilisateur peut visualiser la liste de toutes les tâches existantes (titre, description, statut - à faire, en cours, terminée).
- Ajout d'une nouvelle tâche : L'utilisateur peut ajouter une nouvelle tâche à la liste, en spécifiant un titre et une description pour la tâche.
- Marquer une tâche comme terminée : L'utilisateur peut marquer une tâche existante comme terminée.
- Supprimer une tâche : L'utilisateur peut supprimer une tâche existante de la liste.
Ces fonctionnalités simples nous permettront d'illustrer les concepts clés du développement d'une application web complète en Go, en mettant en oeuvre les différentes couches de l'architecture MVC (modèle, vue, contrôleur), le routage, la gestion des données, le rendu côté serveur, et les tests unitaires.
Mise en place du projet Go : Structure de répertoires et dépendances
Avant de commencer à coder, nous allons mettre en place la structure de répertoires de notre projet Go et gérer les dépendances nécessaires avec Go Modules.
Structure de répertoires du projet :
application-web-go-complete/
├── main.go # Point d'entrée principal de l'application
├── go.mod # Fichier Go Modules (gestion des dépendances)
├── pkg/
│ ├── controllers/ # Package pour les contrôleurs (handlers HTTP)
│ ├── models/ # Package pour les modèles de données (structs Go)
│ └── views/ # Package pour les vues (templates HTML)
├── templates/ # Répertoire pour les fichiers templates HTML
│ ├── layouts/
│ └── pages/
└── static/ # Répertoire pour les fichiers statiques (CSS, JS, images, etc.)
Initialisation du module Go et gestion des dépendances (Go Modules) :
Dans le répertoire racine du projet (application-web-go-complete/), initialisez un nouveau module Go avec la commande go mod init :
go mod init application-web-go-complete
Ajoutez les dépendances nécessaires pour notre application web Go : Gin (framework web), SQLite driver (base de données), et d'autres dépendances potentielles (validation, etc.) avec la commande go get :
go get github.com/gin-gonic/gin
go get github.com/mattn/go-sqlite3
Go Modules va créer et mettre à jour les fichiers go.mod (descripteur du module) et go.sum (checksum des dépendances) à la racine de votre projet, et télécharger les dépendances dans le cache des modules Go.
Implémentation du Modèle (Model) : Structs Go et accès à la base de données
La couche Modèle (Model) de notre application web sera responsable de la gestion des données et de l'accès à la base de données SQLite. Nous allons définir un struct Go Tache pour représenter une tâche dans notre liste TODO, et implémenter des fonctions (méthodes) pour effectuer les opérations CRUD sur les tâches (créer, lire, mettre à jour, supprimer) en interagissant avec la base de données SQLite.
1. Définition du struct Modèle Tache (pkg/models/tache.go) :
package models
import "gorm.io/gorm"
// Tache représente une tâche dans la liste TODO
type Tache struct {
gorm.Model // Embed gorm.Model pour les champs de base (ID, CreatedAt, UpdatedAt, DeletedAt)
Titre string `gorm:"not null"`
Description string
Statut string `gorm:"default:'à faire'"` // Statut par défaut : "à faire"
}
2. Fonctions d'accès à la base de données (CRUD) dans le Modèle (pkg/models/tache.go) :
package models
import (
"gorm.io/gorm"
)
// CreerTache crée une nouvelle tâche dans la base de données
func CreerTache(db *gorm.DB, tache *Tache) error {
result := db.Create(tache)
return result.Error
}
// LireTaches récupère toutes les tâches de la base de données
func LireTaches(db *gorm.DB) ([]Tache, error) {
var taches []Tache
result := db.Find(&taches)
return taches, result.Error
}
// LireTacheParID récupère une tâche par son ID
func LireTacheParID(db *gorm.DB, id string) (*Tache, error) {
var tache Tache
result := db.First(&tache, id)
return &tache, result.Error
}
// MettreAJourTache met à jour une tâche existante dans la base de données
func MettreAJourTache(db *gorm.DB, tache *Tache) error {
result := db.Save(tache)
return result.Error
}
// SupprimerTache supprime une tâche de la base de données par son ID
func SupprimerTache(db *gorm.DB, id string) error {
result := db.Delete(&Tache{}, id)
return result.Error
}
Ce code définit le struct Tache (modèle GORM) et les fonctions CRUD (CreerTache, LireTaches, LireTacheParID, MettreAJourTache, SupprimerTache) pour interagir avec la table taches dans la base de données SQLite. Ces fonctions seront utilisées par les contrôleurs pour manipuler les données des tâches.
Implémentation des Vues (Views) : Templates HTML pour l'interface utilisateur
La couche Vue (View) de notre application web sera responsable de la présentation des données à l'utilisateur, en utilisant des templates HTML Go pour générer dynamiquement l'interface utilisateur web.
1. Fichiers templates HTML (répertoire templates/pages et templates/layouts) :
Nous allons créer plusieurs fichiers templates HTML dans les répertoires templates/pages (pour les templates de contenu spécifiques à chaque page) et templates/layouts (pour le template de layout global - templates/layouts/base.html). Ces templates HTML contiendront du HTML statique et des actions Go ({{ ... }}) pour afficher dynamiquement les données et mettre en oeuvre la logique de présentation.
2. Fonctions de rendu des templates dans la couche Vue (pkg/views/render.go) :
package views
import (
"html/template"
"net/http"
)
var templates *template.Template // Variable globale pour stocker les templates parsés (mis en cache)
func init() {
// Parsing des templates au démarrage de l'application
templates = template.Must(template.ParseGlob("templates/**/*.html")) // Parse tous les fichiers HTML dans 'templates' et ses sous-répertoires
}
// RenduTemplate execute le template HTML spécifié avec les données fournies
func RenduTemplate(w http.ResponseWriter, nomTemplate string, data interface{}) {
tmpl := templates.Lookup(nomTemplate)
if tmpl == nil {
http.Error(w, "Template non trouvé", http.StatusInternalServerError)
return
}
err := tmpl.Execute(w, data)
if err != nil {
http.Error(w, "Erreur lors du rendu du template", http.StatusInternalServerError)
}
}
Ce code définit une variable globale templates pour stocker les templates HTML parsés (mis en cache), une fonction init() pour parser les templates au démarrage de l'application, et une fonction RenduTemplate pour exécuter un template spécifique avec des données et écrire la sortie HTML dans le http.ResponseWriter.
Implémentation des Contrôleurs (Controllers) : Logique applicative et handlers HTTP
La couche Contrôleur (Controller) sera le coeur de notre application web Go, responsable de la gestion des requêtes HTTP entrantes, de l'interaction avec le modèle pour manipuler les données, et du rendu des vues HTML pour afficher les réponses à l'utilisateur. Nous allons créer plusieurs handlers HTTP Gin (fonctions avec la signature gin.HandlerFunc) dans la couche Contrôleur, chacun responsable de la gestion d'une route spécifique de l'application web (racine, liste des tâches, ajout de tâche, tâche terminée, suppression de tâche).
1. Contrôleur principal main.go (fonctions main et configurerRouteur) :
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"application-web-go-complete/pkg/controllers"
"application-web-go-complete/pkg/models"
)
var db *gorm.DB // Variable globale pour la connexion à la base de données GORM
func main() {
// ... (Connexion à la base de données SQLite avec GORM - code non montré ici) ...
routeur := configurerRouteur(db) // Configuration du routeur Gin (routes et handlers)
routeur.Run(":8080") // Lancement du serveur HTTP sur le port 8080
}
// configurerRouteur configure le routeur Gin et définit les routes et les handlers
func configurerRouteur(db *gorm.DB) *gin.Engine {
routeur := gin.Default()
// Route pour la page d'accueil (liste des tâches)
routeur.GET("/", controllers.HandlerAccueil(db))
// Routes pour la gestion des tâches (CRUD)
routeur.GET("/taches", controllers.HandlerListeTaches(db))
routeur.GET("/taches/nouvelle", controllers.HandlerNouvelleTacheFormulaire(db))
routeur.POST("/taches", controllers.HandlerCreerTache(db))
routeur.POST("/taches/:id/terminer", controllers.HandlerMarquerTacheTerminee(db))
routeur.POST("/taches/:id/supprimer", controllers.HandlerSupprimerTache(db))
// ... (Configuration des fichiers statiques, des middleware, etc. si nécessaire) ...
return routeur
}
2. Contrôleur des tâches (pkg/controllers/taches.go) : Handlers HTTP et logique applicative
package controllers
import (
"net/http"
"github.com/gin-gonic/gin"
"application-web-go-complete/pkg/models"
"application-web-go-complete/pkg/views"
)
// HandlerAccueil gère la route racine '/' et affiche la liste des tâches
func HandlerAccueil(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
taches, err := models.LireTaches(db)
if err != nil {
c.String(http.StatusInternalServerError, "Erreur serveur")
return
}
data := gin.H{
"taches": taches,
}
views.RenduTemplate(c.Writer, "pages/index.html", data)
}
}
// HandlerListeTaches gère la route '/taches' et retourne la liste des tâches au format JSON (API)
func HandlerListeTaches(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
taches, err := models.LireTaches(db)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur serveur"})
return
}
c.JSON(http.StatusOK, gin.H{"taches": taches})
}
}
// HandlerNouvelleTacheFormulaire gère la route '/taches/nouvelle' et affiche le formulaire de création de tâche
func HandlerNouvelleTacheFormulaire(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
views.RenduTemplate(c.Writer, "pages/nouvelle_tache.html", gin.H{})
}
}
// HandlerCreerTache gère la route POST '/taches' et crée une nouvelle tâche
func HandlerCreerTache(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var tache models.Tache
if err := c.Bind(&tache); err != nil {
c.String(http.StatusBadRequest, "Données de requête invalides")
return
}
if err := models.CreerTache(db, &tache); err != nil {
c.String(http.StatusInternalServerError, "Erreur serveur")
return
}
c.Redirect(http.StatusFound, "/") // Redirection vers la page d'accueil après la création de la tâche
}
}
// HandlerMarquerTacheTerminee gère la route POST '/taches/:id/terminer' et marque une tâche comme terminée
func HandlerMarquerTacheTerminee(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id := c.Param("id")
tache, err := models.LireTacheParID(db, id)
if err != nil {
c.String(http.StatusInternalServerError, "Erreur serveur")
return
}
tache.Statut = "terminée" // Modification du statut de la tâche
if err := models.MettreAJourTache(db, tache); err != nil {
c.String(http.StatusInternalServerError, "Erreur serveur")
return
}
c.Redirect(http.StatusFound, "/") // Redirection vers la page d'accueil après avoir marqué la tâche comme terminée
}
}
// HandlerSupprimerTache gère la route POST '/taches/:id/supprimer' et supprime une tâche
func HandlerSupprimerTache(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id := c.Param("id")
if err := models.SupprimerTache(db, id); err != nil {
c.String(http.StatusInternalServerError, "Erreur serveur")
return
}
c.Redirect(http.StatusFound, "/") // Redirection vers la page d'accueil après la suppression de la tâche
}
}
Ce code définit les handlers HTTP Gin pour les différentes routes de notre application web TODO List. Chaque handler interagit avec la couche Modèle (pkg/models) pour manipuler les données des tâches et utilise la couche Vue (pkg/views) pour rendre les templates HTML et afficher les réponses à l'utilisateur. L'application web complète est maintenant fonctionnelle, avec une architecture MVC claire et modulaire, un routage HTTP géré par Gin, un accès à la base de données SQLite via GORM, et un rendu côté serveur avec des templates Go.