How to Organize Your Go Code
There is no standardized way to organize your code in Go1. If you’re new to Go and coming from another language or are used to working in the context of a framework, the lack of code organization can have you searching for some guidance and reassurance. I’ll show you how I manage it first, then provide an alternative for anyone seeking more structure.
Keep the main thing, the main thing
All Go applications start with a main function in a main package. In fact, almost every time I start a new Go project, I’ll start with a main.go
file in the root of my project that contains the main
package and has a func main() {}
.
Oftentimes, as that main function becomes larger and unwieldy, I’ll separate bits of code into functions, structs, and methods where it makes sense. But I’ll usually start by doing this all in the same main.go
file.
After a little bit of time, I start to see patterns and will move some of those functions, structs, and methods into their own files. The caveat is that these are not new packages yet. They’re new files under the same directory as the main.go
file, and they all use the same package main
specification.
And then, only after some more time, will I slowly create new folders and move code into their own packages.
This is an iterative approach where I allow my organization to organically form from working on the project. This allows me to avoid the issue of premature optimization (optimization, in this case, being code organization). It’s an approach that takes patience and trust, but one that I quite enjoy.
But it’s not an approach for everybody and sometimes you need just a little more structure and guidance.
A structured alternative
Even when following a more structured approach, I still urge against deciding on what packages you’re going to use upfront. However, you’ll see a couple of common patterns in Go code organization that might work for you and you can grow your project with them.
The first suggestion is not to put your main package in the root directory, but rather in a cmd/{project_name}
folder. In this case, {project_name}
would represent what your binary is. Common ones that I’ve used and encountered include cli
, api
, or www
.
Your resulting file structure might look something like this:
.
├── cmd
│ ├── app
│ │ └── main.go
│ └── cli
│ └── main.go
└── go.mod
The nice part about this approach is that you can have multiple separate main packages (as Go will separate by directory). This is helpful, for instance, if you have an API server in one folder, but also want to include a CLI for running some developer tooling against it with the same project.
When you set up your project like this, you’ll most want to share code between the different executables. That shared code makes an excellent starting point for another package.
Most times, I actually like to keep my main packages lean and just have a main function that calls a Run
function. If taking this approach, you have a couple of options. If you’re starting with a single executable, you could throw that Run function and related code in an application
(or similar) package. If you have multiple executables, then maybe you have a cli
package and www
package (as an example) that contain Run
functions and a third package (maybe application
or something similar) that contains shared logic.
If you take that approach, try to put as much logic in the single shared non-main package as possible. Then take an iterative approach and only move that code into additional packages when necessary.
But where should those packages live?
A common pattern is for any code that is intended to be shared outside of the module the application lives in to be placed in an internal
folder. This allows you to make changes to any API, without affecting any external module that might’ve thought about using that code (as you won’t be able to import packages in an internal
module outside of the parent and sibling folders).
There is some debate about whether internal
folders are needed in the context of an application or if it’s a good pattern altogether2. They are recommended in the Go docs3, but your mileage and preferences may vary. If you choose not to use an internal
folder or want to make your APIs available to be consumed in other modules, one alternative (or in addition to internal
folders) is to use a pkg
folder (or something similarly named). Perhaps more common, is to just include those packages as their own folders in the root. You have some flexibility here.
Following these patterns, your application could look something like this:
.
├── cmd
│ ├── cli
│ │ └── main.go
│ └── www
│ └── main.go
├── go.mod
└── internal
├── application
│ └── sharedapp.go
├── cli
│ └── cli.go
└── web
└── web.go
To see an example of this structured approach in action, check out terminaldotshop’s go application.
Final Thoughts
Both approaches offer the ability to allow the packages to grow and form with your application. Ultimately, I think that’s the right approach, as there is no one-size-fits-all (and the reason why Go doesn’t have a standardized organizational structure).
However, if you’re at the point and wondering how to split out your code into additional packages, let me offer you the following resources:
- Kat Zien’s talk from GopherCon 2018, “How Do You Structure Your Go Apps” is an excellent talk that shows examples of the same app with a flat structure, contextual separation, domain-driven design, and hexagonal structure
- Go’s own documentation on ideas of how to manage and organize your modules
Footnotes
-
You may have come across a golang-standards/project-layout repo that describes how to organize Go code. However, it is not an official standard. In fact, not only is it not endorsed by the Go team, it is even considered an antipattern: https://github.com/golang-standards/project-layout/issues/117. ↩
-
I personally like the internal folder and find it especially when creating shareable modules (as opposed to applications). However you’ll find some articles debating the functionality on both sides of the spectrum such as Why I use the internal folder for a Go-project, Why top level internal is uncessary in Golang, and Don’t Put All Your Code In Internal. ↩
-
https://go.dev/doc/modules/layout#package-or-command-with-supporting-packages ↩