Building an API with GraphQL and Go

Building an API with GraphQL and Go
In this blog post we are going set up an API using Go, GraphQL, and PostgreSQL. I’ve gone through a few different iterations on the structure of this application and so far this is the one I like the best.

For the most part, my background in writing web APIs has been with Node.js and Ruby/Rails. I’m finding that this created a bit of a struggle when first trying to figure out designing web apis in Go. One article that was really helpful to me was Structuring Applications in Go by Ben Johnson. It has influenced some of the code in this tutorial and I recommend reading!

Setup

First things first, lets start by getting the project set up. I’ll be using macOS for the tutorial but for the most part that won’t matter. Here are links to setting up Go and PostgreSQL on mac if you don’t have them on your machine. Below is a link to the code in full for reference:

Create a new project - we’ll call it go-graphql-api. To make it easier lets also create the whole project structure right away.

├── gql
│   ├── gql.go
│   ├── queries.go
│   ├── resolvers.go
│   └── types.go
├── main.go
├── postgres
│   └── postgres.go
└── server
    └── server.go

There are a couple of Go dependencies we’ll also want to grab. I like using realize for hot reloading while developing, chi and render from go-chi as a lightweight router and to help manage request/response payloads, and graphql-go/graphql as our GraphQL implementation.

go get github.com/oxequa/realizego get 

Lastly, let’s set up a database and some mock data to be able to test against. Run psql to enter the Postgres console and we’ll create a database.

CREATE DATABASE go_graphql_db;

Then we’ll connect to it:

\c go_graphql_db

Once connected, go ahead and paste the following SQL into the console.

CREATE TABLE users (
  id serial PRIMARY KEY,
  name VARCHAR (50) NOT NULL,
  age INT NOT NULL,
  profession VARCHAR (50) NOT NULL,
  friendly BOOLEAN NOT NULL
);

INSERT INTO users VALUES
  (1, 'kevin', 35, 'waiter', true),
  (2, 'angela', 21, 'concierge', true),
  (3, 'alex', 26, 'zoo keeper', false),
  (4, 'becky', 67, 'retired', false),
  (5, 'kevin', 15, 'in school', true),
  (6, 'frankie', 45, 'teller', true);

We just created a basic users table and inserted five new users. This will do just fine for the purpose of this walkthrough. We should now be good to start building out our API!

API

Throughout this post all of the code snippets will include a fair bit of comments within them to help with explaining things step by step.

We’ll start with main.go.

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/bradford-hamilton/go-graphql-api/gql"
	"github.com/bradford-hamilton/go-graphql-api/postgres"
	"github.com/bradford-hamilton/go-graphql-api/server"
	"github.com/go-chi/chi"
	"github.com/go-chi/chi/middleware"
	"github.com/go-chi/render"
	"github.com/graphql-go/graphql"
)

func main() {
	// Initialize our api and return a pointer to our router for http.ListenAndServe
	// and a pointer to our db to defer its closing when main() is finished
	router, db := initializeAPI()
	defer db.Close()

	// Listen on port 4000 and if there's an error log it and exit
	log.Fatal(http.ListenAndServe(":4000", router))
}

func initializeAPI() (*chi.Mux, *postgres.Db) {
	// Create a new router
	router := chi.NewRouter()

	// Create a new connection to our pg database
	db, err := postgres.New(
		postgres.ConnString("localhost", 5432, "bradford", "go_graphql_db"),
	)
	if err != nil {
		log.Fatal(err)
	}

	// Create our root query for graphql
	rootQuery := gql.NewRoot(db)
	// Create a new graphql schema, passing in the the root query
	sc, err := graphql.NewSchema(
		graphql.SchemaConfig{Query: rootQuery.Query},
	)
	if err != nil {
		fmt.Println("Error creating schema: ", err)
	}

	// Create a server struct that holds a pointer to our database as well
	// as the address of our graphql schema
	s := server.Server{
		GqlSchema: &sc,
	}

	// Add some middleware to our router
	router.Use(
		render.SetContentType(render.ContentTypeJSON), // set content-type headers as application/json
		middleware.Logger,          // log api request calls
		middleware.DefaultCompress, // compress results, mostly gzipping assets and json
		middleware.StripSlashes,    // match paths with a trailing slash, strip it, and continue routing through the mux
		middleware.Recoverer,       // recover from panics without crashing server
	)

	// Create the graphql route with a Server method to handle it
	router.Post("/graphql", s.GraphQL())

	return router, db
}

Keep in mind the import paths up top for your local gql, postgres, and server packages will be different (path will include your name not mine) as well as the Postgres user you pass into postgres.ConnString(). There are a few different things going on inside of initializeAPI() so we’ll go through step by step and build out each piece.

The router is created with chi.NewRouter() which returns a mux for us. The next few lines we are creating a new connection to our Postgres database. We build a connection string with postgres.ConnString() and pass it into postgres.New(). This is our package code so let’s build that out! We’ll hop over to postgres.go.

package postgres

import (
	"database/sql"
	"fmt"

	// postgres driver
	_ "github.com/lib/pq"
)

// Db is our database struct used for interacting with the database
type Db struct {
	*sql.DB
}

// New makes a new database using the connection string and
// returns it, otherwise returns the error
func New(connString string) (*Db, error) {
	db, err := sql.Open("postgres", connString)
	if err != nil {
		return nil, err
	}

	// Check that our connection is good
	err = db.Ping()
	if err != nil {
		return nil, err
	}

	return &Db{db}, nil
}

// ConnString returns a connection string based on the parameters it's given
// This would normally also contain the password, however we're not using one
func ConnString(host string, port int, user string, dbName string) string {
	return fmt.Sprintf(
		"host=%s port=%d user=%s dbname=%s sslmode=disable",
		host, port, user, dbName,
	)
}

// User shape
type User struct {
	ID         int
	Name       string
	Age        int
	Profession string
	Friendly   bool
}

// GetUsersByName is called within our user query for graphql
func (d *Db) GetUsersByName(name string) []User {
	// Prepare query, takes a name argument, protects from sql injection
	stmt, err := d.Prepare("SELECT * FROM users WHERE name=$1")
	if err != nil {
		fmt.Println("GetUserByName Preperation Err: ", err)
	}

	// Make query with our stmt, passing in name argument
	rows, err := stmt.Query(name)
	if err != nil {
		fmt.Println("GetUserByName Query Err: ", err)
	}

	// Create User struct for holding each row's data
	var r User
	// Create slice of Users for our response
	users := []User{}
	// Copy the columns from row into the values pointed at by r (User)
	for rows.Next() {
		err = rows.Scan(
			&r.ID,
			&r.Name,
			&r.Age,
			&r.Profession,
			&r.Friendly,
		)
		if err != nil {
			fmt.Println("Error scanning rows: ", err)
		}
		users = append(users, r)
	}

	return users

The general idea here is that we provide the ability to create a new database which returns a Db struct holding the connection. We then write GetUserByUsername() as a method on Db. As you can tell the actual functionality/query is pretty useless, but the point is to get everything wired up as a good starting point.

Our next concern back in main.go is on line 40 where we’re creating a a root query that will get used to create our GraphQL schema. Let’s jump into queries.go inside our gql package.

package gql

import (
	"github.com/bradford-hamilton/go-graphql-api/postgres"
	"github.com/graphql-go/graphql"
)

// Root holds a pointer to a graphql object
type Root struct {
	Query *graphql.Object
}

// NewRoot returns base query type. This is where we add all the base queries
func NewRoot(db *postgres.Db) *Root {
	// Create a resolver holding our databse. Resolver can be found in resolvers.go
	resolver := Resolver{db: db}

	// Create a new Root that describes our base query set up. In this
	// example we have a user query that takes one argument called name
	root := Root{
		Query: graphql.NewObject(
			graphql.ObjectConfig{
				Name: "Query",
				Fields: graphql.Fields{
					"users": &graphql.Field{
						// Slice of User type which can be found in types.go
						Type: graphql.NewList(User),
						Args: graphql.FieldConfigArgument{
							"name": &graphql.ArgumentConfig{
								Type: graphql.String,
							},
						},
						Resolve: resolver.UserResolver,
					},
				},
			},
		),
	}
	return &root
}

We pass our db into NewRoot() and then create a Resolver with it. This way all of theResolver methods have access to the database. We then create a new root that has a users query. It takes a an argument called name and is of type graphql.NewList of User (slice/array of User) which we’ve moved out into types.go inside of the gql package. This is where we could start to grow our root to hold many different kinds of queries. Remember to change the import path for your local postgres package to your own. Let’s take a look at resolvers.go.

package gql

import (
	"github.com/bradford-hamilton/go-graphql-api/postgres"
	"github.com/graphql-go/graphql"
)

// Resolver struct holds a connection to our database
type Resolver struct {
	db *postgres.Db
}

// UserResolver resolves our user query through a db call to GetUserByName
func (r *Resolver) UserResolver(p graphql.ResolveParams) (interface{}, error) {
	// Strip the name from arguments and assert that it's a string
	name, ok := p.Args["name"].(string)
	if ok {
		users := r.db.GetUsersByName(name)
		return users, nil
	}

	return nil, nil
}

Same thing again here with updating the postgres import. Here is where we would start adding more resolver methods when we need them. Let’s open up types.go.

package gql

import "github.com/graphql-go/graphql"

// User describes a graphql object containing a User
var User = graphql.NewObject(
	graphql.ObjectConfig{
		Name: "User",
		Fields: graphql.Fields{
			"id": &graphql.Field{
				Type: graphql.Int,
			},
			"name": &graphql.Field{
				Type: graphql.String,
			},
			"age": &graphql.Field{
				Type: graphql.Int,
			},
			"profession": &graphql.Field{
				Type: graphql.String,
			},
			"friendly": &graphql.Field{
				Type: graphql.Boolean,
			},
		},
	},
)

In a similar fashion, here is where we would start adding all of our different types. As you can see within each field we specify the type as well. Now if you look on line 42 back in main.go we create a new schema with our root query.

Almost there!

Down a little further on line 51 we create a new server. It holds a pointer to our GraphQL schema. Let’s check out server.go.

package server

import (
	"encoding/json"
	"net/http"

	"github.com/bradford-hamilton/go-graphql-api/gql"
	"github.com/go-chi/render"
	"github.com/graphql-go/graphql"
)

// Server will hold connection to the db as well as handlers
type Server struct {
	GqlSchema *graphql.Schema
}

type reqBody struct {
	Query string `json:"query"`
}

// GraphQL returns an http.HandlerFunc for our /graphql endpoint
func (s *Server) GraphQL() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// Check to ensure query was provided in the request body
		if r.Body == nil {
			http.Error(w, "Must provide graphql query in request body", 400)
			return
		}

		var rBody reqBody
		// Decode the request body into rBody
		err := json.NewDecoder(r.Body).Decode(&rBody)
		if err != nil {
			http.Error(w, "Error parsing JSON request body", 400)
		}

		// Execute graphql query
		result := gql.ExecuteQuery(rBody.Query, *s.GqlSchema)

		// render.JSON comes from the chi/render package and handles
		// marshalling to json, automatically escaping HTML and setting
		// the Content-Type as application/json.
		render.JSON(w, r, result)
	}
}

Here we have a method on our server called GraphQL. For all intents and purposes this will be the only method needed to handle GraphQL queries. Reminder: update the import path for gql. The last file we need to look at is gql.go.

package gql

import (
	"fmt"

	"github.com/graphql-go/graphql"
)

// ExecuteQuery runs our graphql queries
func ExecuteQuery(query string, schema graphql.Schema) *graphql.Result {
	result := graphql.Do(graphql.Params{
		Schema:        schema,
		RequestString: query,
	})

	// Error check
	if len(result.Errors) > 0 {
		fmt.Printf("Unexpected errors inside ExecuteQuery: %v", result.Errors)
	}

	return result
}

This simply holds the function ExecuteQuery() which runs our GraphQL queries. I imagined that in here is where we would create something like an ExecuteMutation() type function as well to handle GraphQL mutations.

At the end of initializeAPI() we add some middleware to the router and mount our GraphQL server method to handle POSTs to /graphql. If any RESTful routes were needed, they could be mounted here and methods for handling them could be added to Server.

Go ahead and run realize init in the root of the project. You should be prompted twice and respond with n both times.

This creates the .realize.yaml file in the root of your project. Let’s go ahead and overwrite it with:

settings:
  legacy:
    force: false
    interval: 0s
schema:
- name: go-graphql-api
  path: .
  commands:
    run:
      status: true
  watcher:
    extensions:
    - go
    paths:
    - /
    ignored_paths:
    - .git
    - .realize
    - vendor

The important piece of this config is that it will watch for any changes within the project and when it detects one, it will automatically restart the server and rerun main.go.

There are some great tools for exploring your GraphQL API like graphiql, insomnia, and graphql-playground. You can also just make a POST request sending over a raw application/json body like this:

{
 "query": "{users(name:\"kevin\"){id, name, age}}"
}

In Postman it would look something like this:

Go ahead and ask for just one, or any combination of attributes in your query. In true GraphQL fashion we can request just the information we want sent over the wire.

Great Success!

That’s it! Hopefully this has been useful to help get you off the ground with writing a GraphQL API in Go. I tried to break it down into it’s package/file layout so that it had room to grow/scale, for clarity, and to make testing each piece easier. Please feel free to leave comments if you have any recommendations or questions!

Recommended Courses:

Persistence with mongoDB in Go(golang)

Go: The Complete Developer’s Guide (Golang)

Google Go Programming for Beginners (golang)

Getting started with Cloud Native Go

Advanced Google’s Go (golang) Programming Course

Suggest:

Learn GraphQL with Laravel and Vue.js - Full Tutorial

Build A Restful Api With Node.js Express & MongoDB | Rest Api Tutorial

Making It All Fit Together with React and GraphQL

APIs for Beginners - How to use an API (Full Course)

Express.js & Node.js Course for Beginners - Full Tutorial

GraphQL Basics - Build an app with the SpaceX API