Pourquoi Go avec GraphQL ?
GraphQL est un langage de requêtes et une spécification pour API qui permet aux clients d'interroger uniquement les données dont ils ont besoin, ce qui peut améliorer significativement la performance des applications web et mobiles. En tant que développeur Go, le choix de GraphQL peut être bénéfique pour plusieurs raisons :
Performance : Les requêtes GraphQL permettent aux clients de récupérer les données nécessaires sans avoir à faire des appels supplémentaires. Cela réduit la quantité de données transmises et améliore ainsi l'expérience utilisateur.
Flexibilité : Avec GraphQL, les clients peuvent spécifier exactement ce qu'ils veulent demander dans une requête, ce qui rend l'API plus flexible et évolutrice.
Un cas d'utilisation concret pourrait être un service de gestion des projets où un utilisateur ne souhaite récupérer que les détails essentiels d'un projet particulier, sans avoir à faire appel à d'autres endpoints inutilesment.
Prerequis
- Connaissance de base du langage Go
- Familiarité avec les concepts de bases d'API et HTTP
- Installation de Go : https://golang.org/dl/
Concepts fondamentaux
1. Schéma (Schema)
Le schéma définit l'interface de votre API GraphQL. Il décrit les types disponibles, leurs champs, et comment ils sont liés entre eux.
Exemple de schéma :
type Query {
project(id: ID!): Project
}
type Mutation {
createProject(name: String!): Project
}
type Project {
id: ID!
name: String!
tasks: [Task]
}
type Task {
id: ID!
title: String!
completed: Boolean
}
2. Resolveurs (Resolvers)
Les résolveurs sont des fonctions qui exécutent la logique de récupération des données pour chaque champ du schéma.
Exemple de résolveur :
func projectResolver(p graphql.ResolveParams) (interface{}, error) {
id := p.Args["id"].(string)
// Logique pour récupérer le projet
return project, nil
}
3. Contexte (Context)
Le context est utilisé pour passer des données et des informations entre les résolveurs.
Exemple d'utilisation du context :
func createProjectResolver(p graphql.ResolveParams) (interface{}, error) {
name := p.Args["name"].(string)
ctx := p.Context.Value("user").(*User)
// Logique pour créer le projet en utilisant les informations de l'utilisateur
return project, nil
}
4. Serveur GraphQL
Vous pouvez utiliser une bibliothèque comme graphql-go pour exposer un serveur GraphQL.
Exemple de serveur :
package main
import (
"net/http"
"github.com/graphql-go/graphql"
"github.com/graphql-go/handler"
)
var schema, _ = graphql.NewSchema(graphql.SchemaConfig{
Query: queryRoot,
Mutation: mutationRoot,
})
func main() {
h := handler.NewDefault()
h.SetGraphQLHandler(schema)
http.Handle("/graphql", h)
http.ListenAndServe(":8080", nil)
}
Mise en pratique : projet fil rouge
Étape 1 : Installer les dépendances
go get github.com/graphql-go/graphql
go get github.com/graphql-go/handler
Étape 2 : Définir le schéma et les résolveurs
package main
import (
"net/http"
"github.com/graphql-go/graphql"
"github.com/graphql-go/handler"
)
type Project struct {
ID string `json:"id"`
Name string `json:"name"`
Tasks []Task `json:"tasks"`
}
type Task struct {
ID string `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
var projectType = graphql.NewObject(graphql.ObjectConfig{
Name: "Project",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.String,
},
"name": &graphql.Field{
Type: graphql.String,
},
"tasks": &graphql.Field{
Type: graphql.NewList(taskType),
},
},
})
var taskType = graphql.NewObject(graphql.ObjectConfig{
Name: "Task",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.String,
},
"title": &graphql.Field{
Type: graphql.String,
},
"completed": &graphql.Field{
Type: graphql.Boolean,
},
},
})
var queryRoot = graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"project": &graphql.Field{
Type: projectType,
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.String,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
id := p.Args["id"].(string)
// Logique pour récupérer le projet
return Project{ID: id, Name: "Sample Project", Tasks: []Task{}}, nil
},
},
},
})
var mutationRoot = graphql.NewObject(graphql.ObjectConfig{
Name: "Mutation",
Fields: graphql.Fields{
"createProject": &graphql.Field{
Type: projectType,
Args: graphql.FieldConfigArgument{
"name": &graphql.ArgumentConfig{
Type: graphql.String,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
name := p.Args["name"].(string)
// Logique pour créer le projet
return Project{ID: "1", Name: name, Tasks: []Task{}}, nil
},
},
},
})
var schema, _ = graphql.NewSchema(graphql.SchemaConfig{
Query: queryRoot,
Mutation: mutationRoot,
})
func main() {
h := handler.NewDefault()
h.SetGraphQLHandler(schema)
http.Handle("/graphql", h)
http.ListenAndServe(":8080", nil)
}
Étape 3 : Exécuter le serveur
go run main.go
Étape 4 : Interroger l'API GraphQL
Ouvrez un navigateur et accédez à http://localhost:8080/graphql. Vous pouvez utiliser les querys et mutations suivantes pour interagir avec l'API :
Query :
query {
project(id: "1") {
id
name
tasks {
id
title
completed
}
}
}
Mutation :
mutation {
createProject(name: "New Project") {
id
name
tasks {
id
title
completed
}
}
}
Erreurs frequentes et debugging
1. panic: runtime error: invalid memory address or nil pointer dereference
Code incorrect :
func resolveProject(p graphql.ResolveParams) (interface{}, error) {
id := p.Args["id"].(string)
project := db.FindProject(id) // db is a database connection
return project, nil
}
Code correct :
func resolveProject(p graphql.ResolveParams) (interface{}, error) {
id := p.Args["id"].(string)
project, err := db.FindProject(id) // db is a database connection
if err != nil {
return nil, err
}
return project, nil
}
2. undefined: graphql.NewSchema
Code incorrect :
schema, _ = graphql.NewSchema(graphql.SchemaConfig{
Query: queryRoot,
})
Code correct :
import (
"github.com/graphql-go/graphql"
)
schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: queryRoot,
})
if err != nil {
log.Fatal(err)
}
3. missing field resolver for field 'tasks' in type 'Project'
Code incorrect :
var projectType = graphql.NewObject(graphql.ObjectConfig{
Name: "Project",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.String,
},
"name": &graphql.Field{
Type: graphql.String,
},
},
})
Code correct :
var projectType = graphql.NewObject(graphql.ObjectConfig{
Name: "Project",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.String,
},
"name": &graphql.Field{
Type: graphql.String,
},
"tasks": &graphql.Field{
Type: graphql.NewList(taskType),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
project := p.Source.(Project)
// Logique pour récupérer les tâches du projet
return []Task{}, nil
},
},
},
})
Pour aller plus loin
1. Authentification et Autorisation
Ajouter des fonctionnalités d'authentification et d'autorisation pour sécuriser votre API.
2. Pagination et Filtrage
Ajouter des capacités de pagination et de filtrage pour permettre aux clients de récupérer des données plus complexes et détaillées.
3. Subscriptions
Permettre aux clients d'écouter les mises à jour en temps réel avec GraphQL Subscriptions.
Défi pratique
Créez une API de gestion des utilisateurs avec des fonctionnalités telles que l'inscription, la connexion et la récupération des profils d'utilisateurs. Implémentez les fonctionnalités CRUD pour les utilisateurs.
- Défi : Créer une API GraphQL pour gérer les utilisateurs