Create a graphql backend on Golang

Today we will develop an application in Golang + GraphQL.

We often use GraphQL in our projects and we know a lot about it, used it together with various programming languages: Javascript, Ruby and now our hands have reached the point of trying the Golang GraphQL bunch.

A lot has been said about the advantages of GraphQL query language on the Internet, many praise it for its simplicity, flexibility and convenience, both when used on the server side and on the client.

For convenient development using GraphQL, GraphiQL or GraphQL Playground is often configured - an interface for sending requests and viewing API documentation.
To deploy such a Playground locally, it is enough to use this open source solution - Golang HTTP.Handler for graphl-go.

graphql playground golang

It looks like a running playground.

Let's move on to writing a small application, an example of which will figure out how to work with GraphQL and Go. The full application code can be found on the link on Github .

First of all, we’ll start the server and the playground.

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

Not a single graphql backend can do without a description of the circuit; now we will analyze its 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,
                 },
              },
           },
        },
     })}
}

Above, we described the User type with the fields _id, firstName, lastName and email, which we will use as the type of response to requests. The _id field is of type ObjectID, which is a custom type, since it was necessary to serialize the native ObjectID type MongoDB, which is a structure of this kind.

type InsertOneResult struct {
  InsertedID interface{}
}

To describe the input parameters to the mutation for adding a user, the UserInput type was created containing 3 optional fields that describe our user. The _id field is absent in this type, since it will be generated in the resolver.

To connect to mongodb, this project uses the golang mongodb driver.

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
}

Here we processed the user acquisition and mutation request using mongodb as a place to store user data. Instead of this database, you could use any other without any problems, both non-relational and relational, because GraphQL does not limit us in choosing a database.

I used docker compose to bundle golang and mongodb. For this, I described a small settings file. Everything is ready. A couple of golang mongo technologies allows us to store user data in a convenient form for further return from GraphQL queries.

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





Of course, this application turned out to be quite simple and compact. And it can be taken as a basis for your next project and supplemented with new types, requests and mutations, and other functionality. This time we did not consider examples of implementation of some more interesting GraphQL features, for example, subscription. Perhaps I will continue this example in the future, so leave comments if something was missing for you in this article and in the next part we will definitely consider this.

All Articles