Aller au contenu
Développement medium

Consommer une API REST en Go

9 min de lecture

Go

Interroger une API réelle, ce n'est pas un GET naïf : il faut authentifier la requête, décoder le JSON, paginer les résultats et pouvoir annuler un appel qui traîne. Ce guide construit un collecteur qui parcourt une API paginée avec net/http, un token d'authentification et un contexte de timeout, puis montre l'alternative des clients générés comme go-github. Public visé : intermédiaire en Go. Tous les exemples sont testés avec Go 1.26.

  • Construire une requête authentifiée avec net/http et un contexte.
  • Décoder une réponse JSON dans des structures Go.
  • Parcourir toutes les pages d'une API paginée.
  • Recourir à un client généré (go-github) quand il existe.

On ne mappe que les champs utiles de la réponse JSON. Les tags json:"..." relient chaque champ de la struct à sa clé dans le document.

main.go
// Repo est le sous-ensemble des champs qui nous intéressent.
type Repo struct {
Name string `json:"name"`
Stars int `json:"stars"`
}

Les champs non listés dans la struct sont simplement ignorés au décodage : inutile de modéliser toute la réponse.

La bonne pratique est http.NewRequestWithContext : le contexte permet d'annuler l'appel (timeout, arrêt du programme). On ajoute l'en-tête d'autorisation, on exécute, et on vérifie le code de statut avant de décoder.

main.go
func fetchPage(ctx context.Context, base, token string, page int) ([]Repo, error) {
url := fmt.Sprintf("%s/repos?page=%d", base, page)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close() // toujours fermer le corps de réponse
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("statut inattendu : %s", resp.Status)
}
var repos []Repo
if err := json.NewDecoder(resp.Body).Decode(&repos); err != nil {
return nil, err
}
return repos, nil
}

Deux réflexes non négociables : defer resp.Body.Close() pour ne pas fuir de connexions, et vérifier resp.StatusCode car une erreur HTTP (401, 500) n'est pas une erreur Go, client.Do réussit quand même.

Une API ne renvoie qu'une page de résultats à la fois. Le collecteur boucle en incrémentant le numéro de page et s'arrête quand une page revient vide.

main.go
// fetchAll parcourt les pages jusqu'à en recevoir une vide.
func fetchAll(ctx context.Context, base, token string) ([]Repo, error) {
var all []Repo
for page := 1; ; page++ {
batch, err := fetchPage(ctx, base, token, page)
if err != nil {
return nil, err
}
if len(batch) == 0 {
break
}
all = append(all, batch...)
}
return all, nil
}

Le main fixe un contexte avec délai maximal, puis lance la collecte. Ici, un petit serveur de test sert deux pages, la troisième étant vide :

main.go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
repos, err := fetchAll(ctx, apiURL, "secret-token")
if err != nil {
fmt.Println("erreur:", err)
return
}
for _, rp := range repos {
fmt.Printf("- %s (%d stars)\n", rp.Name, rp.Stars)
}
fmt.Printf("total: %d dépôts\n", len(repos))

À l'exécution, les trois dépôts répartis sur deux pages sont bien tous collectés :

- pitstop (128 stars)
- confkit (64 stars)
- plumber (512 stars)
total: 3 dépôts

Réécrire l'authentification, la pagination et la gestion d'erreurs pour une grande API serait fastidieux. Pour GitHub et GitLab, des clients officiels encapsulent tout cela. Voici la même collecte avec go-github : le client gère l'auth, la pagination se pilote via resp.NextPage.

github.go
import (
"context"
"github.com/google/go-github/v89/github"
)
func listOrgRepos(ctx context.Context, org, token string) ([]string, error) {
// v89 : NewClient prend des options fonctionnelles et renvoie une erreur.
client, err := github.NewClient(github.WithAuthToken(token))
if err != nil {
return nil, err
}
opt := &github.RepositoryListByOrgOptions{
ListOptions: github.ListOptions{PerPage: 100},
}
var names []string
for {
repos, resp, err := client.Repositories.ListByOrg(ctx, org, opt)
if err != nil {
return nil, err
}
for _, r := range repos {
names = append(names, r.GetName())
}
if resp.NextPage == 0 { // plus de page suivante
break
}
opt.Page = resp.NextPage
}
return names, nil
}

Le client gère pour vous les en-têtes, le format de pagination de GitHub et les limites de débit. La boucle sur resp.NextPage est le motif standard de pagination avec go-github.

  • Passez toujours un contexte : sans lui, un appel bloqué fige votre programme.
  • Fermez le corps de réponse avec defer resp.Body.Close(), sinon vous fuyez des connexions.
  • Vérifiez resp.StatusCode : une réponse 4xx/5xx n'est pas une erreur Go.
  • Fixez un Timeout sur le http.Client pour borner chaque appel.
  • Préférez un client généré (go-github, gitlab) quand il existe : moins de code, moins de bugs.
  • http.NewRequestWithContext est la base : une requête annulable et authentifiée.
  • Le décodage JSON ne modélise que les champs utiles via des tags json.
  • La pagination consiste à suivre la page suivante jusqu'à l'épuisement.
  • defer Body.Close() et la vérification du statut sont non négociables.
  • Pour GitHub/GitLab, un client généré encapsule auth, pagination et limites de débit.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn