Not sure how to structure your Go web application?

My new book guides you through the start-to-finish build of a real world web application in Go — covering topics like how to structure your code, manage dependencies, create dynamic database-driven pages, and how to authenticate and authorize users securely.

Take a look!

Quick tip: A time-saving Makefile for your Go projects

Published on:

Whenever I start a new Go project, one of the first things I do is create a Makefile in the root of my project directory.

This Makefile serves two purposes. The first is to automate common admin tasks (like running tests, checking for vulnerabilities, pushing changes to a remote repository, and deploying to production), and the second is to provide short aliases for Go commands that are long or difficult to remember. I find that it's a simple way to save time and mental overhead, as well as helping me to catch potential problems early and keep my codebases in good shape.

While the exact contents of the Makefile changes from project to project, in this post, I want to share the boilerplate that I'm currently using as a starting point. It's generic enough that you should be able to use it as-is for almost all projects.

File: Makefile
# Change these variables as necessary.
MAIN_PACKAGE_PATH := ./cmd/example
BINARY_NAME := example

# ==================================================================================== #
# ==================================================================================== #

## help: print this help message
.PHONY: help
    @echo 'Usage:'
    @sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' |  sed -e 's/^/ /'

.PHONY: confirm
    @echo -n 'Are you sure? [y/N] ' && read ans && [ $${ans:-N} = y ]

.PHONY: no-dirty
    git diff --exit-code

# ==================================================================================== #
# ==================================================================================== #

## tidy: format code and tidy modfile
.PHONY: tidy
    go fmt ./...
    go mod tidy -v

## audit: run quality control checks
.PHONY: audit
    go mod verify
    go vet ./...
    go run -checks=all,-ST1000,-U1000 ./...
    go run ./...
    go test -race -buildvcs -vet=off ./...

# ==================================================================================== #
# ==================================================================================== #

## test: run all tests
.PHONY: test
    go test -v -race -buildvcs ./...

## test/cover: run all tests and display coverage
.PHONY: test/cover
    go test -v -race -buildvcs -coverprofile=/tmp/coverage.out ./...
    go tool cover -html=/tmp/coverage.out

## build: build the application
.PHONY: build
    # Include additional build steps, like TypeScript, SCSS or Tailwind compilation here...
    go build -o=/tmp/bin/${BINARY_NAME} ${MAIN_PACKAGE_PATH}

## run: run the  application
.PHONY: run
run: build

## run/live: run the application with reloading on file changes
.PHONY: run/live
    go run \
        --build.cmd "make build" --build.bin "/tmp/bin/${BINARY_NAME}" --build.delay "100" \
        --build.exclude_dir "" \
        --build.include_ext "go, tpl, tmpl, html, css, scss, js, ts, sql, jpeg, jpg, gif, png, bmp, svg, webp, ico" \
        --misc.clean_on_exit "true"

# ==================================================================================== #
# ==================================================================================== #

## push: push changes to the remote Git repository
.PHONY: push
push: tidy audit no-dirty
    git push

## production/deploy: deploy the application to production
.PHONY: production/deploy
production/deploy: confirm tidy audit no-dirty
    GOOS=linux GOARCH=amd64 go build -ldflags='-s' -o=/tmp/bin/linux_amd64/${BINARY_NAME} ${MAIN_PACKAGE_PATH}
    upx -5 /tmp/bin/linux_amd64/${BINARY_NAME}
    # Include additional deployment steps here...

The Makefile is organized into several sections, each with its own set of targets:


  • help: Prints a help message for the Makefile, including a list of available targets and their descriptions.
  • confirm: Prompts the user to confirm an action with a "y/N" prompt.
  • no-dirty: Checks that there there are no uncommitted changes in the tracked files in the current git repository.


  • tidy: Formats the code using go fmt and updates the dependencies using go mod tidy.
  • audit: Runs quality control checks on the codebase, including verifying the dependencies with go mod verify, running running static analysis with go vet and staticcheck, checking for vulnerabilities using govulncheck, and running all tests. Note that it uses go run to execute the latest versions of the remote staticcheck and govulncheck packages, meaning that you don't need to install these tools first. I've written more about this pattern in a previous post.


  • test: Runs all tests. Note that we enable the race detector and embed build info in the test binary.
  • test/cover: Runs all tests and outputs a coverage report in HTML format.
  • build: Builds the package at MAIN_PACKAGE_PATH and outputs a binary at /tmp/bin/{BINARY_NAME}.
  • run: Calls the build target and then runs the binary. Note that my main reason for not using go run here is that go run doesn't embed build info in the binary.
  • run/live: Use the air tool to run the application with live reloading enabled. When changes are made to any files with the specified extensions, the application is rebuilt and the binary is re-run.
  • Depending on the project I often add more to this section, such as targets for connecting to a development database instance and managing SQL migrations. Here's an example.


  • push: Push changes to the remote Git repository. This automatically runs the tidy and audit quality-control checks first, and no-dirty to check that there are no uncommitted changes to tracked files.
  • production/deploy: Builds the a binary for linux/amd64 architecture, compress it using upx, and then run any deployment steps. Note that this target asks for y/N confirmation before anything is executed, and also runs the tidy, audit and no-dirty checks first.
  • Depending on the project I often add more to this section too. For example, a staging/deploy rule for deploying to a staging server, production/connect for SSHing into a production server, production/log for viewing production logs, production/db for connecting to the production database, and production/upgrade for updating and upgrading software on a production server.


Each of these targets can be executed by running make followed by the target name in your terminal. For example:

$ make tidy
go fmt ./...
go mod tidy -v

If you run make help (or the naked make command without specifiying a target) then you'll get a description of the available targets.

$ make help
  help                print this help message
  tidy                format code and tidy modfile
  audit               run quality control checks
  test                run all tests
  test/cover          run all tests and display coverage
  build               build the application
  run                 run the  application
  run/live            run the application with reloading on file changes
  push                push changes to the remote Git repository
  production/deploy   deploy the application to production