A Golang and GraphQL Project Starter to build High Performance, Cloud Native production APIs — fast!

I’ve been working with writing Graphql APIs for a while now — primarily writing Backend Services using Golang and PostgreSQL . Very recently, I also had the need to create a new project that exposed a GraphQL server based off one of my existing applications.

This is when I realized I could extract create a starter using my existing stack and make this open source for anyone to start using it. This article is all about a GraphQL api starter using Golang at it’s core, and goes through the application and the organization of application in a reasonable depth.Please refer to the end of this article for a link to this starter.

Please note that this is an opinionated stack which i like to use in real projects. This is not a one-minute guide. But you can build serious apps extremely fast using this stack with all the customization you need.

Application Overview

The core idea of the application is to expose a GraphQL server and write APIs really fast.

I built a sample application to demonstrate the capabilities within the starter. On a high level, the starter application just does two things. Allows users to save notes in a database. And the ability to view all notes as a query.

All the functionality is exposed via two simple GraphQL APIs and mutations. Before we dive deep, let’s look at the tech stack that powers this service.

Tech Stack

  • Go 1.19
  • GqlGen for our Graphql Server
  • Postgres and pgx driver. ( Install postgres using docker. https://hub.docker.com/_/postgres)
  • Docker ( For Hosting our Application ) & Docker Compose CLI

In terms of tech Stack, it’s a standard Golang application leveraging GqlGen framework. GqlGen is a framework for writing graphql servers in Golang. It is an amazing framework in my experience. For data storage, this application uses Postgres under the hood.

Dev Environment Setup

Apart from these core components, this starter also has the following third party library dependencies.

I chose wire for dependency injection because it strikes a nice balance between speed and the amount of code one must write for dependency management. It’s simple, compiled , and has zero runtime cost for dependency wiring.

The Taskfile component gives us the ability to write repeatable scripts and organizing them in a clean way. A simple Taskfile.yaml looks like this.

version: '3'  
env:  
  APP_ENV: dev  
tasks:  
  depsgen:  
    dir: cmd/graphql-server/dependencies  
    cmds:  
      - wire

depsgen command navigates to a certain directory and runs the dependency injection generation command. This comes quite handy while developing large applications. You can find a Taskfile.yaml at the root of the project.

Creating a new Project

To create a new project from this starter, visit https://github.com/shanmukhsista/go-graphql-starter and click on the Use this Template button on Github UI.

You can create a new repository and start working with your own copy. Assuming that you have all the development dependencies setup, and database migrations applied, you can simply run this project using the commands given below.

Database Migrations

Before we start working with our application, we must setup our database. This project uses Postgres as our application database. To start a postgres database locally, use the docker-compose command below.

docker-compose -f docker-compose.yaml up -d

This starts up a postgres instance for us on port 5435 . With our database now up and running, it’s time to create our initial schemas and apply migrations for the project.

This project using golang-migrate library to work with migrations and schema updates. All scripts for our database can be found under the migrations directory. To apply all the latest scripts to our database, run the command below

task migratedb

By default, this uses the same PostgreSQL connection that we work with locally.

One can always chose to manually apply all migrations in production.

If everything works as expected, you should see a message similar to the one below

task: [migratedb] migrate -source file://migrations -database $DATABASE_URL up
1/u initial_notes_schema (63.19343ms)

Every time you run this command, it should apply the latest migrations and run your application. To add new table schemas / remove anything, just use the existing migration sql scripts or add a new migration using commands from golang-migrate cli.

Running our GraphQL Server

We’ve setup most of our dependencies at this point. Try running the following command to startup our graphql server.

task run-graphql-server

This command builds the entire project, and runs the graphql server on http://localhost:7777 if everything was perfect.

Try navigating to this address and you should be able to see a Graphql UI.

You can now execute queries and mutations within our application. Try querying for all notes.

query{  
  notes{  
    id  
    title  
    content  
  }  
}

{  
  "data": {  
    "notes": []  
  }  
}

Notice that we don’t have any notes in our system. Let’s create one using the mutation below.

mutation{  
  createNewNote(input:{  
    title:"First Note on Starter" ,  
    content:"Content for note. "  
  }){  
    id  
  }  
}

You should see a new note created with an id assigned.

{  
  "data": {  
    "createNewNote": {  
      "id": "V9vGDSqTZ3BAquLrqKhvkj"  
    }  
  }  
}

Now let’s query all notes again, and see.

{  
  "data": {  
    "notes": [  
      {  
        "id": "V9vGDSqTZ3BAquLrqKhvkj",  
        "title": "First Note on Starter",  
        "content": "Content for note. "  
      }  
    ]  
  }  
}

That was our Hello World for our Graphql Server. This application tries to follow all practices that a production application would. It’s extremely fast to bootstrap a fully customizable server and deploy it. On top of it , it’s extremely lightweight, resource optimized and has very small containerized builds. Before we jump into deployment, let’s take a look at basic project organization. If you’re already aware of golagn project organization, please feel free to skip this one.

Project Organization

The source code of our project is split across three main directories.

  • cmd
  • graphql-server
  • internal
  • common
  • services
  • pkg

cmd Directory

The command directory contains any executables that are required for starting the application. In this case, our executable is graphql-server — which contains the logic to start our Graphql Server for us. Any new executables or applications can reside within the cmd directory.

internal directory

Internal directory can be used to host packages which cannot be imported by any other third party library or application within your organization. On the contrary, pkg directory allows you to create library packages for all the code you write, and can be imported or used in any other application or third party library.

pkg directory

The pkg directory is where majority of the code would reside in. We can include any shareable code / services / libraries we plan to write here. You’ll notice that it has the notes service already built into it. Our main graphql server just uses this pkg directory’s service to hook it up using graphql . In future, if we decide to use REST apis using the same models, we would just wire our rest api handler with the existing notes service.

Try keeping pkg directory as clean as possible. There’s also a common directory where we can host our framework dependencies — database, logging, monitoring etc. As of now this starter has the database aspects built into it. I’ll try to keep adding more in future releases.

Adding / Updating Mutations and Queries

Our server exposes graphql APIs and a Playground. All of this is powered by GqlGen Framework. As developers, we can create new queries, models, mutations by updating our graphql schema. And we can use gqlgen cli.

Let’s say we want to add a new mutation deleteNote that deletes a note with a given Id. We can do this by first updating the schema.graphqls located at path — cmd/graphql-server/graph/schema.graphqls

type Mutation {  
  createNewNote(input: NewNoteInput!): Note!  
  # Deletes a note with id and returns the deleted note id.   
  deleteNote(id : ID!) : ID!  
}

We can even define custom types in our schema. Everything starts with writing the schema first. Once our schema is done, we will then execute the following command to generate our models and API definitions.

task gqlgen  
  
task: [gqlgen] echo 'Generating GraphQL Schema'  
Generating GraphQL Schema  
task: [gqlgen] go run github.com/99designs/gqlgen generate  
task: [gqlgen] echo 'Generated Schema. Please check ./cmd/go-graphql-starter/graph folder for any errors.'  
Generated Schema. Please check ./cmd/go-graphql-starter/graph folder for any errors.

This generates our models, types, and mutations. it also gives you an API definition for the new resolver method. Checkout cmd/graphql-server/graph/schema.resolvers.go

You’ll notice that a new method was added to the resolvers file. This is where we implement the logic. You’ll also notice that all inputs and output definitions are generated golang types. Everything is already handled for us by the framework.

// DeleteNote is the resolver for the deleteNote field.  
func (r *mutationResolver) DeleteNote(ctx context.Context, id string) (string, error) {  
   panic(fmt.Errorf("not implemented: DeleteNote - deleteNote"))  
}

Just implementing this method will give us the ability to call our graphql APIs. Try implementing any queries / mutations and see how gqlgen generates our APIs. I am a huge fan of the schema first approach taken by gqlgen.

Build and Deploy Containers

Now that we’ve made changes, it’s time to build and deploy our application. We can containerize and deploy to any cloud provider or service. Within the cmd/graphql-server/_docker folder, you’ll see a Dockerfile. This Dockerfile is what is used to build the entire server. Golang apps can be containerized using very lean containers. All we do is build an executable that can be run in a linux container. The final Dockerfile to build containers looks like this.

############################  
# STEP 1 build executable binary  
############################  
ARG ENV  
FROM golang:alpine AS builder  
# Install git.  
# Git is required for fetching the dependencies.  
RUN apk update && apk add  git && apk add  build-base upx  
  
WORKDIR /src/go-graphql-starter/graphql-server/  
COPY . .  
# Fetch dependencies.  
# Using go get.  
# RUN cd ./cmd/graphql-server && go build  -mod vendor  -ldflags '-w -s'  -o /go/bin/graphql-server  
# Build the binary.  
RUN cd ./cmd/graphql-server && go build  -o /go/bin/graphql-server  
RUN upx /go/bin/graphql-server  
  
############################  
# STEP 2 build a small image  
############################  
FROM alpine  
ARG ENV  
# Copy our static executable.  
COPY --from=builder /go/bin/graphql-server /go/bin/graphql-server  
COPY --from=builder /src/go-graphql-starter/graphql-server/cmd/graphql-server/config/$ENV/config.yaml /go/bin/  
# Run the hello binary.  
ENTRYPOINT ["/go/bin/graphql-server",  "-configpath=/go/bin/config.yaml"]

One thing I also like to do is to use upx to try and reduce the build size of the executable. Please note that this may increase the time it takes to build our containers by a small amount.

Once we have this container, we can push it to any docker registry and just run our container using docker run command. Our server starts up and reads the specific environment config we used to build it.

Next Steps!

I tried to give a brief intro point for this starter in this article. This project acts as a starter for a couple of my production projects. By leveraging this model, it gives me the flexibility to write large scale apps, as well as the simplicity to deploy a cloud native Graphql endpoints with ease. It is very resource efficient and keeps a low profile when it comes to usage of CPU and memory.

The starter is located on Github

https://github.com/shanmukhsista/go-graphql-starter

GqlGen framework helps a lot in our case and takes away a lot of groundwork for setting up a server. In upcoming releases, I'll try to add error handling and integration tests with our GraphQL Apis. Writing integration tests will help you cover your code and apis with ease and be confident about the entire logic without having to worry about unexpected edge cases. it should befairly straightforward to write independent integration tests that cover your code functionally without having to rely on a frontend application.