My Favourite Go Project Structure
In this story I want to present my project setup for applications written in Go. It’s suits well for larger applications that serve cmd commands, UIs, APIs, or all of them. I spent a lot of time thinking on where to locate things in my Go projects, that’s why I want to share the result with you to provide a reasonable foundation for your next Go project. Despite recommending specific libraries for particular tasks, the actual folder structure is not bound to these libraries, it can be used with any existing alternatives. So let’s not waste any time and start with the actual folder structure:
cmd
This folder contains the cmd commands generated and handled by Cobra. Cobra has a very good documentation and a nice cmd tooling to create (sub)commands. And it allows easy configuration for nested commands, where each of the (sub)commands can be configured independently. So definitely a great choice!
internal
This folder is for internal logic that should not be imported from any other package. It’s a convention in go that prohibits other packages to import any functions or structs located in the internal folder, only the current module can import things from there.
An import of a path containing the element “internal” is disallowed if the importing code is outside the tree rooted at the parent of the “internal” directory.
I don’t often put code in the internal folder, because in the past I sometimes ran in the issue that I wanted to use internal code in my own packages but due to this rule, I could not do so. So I would propose to use this folder only for code that is wrapped in other files outside of the internal folder and which you don’t want anyone to import directly.
pkg
The pkg
folder is one of the most important ones, it contains core parts of the application logic which is intended to be imported by other packages. In my projects, I create subfolders for any specific group of logic I want to make public available. Examples I wrote are pkg/ssh
for an abstraction that performs actions over ssh, or pkg/cmd
to wrap the usage of executing cmd commands from my application.
Side note: There is an interesting blog post about pkg vs internal folder.
models
If you’re working with any kind of database and a respective ORM to connect to it (e.g. gorm), a dedicated folder for these models makes sense. You can import the models from any other location. Since go allows configuring different kind of usages for fields via tag reflection, e.g. json or gorm specific configuration, there is mostly no need to write multiple structs for a model to be handled by different packages.
Side note: You may also move the models in subfolders of the pkg
directory if they rely to specific logic, e.g. an SshConfig
model can also be located in pkg/ssh
instead of models
. It depends on your own favour.
api
If your application provides any API to connect to (e.g. gRPC, HTTP, …), the api
folder is the correct location where to put any schemata or specification. You may also add the routes of the API endpoints here. It’s easy for other developers to get an overview of what endpoints your application provides.
ui
If you’re aiming to create a GUI application (instead of or even in combination with a cli application), moving things that are UI-related in a specific folder is a good way to separate it from the rest. I personally prefer wails for GUI application development in Go. It’s a lightweight library using native WebView instead of Electron, provides a list of available starters (I prefer React with Typescript) and it’s easy to use.
lib
At the first view, you may think that lib
sounds like a conflict with pkg
, both are folders to store code that should be reused in the current or any other module importing it. But I think the lib folder can serve a different purpose: Bundling the core logic of the application to make it usable from the cli and the UI part. Let’s consider a build
command, that should be exported in the UI and in cmd. To achieve this, we wrap all available cmd args for the build command into a struct called BuildArgs
in lib/build.go
. Additionally, a function called func Build(args *BuildArgs)
is exported from lib/build.go
. The cmd part can now simply wrap the cmd args into the BuildArgs
struct and call the Build
function. The UI part can collect the required input from the user and proceed the same way. So in my projects, the lib
folder contains core function of the application that is planned to be reused with different kind of user input.
scripts
In case your project requires some external scripts (e.g. bash, lua, …) it’s a good idea to put these scripts into a dedicated folder. Each developer can easily recognise that this folder probably does not contain any go code but some other scripting languages.
tools
If your project needs some external tools to do particular tasks (e.g. code generation) that are probably not intended to be installed globally (in contrast to e.g. Cobra).
utils
This folder is probably the most dangerous of the ones presented here. In utils
you can basically put everything that is not related to any particular part of logic but which is more a general helper that can be used in different places. However, you should be careful before adding any code to the utils
folder: In general, even the helper functions should be located either in the internal
or pkg
directory to which they logically fit. And it’s easy to create a trash bin of functions and structs in the util
folder to avoid thinking about how they can be integrated in the actual project structure. So please be careful putting code in this folder.
Summary
This story covers my structure for larger go applications. It’s a mix of recommendations from external sources (see below) and my personal experiences and choices. Please feel free to comment any feedback, I’m glad to improve the setup!
Inspired by:
— https://github.com/golang-standards/project-layout
— https://dev.to/jinxankit/go-project-structure-and-guidelines-4ccm
— https://tutorialedge.net/golang/go-project-structure-best-practices/