Jakub Jarosz

Security, Systems & Network Automation

Organizing Your Go Code: Tips for Beginners

2024-12-11 Go

How should I organize my Go code? What directories to create? How many nesed levels? Where to put what files? How about tests and test data? Shall I follow this and that template? Could we automate project structure generation?

Developers new to Go face many dilemmas! You know the drill if you spend years developing Rails or Django web apps: You come to Go and feel lost. Worse, you see people creating trees of directories, populating them with empty files, and then trying to fit growing Go code into “boxes.”

At some point, sooner rather than later, the moment of enlightenment will come, and you will ask: Is it a better, simpler way?

Let’s start building a new project. What would be our first step? What do we need to start? You create a Go API client for the Shodan service. Shodan is the world’s first search engine focused on Internet-connected devices. Do you want to know the IP addresses of unsecured networking cameras in your town? Are you curious if the server you configured for your neighbour exposes his private data? With Shodan, looking for security holes is a fun and exciting journey!

First, you need to consider the project name. This will also be the name of the Go module on GitHub.

For the sake of our exercise, let’s name it godan—the Go SDK/API for Shodan.

godan

Once we have the name, we create a directory with the same name.

mkdir godan

We completed the first step. Let’s enter our new home and start arranging the internals.

cd godan

Creating Go module (package)

Our next move is to initialize the godan directory and make it the home for the Go module. To do this, we use the go mod init command. Since we will host the module on GitHub, we decided to name it github.com/qba73/godan.

go mod init github.com/qba73/godan

After this step, the godan dir has one file inside: go.mod. What does the file look like?

go.mod

module github.com/qba73/godan

go 1.23.4

The first line is the name of our module. The following line tells us what version of Go we will use. Note that the version of Go matches the version installed on your machine!

So far, so good. What would be the next step? No, we don’t need ANY directory at the moment. We don’t need to worry about what code is internal and which is not—forget it! What we do need is to create two files: one for tests and one for our code.

Let’s create them.

touch godan_test.go
touch godan.go

Have you noticed the pattern? Both files have names matching the module name: godan.

Next we edit both files and add package names.

godan.go

package godan

godan_test.go

package godan_test

Hold on! Why godan_test? Why not godan? The name indicates that tests in the godan_test.go will import the godan package. This means we would focus on testing exported functions, functions that form the package API, and describing package behaviour!

Ok, let’s check what we have so far. The tree command displays our module structure.

tree
.
├── go.mod
├── godan.go
└── godan_test.go

1 directory, 3 files

One directory—the Go module—and three files. Do we need anything else? Nope. Nada! That’s all we need to start designing behaviour with tests. We can concentrate on adding functionality! Test after test, function after function, we can grow the Go module.

KISS and YAGNI principles applied!

Adding CLI

What can we do when we decide to add a CLI to our growing library? Aha! Now is the moment to create the first directory. Let’s name it cmd.

mkdir cmd

We create the main.go file in the cmd directory.

tree
.
├── cmd
│  └── main.go
├── go.mod
├── godan.go
└── godan_test.go

2 directories, 4 files

What is the main.go? It’s a place for the main entry to our Go application! The main.go imports the package godan. Ideally, the only responsibility of the main function is to call some exported function from the godan package. For example: godan.RunCLI or godan.Main or something similar.

cmd/main.go

package main

import "github.com/qba73/godan"

func main() {
    // Call your app entry point here.
}

That’s all we really need to start developing our godan package. If we need a CLI (command-line interface), the cmd dir and the main function in the main.go will do the job!

All design decisions start and end with the package.

– B. Kennedy

We don’t need fancy generators. We need an organic design approach.

That’s the key difference in thinking the Go way:

  • keep it simple
  • focus on readability
  • make things easy to understand

Postpone design decisions until the right moment—the moment you see what functionality could be a good candidate for separate packages, files, etc. It may be a long time from now, or it may never happen.

Building a Go application is like growing a garden. You start with a small flower or two. Next week, you add a few more. You arrange them on shelves as they grow and blossom. You do not start by building a bunch of greenhouses, shelves, watering infrastructure, and ventilation systems. You can add them later, only if you need them!