Crear un backend graphql en Golang

Hoy desarrollaremos una aplicación en Golang + GraphQL.

A menudo usamos GraphQL en nuestros proyectos y sabemos mucho al respecto, lo usamos junto con varios lenguajes de programación: Javascript, Ruby, y ahora nuestras manos han llegado al punto de probar el paquete Golang GraphQL.

Se ha dicho mucho sobre las ventajas del lenguaje de consulta GraphQL en Internet, muchos lo elogian por su simplicidad, flexibilidad y conveniencia, tanto cuando se usa en el lado del servidor como en el cliente.

Para un desarrollo conveniente utilizando GraphQL, GraphiQL o GraphQL Playground, a menudo se configura una interfaz para enviar solicitudes y ver la documentación de la API.
Para implementar dicho Playground localmente, es suficiente usar esta solución de código abierto: Golang HTTP.Handler para graphl-go.

patio de juegos Graphql Golang

Parece un parque infantil en funcionamiento.

Pasemos a escribir una pequeña aplicación, un ejemplo de la cual descubrirá cómo trabajar con GraphQL y Go. El código completo de la aplicación se puede encontrar en el enlace en Github .

En primer lugar, iniciaremos el servidor y el patio de recreo.

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)
  }
}

Ningún backend de graphql puede prescindir de una descripción del circuito; ahora analizaremos su descripción.

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,
                 },
              },
           },
        },
     })}
}

Arriba, describimos el tipo de usuario con los campos _id, firstName, lastName y email, que usaremos como tipo de respuesta a las solicitudes. El campo _id es de tipo ObjectID, que es un tipo personalizado, ya que era necesario serializar el tipo de ObjectID nativo MongoDB, que es una estructura de este tipo.

type InsertOneResult struct {
  InsertedID interface{}
}

Para describir los parámetros de entrada a la mutación para agregar un usuario, se creó el tipo UserInput que contiene 3 campos opcionales que describen a nuestro usuario. El campo _id está ausente en este tipo, ya que se generará en el resolutor.

Para conectarse a mongodb, este proyecto utiliza el controlador 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
}

Aquí procesamos la solicitud de adquisición y mutación del usuario, utilizando mongodb como un lugar para almacenar datos del usuario. En lugar de esta base de datos, puede usar cualquier otra sin problemas, tanto no relacionales como relacionales, porque GraphQL no nos limita a la hora de elegir una base de datos.

Usé docker compose para agrupar golang y mongodb. Para esto, describí un pequeño archivo de configuración. Todo está listo. Un par de tecnologías de Golang Mongo nos permite almacenar datos de usuario en una forma conveniente para un mayor retorno de las consultas 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





Por supuesto, esta aplicación resultó ser bastante simple y compacta. Y puede tomarse como base para su próximo proyecto y complementarse con nuevos tipos, solicitudes y mutaciones, y otras funciones. Esta vez no consideramos ejemplos de implementación de algunas características más interesantes de GraphQL, por ejemplo, suscripción. Quizás continúe este ejemplo en el futuro, así que deje comentarios si le falta algo en este artículo y en la siguiente parte definitivamente lo consideraremos.

All Articles