Un serveur web en 6 lignes
La bibliothèque standard de Go inclut tout ce qu'il faut pour créer un serveur web. Pas besoin de framework externe :
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Bonjour depuis Go !")
})
http.ListenAndServe(":8080", nil)
}
Lancez avec go run main.go et ouvrez http://localhost:8080. Votre serveur est en ligne.
En Python, il faudrait Flask ou Django. En JavaScript, Express. En Go, la bibliothèque standard suffit. Et ce serveur gère chaque requête dans sa propre goroutine : concurrence gratuite.
Plusieurs routes
Ajoutez autant de routes que nécessaire avec http.HandleFunc :
func main() {
http.HandleFunc("/", handleHome)
http.HandleFunc("/api/health", handleHealth)
http.HandleFunc("/api/users", handleUsers)
fmt.Println("Serveur démarré sur :8080")
http.ListenAndServe(":8080", nil)
}
func handleHome(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Page d'accueil")
}
func handleHealth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"status": "ok"}`)
}
func handleUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `[{"name": "Alice"}, {"name": "Bob"}]`)
}
Chaque handler reçoit deux paramètres :
w http.ResponseWriter: pour écrire la réponser *http.Request: la requête entrante (méthode, URL, body, headers)
JSON : encoding/json
Le package encoding/json convertit les structs Go en JSON et inversement :
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
// Struct → JSON (Marshal)
func handleGetUser(w http.ResponseWriter, r *http.Request) {
user := User{Name: "Alice", Email: "alice@example.com", Age: 30}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
// JSON → Struct (Unmarshal)
func handleCreateUser(w http.ResponseWriter, r *http.Request) {
var user User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, "JSON invalide", http.StatusBadRequest)
return
}
fmt.Fprintf(w, "Utilisateur créé : %s", user.Name)
}
Les tags `json:"name"` après chaque champ contrôlent le nom dans le JSON. Sans eux, Go utiliserait Name avec une majuscule.
Sortie JSON de handleGetUser :
{"name":"Alice","email":"alice@example.com","age":30}
Une API REST complète
Combinons tout pour créer une API de gestion de tâches :
type Task struct {
ID int `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
}
var tasks = []Task{
{ID: 1, Title: "Apprendre Go", Done: false},
{ID: 2, Title: "Créer une API", Done: false},
}
func handleTasks(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case "GET":
json.NewEncoder(w).Encode(tasks)
case "POST":
var task Task
json.NewDecoder(r.Body).Decode(&task)
task.ID = len(tasks) + 1
tasks = append(tasks, task)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(task)
default:
http.Error(w, "Méthode non autorisée", http.StatusMethodNotAllowed)
}
}
Cette API gère GET /api/tasks pour lister et POST /api/tasks pour créer. Testez-la avec curl :
# Lister les tâches
curl http://localhost:8080/api/tasks
# Créer une tâche
curl -X POST -d '{"title":"Tester l'API"}' http://localhost:8080/api/tasks
À vous d'essayer (le serveur HTTP ne tourne pas ici, mais l'encodage JSON oui) :
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
func main() {
user := User{Name: "Alice", Email: "alice@example.com", Age: 30}
data, _ := json.Marshal(user)
fmt.Println(string(data))
}
A web server in 6 lines
Go's standard library includes everything you need to create a web server. No external framework required:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from Go!")
})
http.ListenAndServe(":8080", nil)
}
Run with go run main.go and open http://localhost:8080. Your server is live.
In Python, you'd need Flask or Django. In JavaScript, Express. In Go, the standard library is enough. And this server handles each request in its own goroutine — free concurrency.
Multiple routes
Add as many routes as needed with http.HandleFunc:
func main() {
http.HandleFunc("/", handleHome)
http.HandleFunc("/api/health", handleHealth)
http.HandleFunc("/api/users", handleUsers)
fmt.Println("Server started on :8080")
http.ListenAndServe(":8080", nil)
}
func handleHome(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Home page")
}
func handleHealth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"status": "ok"}`)
}
func handleUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `[{"name": "Alice"}, {"name": "Bob"}]`)
}
Each handler receives two parameters:
w http.ResponseWriter— to write the responser *http.Request— the incoming request (method, URL, body, headers)
JSON: encoding/json
The encoding/json package converts Go structs to JSON and vice versa:
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
// Struct → JSON (Marshal)
func handleGetUser(w http.ResponseWriter, r *http.Request) {
user := User{Name: "Alice", Email: "alice@example.com", Age: 30}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
// JSON → Struct (Unmarshal)
func handleCreateUser(w http.ResponseWriter, r *http.Request) {
var user User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
fmt.Fprintf(w, "User created: %s", user.Name)
}
The tags `json:"name"` after each field control the name in JSON. Without them, Go would use Name with a capital letter.
JSON output from handleGetUser:
{"name":"Alice","email":"alice@example.com","age":30}
A complete REST API
Let's combine everything to create a task management API:
type Task struct {
ID int `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
}
var tasks = []Task{
{ID: 1, Title: "Learn Go", Done: false},
{ID: 2, Title: "Create an API", Done: false},
}
func handleTasks(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case "GET":
json.NewEncoder(w).Encode(tasks)
case "POST":
var task Task
json.NewDecoder(r.Body).Decode(&task)
task.ID = len(tasks) + 1
tasks = append(tasks, task)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(task)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
This API handles GET /api/tasks to list and POST /api/tasks to create. Test it with curl:
# List tasks
curl http://localhost:8080/api/tasks
# Create a task
curl -X POST -d '{"title":"Test the API"}' http://localhost:8080/api/tasks
Note : le serveur HTTP ne fonctionne pas dans le Playground. Installez Go localement ou utilisez Gitpod/Replit pour tester :
Copiez ce prompt dans Claude ou ChatGPT :
Crée une API REST Go complète pour gérer des livres (CRUD). Utilise uniquement la bibliothèque standard. Ajoute la validation des données et des codes de statut HTTP appropriés.
Sans relire le code de l'IA : décris de mémoire ce que fait un handler POST qui crée une ressource, de la lecture du r.Body jusqu'à la réponse. Quels packages et quel code de statut utilises-tu ?
json.NewDecoder(r.Body).Decode(&task) (package encoding/json) ; on vérifie l'erreur retournée et on répond http.StatusBadRequest (400) si le JSON est invalide ; en cas de succès on écrit Content-Type: application/json, on renvoie http.StatusCreated (201) avec w.WriteHeader, puis on encode la ressource créée avec json.NewEncoder(w).Encode(...). Le routage et la méthode viennent de net/http.L'IA te propose ce handler POST. Ton rôle de relecteur : l'accepter tel quel ou le rejeter, et dire pourquoi.
func handleCreateTask(w http.ResponseWriter, r *http.Request) {
var task Task
json.NewDecoder(r.Body).Decode(&task)
task.ID = len(tasks) + 1
tasks = append(tasks, task)
json.NewEncoder(w).Encode(task)
}
Decode est ignorée : si le client envoie un JSON invalide, task reste vide et on enregistre quand même une tâche vide au lieu de répondre 400 Bad Request. Réflexe Go : on ne jette jamais une error retournée. Il faut if err := json.NewDecoder(r.Body).Decode(&task); err != nil { http.Error(w, "JSON invalide", http.StatusBadRequest); return }. Bonus : pense aussi à w.WriteHeader(http.StatusCreated) avant d'encoder, sinon la réponse part en 200 alors qu'on a créé une ressource.Sans remonter dans la leçon : quelle est la signature d'un handler HTTP en Go, et avec quelle fonction renvoies-tu un struct au format JSON dans la réponse ?
func(w http.ResponseWriter, r *http.Request) : w sert à écrire la réponse, r contient la requête entrante. Pour renvoyer un struct en JSON, on pose d'abord l'en-tête w.Header().Set("Content-Type", "application/json") puis on appelle json.NewEncoder(w).Encode(monStruct) (ou json.Marshal qui retourne un []byte).Your REST API answers in JSON, but how do you know it won't break on the next change? In the last lesson, you write tests with the built-in testing package and measure performance with benchmarks.
Lesson 8: Tests and benchmarks →