Créer un backend graphql sur Golang

Aujourd'hui, nous allons développer une application dans Golang + GraphQL.

Nous utilisons souvent GraphQL dans nos projets et nous en savons beaucoup sur lui, nous l'avons utilisé avec divers langages de programmation: Javascript, Ruby et maintenant nos mains ont atteint le point d'essayer le bouquet Golang GraphQL.

On a beaucoup parlé des avantages du langage de requête GraphQL sur Internet, beaucoup le louent pour sa simplicité, sa flexibilité et sa commodité, à la fois lorsqu'il est utilisé côté serveur et côté client.

Pour un développement pratique à l'aide de GraphQL, GraphiQL ou GraphQL Playground est souvent configuré - une interface pour envoyer des demandes et afficher la documentation de l'API.
Pour déployer un tel Playground localement, il suffit d'utiliser cette solution open source - Golang HTTP.Handler pour graphl-go.

aire de jeux graphql golang

Cela ressemble à une cour de récréation.

Passons maintenant à l'écriture d'une petite application, dont un exemple expliquera comment travailler avec GraphQL et Go. Le code d'application complet peut être trouvé sur le lien sur Github .

Tout d'abord, nous allons démarrer le serveur et la cour de récréation.

func main() {
  schema, err := graphql.NewSchema(defineSchema()) //      
  if err != nil {
     log.Panic("Error when creating the graphQL schema", err)
  }

  h := handler.New(&handler.Config{
     Schema:     &schema,
     Pretty:     true,
     GraphiQL:   false,
     Playground: true,
  }) //      FormatErrorFn -    

  http.Handle("/graphql", h) //      playground    
  err = http.ListenAndServe(":8080", nil)
  if err != nil {
     log.Panic("Error when starting the http server", err)
  }
}

Aucun des backend graphql ne peut se passer d'une description du schéma, nous allons maintenant analyser sa description.

var User = graphql.NewObject(
  graphql.ObjectConfig{
     Name: "User",
     Fields: graphql.Fields{
        "_id": &graphql.Field{
           Type: ObjectID,
        },
        "firstName": &graphql.Field{
           Type: graphql.String,
        },
        "lastName": &graphql.Field{
           Type: graphql.String,
        },
        "email": &graphql.Field{
           Type: graphql.String,
        },
     },
  },
)

var UserInput = graphql.NewInputObject(
  graphql.InputObjectConfig{
     Name: "UserInput",
     Fields: graphql.InputObjectConfigFieldMap{
        "firstName": &graphql.InputObjectFieldConfig{
           Type: graphql.String,
        },
        "lastName": &graphql.InputObjectFieldConfig{
           Type: graphql.Int,
        },
        "email": &graphql.InputObjectFieldConfig{
           Type: graphql.String,
        },
     },
  },
)

func defineSchema() graphql.SchemaConfig {
  return graphql.SchemaConfig{
     Query: graphql.NewObject(graphql.ObjectConfig{
        Name: "Query",
        Fields: graphql.Fields{
           "users": &graphql.Field{
              Name:    "users",
              Type:    graphql.NewList(User),
              Resolve: usersResolver,
           },
        },
     }),
     Mutation: graphql.NewObject(graphql.ObjectConfig{
        Name: "Mutation",
        Fields: graphql.Fields{
           "addUser": &graphql.Field{
              Name:    "addUser",
              Type:    User,
              Resolve: addUserResolver,
              Args: graphql.FieldConfigArgument{
                 "input": &graphql.ArgumentConfig{
                    Type: UserInput,
                 },
              },
           },
        },
     })}
}

Ci-dessus, nous avons décrit le type d'utilisateur avec les champs _id, firstName, lastName et email, que nous utiliserons comme type de réponse aux demandes. Le champ _id est de type ObjectID, qui est un type personnalisé, car il était nécessaire de sérialiser le type ObjectID natif MongoDB, qui est une structure de ce type.

type InsertOneResult struct {
  InsertedID interface{}
}

Pour décrire les paramètres d'entrée à la mutation pour ajouter un utilisateur, le type UserInput a été créé contenant 3 champs facultatifs qui décrivent notre utilisateur. Le champ _id est absent dans ce type, car il sera généré dans le résolveur.

Pour se connecter à mongodb, ce projet utilise le pilote golang mongodb.

func usersCollection() *mongo.Collection { // ,   users    
  ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
  client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://mongo:27017"))
  if err != nil {
     log.Panic("Error when creating mongodb connection client", err)
  }
  collection := client.Database("testing").Collection("users")
  err = client.Connect(ctx)
  if err != nil {
     log.Panic("Error when connecting to mongodb", err)
  }

  return collection
}


//      
func usersResolver(_ graphql.ResolveParams) (interface{}, error) {
  ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
  collection := usersCollection()
  result, err := collection.Find(ctx, bson.D{})
  if err != nil {
     log.Print("Error when finding user", err)
     return nil, err
  }

  defer result.Close(ctx)

  var r []bson.M
  err = result.All(ctx, &r)
  if err != nil {
     log.Print("Error when reading users from cursor", err)
  }

  return r, nil
}


//     
func addUserResolver(p graphql.ResolveParams) (interface{}, error) {
  ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
  collection := usersCollection()
  //         ,   _id
  id, err := collection.InsertOne(ctx, p.Args["input"])
  if err != nil {
     log.Print("Error when inserting user", err)
     return nil, err
  }

  var result bson.M
  //       _id
  err = collection.FindOne(ctx, bson.M{"_id": id.InsertedID}).Decode(&result)
  if err != nil {
     log.Print("Error when finding the inserted user by its id", err)
     return nil, err
  }

  return result, nil
}

Ici, nous avons traité la demande d'acquisition et de mutation des utilisateurs, en utilisant mongodb comme lieu de stockage des données des utilisateurs. Au lieu de cette base de données, vous pouvez en utiliser une autre sans aucun problème, à la fois non relationnel et relationnel, car GraphQL ne nous limite pas dans le choix d'une base de données.

J'ai utilisé docker compose pour regrouper golang et mongodb. Pour cela, j'ai décrit un petit fichier de paramètres. Tout est prêt. Une paire de technologies golang mongo nous permet de stocker les données utilisateur sous une forme pratique pour un retour ultérieur des requêtes GraphQL.

version: '3'
services:
graphql:
image: golang
volumes:
- .:/go/src
command: /bin/bash -c "cd src && go run *.go"
ports:
- 8080:8080
mongo:
image: mongo





Bien sûr, cette application s'est avérée assez simple et compacte. Et il peut être utilisé comme base pour votre prochain projet et complété par de nouveaux types, demandes et mutations, et d'autres fonctionnalités. Cette fois, nous n'avons pas considéré d'exemples d'implémentation de fonctionnalités GraphQL plus intéressantes, par exemple, l'abonnement. Je continuerai peut-être cet exemple à l'avenir, alors laissez des commentaires si quelque chose ne vous suffisait pas dans cet article et dans la partie suivante, nous y réfléchirons certainement.

All Articles