How to structure Go repository
The condensed introduction with real-life examples so you could organize your Go repository the easy way.
While browsing different groups and communities around the Internet I often spot a question about structuring the repository of application written in Go. I remember how confusing it was back those days when I’ve been switching from PHP and JS development, writing Go code. Mostly because working with PHP and JS I used to use boilerplates, templates or framework init scripts to set things up. And one of the aspects that framework provides is folder and file structure. Despite it requires some time to learn and remember where to put files of different sort - it solves the issue of organizing repositories, establishing standards.
When I started to work with Go I learned pretty fast that community loves the clean approach: we reduce the amount of things that are not used to a maximum degree. Even the way dependencies are resolved work that way: you may grab just a small portion of the library that is copied to your project, simply because everything else is not used. And this feels so natural and good after a short while.
But the problem is still there: there is an empty project folder you open and you are staring, confused about where to start. No worries! We all have been there, and this is a great moment to address your doubts and find answers to your questions. It is definitely better to do now, with an empty project. Comparing to the half-a-year long project where you decided to refactor the structure. It is not impossible, but this might be painful, especially taking in to account it does not provide clear business value directly.
Root: dependencies and VCS
So lets start with very basics: you’d like to use Go’s dependency manager: go mod and you’re most probably using git. Therefore obviously you execute git init and there is .git in your root. The next step would be to execute go mod init, that create go.mod and go.sum file in your root.
Great, we have a home for one of the most important files. It is perfectly fine to keep it this way.
Let’s write some code
Now you’d like to finally write some code that does amazing things. You may already know that there is always “package main” that has “func main()” being the entry point to your software. The start of the entire execution. And you are absolutely right. But where to put it?
cmd
That’s simple, just create a cmd folder. And here the flexibility of Go starts: sometimes you may find main.go dangling in the root of the project. Sometimes it is directly under cmd: cmd/main.go, but the most advanced approach is to keep it possible to scale up, in case you need (or may need in future) to add more entry-points, that works with the same dependencies. In this case you should create subfolders within your cmd folder, each folder should be named to explicitly describe the application. Each folder contains… yes, main.go with package main and func main().
For example I used to create server and cli subfolders, and later on I am building separate binaries for server and CLI. Others may prefer to have a cli command, (i.e. start or run) as a part of CLI binary that starts the server. This way you have a single binary, that can contain all your CLI application and starts a server. I am not a huge fan of this approach, but it works just well.
internal & pkg
Concept of internal folder arrived by the time I started my journey with Go. And the idea is pretty straightforward and simple: project specific code should live here. Let me rephrase it: the code, that is under internal folder is not intended to be re-used anywhere. You may think of at as of private code, but I prefer to stick to the definition of the single project specific code. If you’re building a web application or a software that solves specific business needs than there are high chances you will mostly keep your code here, in internal folder.
However, keep in mind it is not just a convention or code smell to import from internal - dependency manager won’t allow you to import anything from the internal directory.
This is exactly the reason pkg folder is also a thing. It should contain everything, that might be re-used by other projects. There are many use-cases: it might be some structs, to keep consistency for API data between the services. It might be configurations. It might be team-wide packages, which are not ready yet to become a stand-alone packages.
Keep anything, that you’d like to be re-used inside the pkg directory of the project. Everything else, project specific goes into the internal. Simple, right?
vendor
Depending on your previous experience, you may be familiar with vendor, node_modules or other folder naming, that contains all the 3rd party code. Go project can be successfully built and developed without the vendor folder. All because of GOPATH, the environment variable that points to a dedicated, system-wide folder that contains all the 3rd party code. But at some point go mod vendor command was introduced and it creates the vendor folder inside the project.
I personally enjoy this possibility in order to experiment with dependencies: sometimes there are bugs, or these might be the team-wide dependency, that needs some changes and it is convenient to make small changes on the go before I am moving it to the repository dedicated for that library.
Do you remember, go mod does not include anything, that is not used in the specific project, even it is a part of a dependency packet you are installing? This makes vendor folder don’t go up in size too fast, comparing to the dependency directories used in other ecosystems. This makes it not so bad idea to actually commit the dependencies (I was never doing that, relying on go.sum) and avoid sometimes inconvenient process of resolving the private packages.
If you are curious and want to learn more about the long path of dependency management in Go, which was not feeling so straightforward after years with composer, npm and others than you might visit golang/dep repository, that was an early attempt for implementing package manager and is deprecated for many years. This still might be a good source of explanation why certain things work the way they do today.
There is a lot of flexibility.
Keep it organized, though!
It is a fact, that standards like code formatting, organization, design pattern and many others are not a must for machine. All machines are able to execute any code, that could be parsed and compiled according to the rules. It is us, the people, who need all the code beauty, bells and whistles in order to keep it easy for our brain to process all the information. It is always great to onboard a team, who is following practices. The wider is adoption of the specific practice, the higher is the chance of fast and smooth new team member onboarding.
Because it is easy, when everything is named and organized the way we know. It brings also lots of satisfaction and almost feel-like-at-home feeling, when you realize you can easily navigate through the project you’ve been working for a week. And this is the main (and actually the only valid) reason, why we should work towards standards, improve them and follow up.
Therefore I would like to refer to the standards I used to use for many years and overall it works great. The best thing is - it is already here, and there are good chances your future colleagues are already following it, or at least it was taking as a base for the more specific standard.
https://github.com/golang-standards/project-layout
I could personally mention just a couple more directories, that are present in any project I’m building:
“/deployments” - if you’re using docker, most probably you are using docker compose at least for local development. So that’s the perfect place for docker compose files.
“/scripts” - This one is great for some scripts and boostrapping. For example I keep a script for configuring database the way I need.
“/build” - Here I keep additional docker files. Like the one for building custom database image, which utilizes the scripts from the “/scripts” described above. But please remember one advice: some technologies may have own limitations. For example if you’re using dockerfile to build an image for you repository, I’d bet you want to keep it in the root, otherwise you will struggle to pass the entire context to the build.
In simple words: please do not treat it the only way of organizing your repositories and code. Most of applications written in Go are written from scratch, and there is no auto-wiring and strict rules of placing files in the right place. But keeping it organized and standardized you are doing great for everyone, who will work with the project in the future. Even your future self.
I personally would live this article exist years ago, when I’ve been looking for an explanation how to organize code, before I found project-layout and learned other details, like pkg vs internal differences. I think I helped you to save a couple of hours googling and figuring all that stuff on your own.
Keep GO-ing!