In this article we will build a GraphQL server using Go and gqlgen library. Then we will expose our server using HTTPs and run it as a Docker container. gqlgen is a Go library for building GraphQL servers: https://github.com/99designs/gqlgen.

The source code can be found here: https://github.com/marukhno/go-graphql

The article includes steps to:
1. Initialize a Go module and GraphQL project
2. Define GraphQL schema
3. Develop resolvers for GraphQL queries and mutations
4. Configure HTTPs for Go GraphQL server
5. Run Go GraphQL server in a Docker container

1. Initialize a Go module and GraphQL project

mkdir go-graphql && cd go-graphql
echo "# go-graphql" >> README.md
git init
Create .gitignore file if needed.
git add README.md .gitignore
git commit -m "first commit"
git branch -M main
Create go-graphql repository on Github.
git remote add origin https://github.com/marukhno/go-graphql.git
git push -u origin main
Initialize Go module:
go mod init github.com/marukhno/go-graphql

Install gqlgen - a Go library for building GraphQL servers
go get github.com/99designs/gqlgen

Initialize a new project using the recommended folder structure by running this command
go run github.com/99designs/gqlgen init

gqlgen project layout after initialization

2. Define GraphQL schema

gqlgen is based on a Schema first approach. Thus, you need to create a schema and then gqlgen will generate resolvers for you. Create the following schema in schema.graphqls. Delete everything that was generated there on initialization.
vi graph/schema.graphqls

# Query is an entry point for the requests sent by the client.
# This Query can be used to get an Account by its name and an array of Account available.
type Query {
  account(id: ID!): Account
  accounts(limit: Int): [Account]
}

type Account {
  id: ID!
  name: Name!
  shippingAddress: Address!
  creditCard: CreditCard!
}

type Name {
  first: String!
  last: String!
}

type Address {
  country: String!
  street: String!
  state: String!
  zip: String!
  building: String!
}

type CreditCard {
  number: String!
  pin: Int!
  expirationDate: String!
}

To visualize this schema you can use GraphQL Voyager tool: https://github.com/APIs-guru/graphql-voyager. Open https://apis.guru/graphql-voyager/ and upload your SDL schema there.

GraphQL Voyager - schema visualisation tool

Remove graph/schema.resolvers.go file in order to delete built-in example resolvers
rm graph/schema.resolvers.go
Now run the following command to regenerate resolvers based on your SDL
go run github.com/99designs/gqlgen generate

3. Develop resolvers for GraphQL queries and mutations

At the first step we will create mock answer for a query.
Open schema.resolvers.go file which contains realization for your queries:

Resolvers generated by gqlgen

Let's create a simple mock response for the query "Account" in schema.resolvers.go.

func (r *queryResolver) Account(ctx context.Context, id string) (*model.Account, error) {
	mockAcc := &model.Account{
		Name: &model.Name{
			First: "Ivan",
			Last:  "Ivanov",
		},
		ShippingAddress: &model.Address{
			Country:  "Russia",
			Street:   "Lenina",
			State:    "Moscow",
			Zip:      "123456",
			Building: "1",
		},
		CreditCard: &model.CreditCard{
			Number: "1234 5678 9101 2131",
			Pin:    555,
			ExpirationDate: "20-02-2022",
		},
	}
	return mockAcc, nil
}

Now run the server with
go run server.go

And send this query in Graphiql (open it in a browser using http://localhost:8080/):

{
  account(id: "1"){
    id
    name{
      first
      last
    }
    shippingAddress{
      country
      street
      state
      zip
      building
    }
    creditCard{
      number
      pin
      expirationDate
    }
  }
}

Running this query we will see our mock response we have created recently:

GraphQL query response

If we need a subset of data we can just specify which fields we are interested in:

GraphQL query a subset of data

Now we extend the schema and add a Mutation which is used when you need to create, update or delete existing data.
Add the following to graph/schema.graphqls:

input NewName{
  first: String!
  last: String!
}

input NewAddress{
  country: String!
  street: String!
  state: String!
  zip: String!
  building: String!
}

input NewCreditCard{
  number: String!
  pin: Int!
  expirationDate: String!
}

input NewAccount{
  id: ID!
  name: NewName!
  shippingAddress: NewAddress!
  creditCard: NewCreditCard!
}

type Mutation {
  createAccount(input: NewAccount!): Account!
}

Regenerate resolvers
go run github.com/99designs/gqlgen generate

Open schema.resolvers.go and develop an implementation for the Mutation.

func (r *mutationResolver) CreateAccount(ctx context.Context, input model.NewAccount) (*model.Account, error) {
	mockAcc := model.Account{
		ID: input.ID,
		Name: &model.Name{
			First: input.Name.First,
			Last:  input.Name.Last,
		},
		ShippingAddress: &model.Address{
			Country:  input.ShippingAddress.Country,
			Street:   input.ShippingAddress.Street,
			State:    input.ShippingAddress.State,
			Zip:      input.ShippingAddress.Zip,
			Building: input.ShippingAddress.Building,
		},
		CreditCard: &model.CreditCard{
			Number:         input.CreditCard.Number,
			Pin:            input.CreditCard.Pin,
			ExpirationDate: input.CreditCard.ExpirationDate,
		},
	}
	return &mockAcc, nil
}

Rerun the server and test your mutation with the following request:

mutation{
  createAccount(input:
    {
      id: 1,
      name:{
        first:"John",
        last: "Jones"
      },
      shippingAddress:{
        country: "USA",
        street: "astreet",
        state: "VA",
        zip: "12345",
        building: "12"
      },
      creditCard:{
        number: "1234 5678 9101 2131",
        pin: 123,
        expirationDate: "20-05-2023"
      }
    })
    {
      id
      name{
        first
        last
      }
    }
}
Running GraphQL mutation request

Now, when we tested our Query and Mutation with mock data, let's create a simple realization by adding a structure to keep our data.
Edit resolver.go file and add the following row there. This map will keep our Accounts data which can be accessed by id.
var Accounts = make(map[string]*model.Account)

Update your resolvers (schema.resolvers.go) to look like this:

func (r *mutationResolver) CreateAccount(ctx context.Context, input model.NewAccount) (*model.Account, error) {
	mockAcc := model.Account{
		ID: input.ID,
		Name: &model.Name{
			First: input.Name.First,
			Last:  input.Name.Last,
		},
		ShippingAddress: &model.Address{
			Country:  input.ShippingAddress.Country,
			Street:   input.ShippingAddress.Street,
			State:    input.ShippingAddress.State,
			Zip:      input.ShippingAddress.Zip,
			Building: input.ShippingAddress.Building,
		},
		CreditCard: &model.CreditCard{
			Number:         input.CreditCard.Number,
			Pin:            input.CreditCard.Pin,
			ExpirationDate: input.CreditCard.ExpirationDate,
		},
	}
	Accounts[input.ID] = &mockAcc
	return &mockAcc, nil
}

func (r *queryResolver) Account(ctx context.Context, id string) (*model.Account, error) {
	if acc, ok := Accounts[id]; ok {
		return acc, nil
	} else {
		return nil, nil
	}
}

func (r *queryResolver) Accounts(ctx context.Context, limit *int) ([]*model.Account, error) {
	accArray := make([]*model.Account, 0, len(Accounts))
	for _,v := range Accounts {
		accArray = append(accArray, v)
	}
	l := *limit
	if l > len(accArray) {
		l = len(accArray)
	}

	return accArray[:l], nil
}

Rerun the server and test your GraphQL server by inserting a few Accounts using CreateAccount mutation and querying a particular Account or a group of Accounts using Account and Accounts Queries.
To get a list of Accounts you can use this query:

{
  accounts(limit:2){
    id
    name{first, last}
    shippingAddress{country}
    creditCard{number}
  }
}
Running a query on GraphQL server

4. Configure HTTPs for Go GraphQL server

The server is available through HTTP. Let's configure it to use HTTPs. For this example we will generate self-signed certificate and key.
Generate TLS certificate and key:
mkdir certs && cd certs

openssl req \
  -newkey rsa:4096 -nodes -sha256 -keyout go-graphql.key \
  -x509 -days 365 -out go-graphql.crt

Answer questions suggested by the wizard:

openssl certificate and key generation

Change server.go main() function to use HTTPs

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = defaultPort
	}

	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))

	http.Handle("/", playground.Handler("GraphQL playground", "/query"))
	http.Handle("/query", srv)

	//log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
	//log.Fatal(http.ListenAndServe(":"+port, nil))
	log.Printf("connect to https://localhost:%s/ for GraphQL playground", port)
	err := http.ListenAndServeTLS(":"+port, "certs/go-graphql.crt", "certs/go-graphql.key", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

Rerun the server. You should see that your server is available through HTTPs this time. As we used self-signed certificate you will see "unknown certificate" warnings in logs. Just ignore them.

5. Run Go GraphQL server in a Docker container

Now we will create a Docker image for our GraphQL server and run it as a Docker container.
Create a Dockerfile:

vi Dockerfile
FROM golang:1.15.5
RUN mkdir /app
ADD . /app/
WORKDIR /app
RUN go build -o main .
EXPOSE 8080
CMD ["/app/main"]

Build an image based on the Dockerfile:
docker build --tag=go-graphql:1.0.0 .

Run your server in a container. If you need it to be run in background add "-d" flag.
docker run -p 8080:8080 go-graphql:1.0.0

Open Graphiql test client and check that you can send GraphQL requests to the container:

Query Go GraphQL running in a Docker container