Go modules were introduced a long time ago in Go. However I still found a hard time getting into writing using Go modules, not knowing how to structure my projects and then call those modules I wrote.

Turns out its dead simple, but maybe this will be a useful learning source or reference.

Generic Structure

From what I’ve seen, Go projects often use /pkg to write packages written for the project - packages in here should normally be usable outside of the project, however, if they are not then /internal is what I use instead.

The source for the binaries normally is put in /cmd.

Tests might be written next to the code which they are testing, however sometimes it may make more sense to use a /test.

This Github repository contains many more examples of what to usually put where.

Note, it is entirely up to you if you want to follow this or not. For many small projects it is enough to just put everything in the root directory. Go will often require you to make different directories however, so having structure may be useful for many projects, as well as later making it clearer what you intended for different parts to mean.

Quick Example Structure

To create the module in the first place, in the project root call go mod init <site>/<name>.

Here is a small example structure

.
├── cmd
│   └── main.go
├── go.mod
├── go.sum
└── pkg
    ├── bar
    │   └── bar.go
    └── foo
        └── foo.go

Here I just used go mod init example.com/example, often you may use a Github repository instead.

go.mod will show our module name and our project dependencies.

module example.com/example

go 1.16

Then the main part of the project is /cmd/main.go, which calls the packages written in /pkg

package main

import (
	"example.com/example/pkg/bar"
	. "example.com/example/pkg/foo"
)

func main() {
	Foo()
	bar.Bar()
}

Here you can see how the local packages are imported, then how their contents is called.

Here we also see a way to import the package foo, prefixing the line in imports() with a . so that we do not have to call foo.; instead, we can immediately call the functions in foo.

/pkg/foo/foo.go looks like this:

package foo

import "fmt"

// Prints "foo!"
func Foo() {
	fmt.Println("foo!")
}

As Foo() is supposed to be a public function, we must start it with a capital letter. In modules, Go automatically marks functions which start with a capital letter as public and those which do not as private. This is a bit of a strange feature, but it is what it is and it works.

We can also call other packages from inside our own packages, we can see in /pkg/bar/bar.go

package bar

import (
	"fmt"

	"example.com/example/pkg/foo"
)

// Prints "bar!" then calls foo.Foo()
func Bar() {
	fmt.Println("bar!")
	foo.Foo()
}

Be aware that cyclical dependencies are not allowed. For example, if bar imports foo and foo imports bar then our project will not compile.

Managing Dependencies

If you do use modules as dependencies, then you will also find go mod tidy to be a useful command.This will add and remove modules from the go.mod file based off of what modules you are using in your source code. It will also generate a go.sum which contains the modules you have imported and their cryptographic hashes/checksums.

Another useful command could be go mod vendor if you want the source of the modules you import to be accessible from inside your own project.

If you require your dependencies at a certain version, you can use @ in your go get command, i.e. go get github.com/somebody/module@<commit-or-tag>. This will also update the go.mod file. As you may have guessed, you can manage the version of your own project using git tags as well.