Project setup and skeleton structure
Let’s kick things off by creating a greenlight directory to act as the top-level ‘home’ for this project. I’m going to create my project directory at $HOME/Projects/greenlight, but feel free to choose a different location if you wish.
$ mkdir -p $HOME/Projects/greenlight
Then change into this directory and use the go mod init command to enable modules for the project.
When running this command, you’ll need to specify a module path. This is essentially a unique identifier for your project. In this book I’ll use greenlight.alexedwards.net as my module path, but if you’re following along you should ideally swap this for something that is unique to you instead.
$ cd $HOME/Projects/greenlight $ go mod init greenlight.alexedwards.net go: creating new go.mod: module greenlight.alexedwards.net
At this point you’ll see that a go.mod file has been created in the root of your project directory. If you open it up, it should look similar to this:
module greenlight.alexedwards.net go 1.25.0
We discussed modules in detail in the first Let’s Go book, but as a quick refresher let’s recap the main points here.
- When there is a valid
go.modfile in the root of your project directory, your project is a module. - When you’re working inside your project directory and download a dependency with
go get, then the exact version of the dependency will be recorded in thego.modfile. Because the exact version is known, this makes it much easier to ensure reproducible builds across different machines and environments. - When you run or build the code in your project, Go will use the exact dependencies listed in the
go.modfile. If the necessary dependencies aren’t already on your local machine, then Go will automatically download them for you — along with any recursive dependencies too. - The
go.modfile also defines the module path (which isgreenlight.alexedwards.netin my case). This is essentially the identifier that will be used as the root import path for the packages in your project. - It’s good practice to make the module path unique to you and your project. A common convention in the Go community is to base it on a URL that you own.
Generating the skeleton directory structure
Alright, now that our project directory has been created and we have a go.mod file, you can go ahead and run the following commands to generate a high-level skeleton structure for the project:
$ mkdir -p bin cmd/api internal migrations remote $ touch Makefile $ touch cmd/api/main.go
At this point your project directory should look exactly like this:
. ├── bin ├── cmd │ └── api │ └── main.go ├── internal ├── migrations ├── remote ├── go.mod └── Makefile
Let’s take a moment to talk through these files and folders and explain the purpose they’ll serve in our finished project.
- The
bindirectory will contain our compiled application binaries, ready for deployment to a production server. - The
cmd/apidirectory will contain the application-specific code for our Greenlight API application. This will include the code for running the server, reading and writing HTTP requests, and managing authentication. - The
internaldirectory will contain various ancillary packages used by our API. It will contain the code for interacting with our database, doing data validation, sending emails and so on. Basically, any code that isn’t application-specific and can potentially be reused will live in here. Our Go code undercmd/apiwill import the packages in theinternaldirectory (but never the other way around). - The
migrationsdirectory will contain the SQL migration files for our database. - The
remotedirectory will contain the configuration files and setup scripts for our production server. - The
go.modfile will declare our project dependencies, versions and module path. - The
Makefilewill contain recipes for automating common administrative tasks — like auditing our Go code, building binaries, and executing database migrations.
It’s important to point out that the directory name internal carries a special meaning and behavior in Go: any packages which live under this directory can only be imported by code inside the parent of the internal directory. In our case, this means that any packages which live in internal can only be imported by code inside our greenlight project directory.
Or, looking at it the other way, this means that any packages under internal cannot be imported by code outside of our project.
This is useful because it prevents other codebases from importing and relying on the (potentially unversioned and unsupported) packages in our internal directory — even if the project code is publicly available somewhere like GitHub.
Hello world!
Before we continue, let’s quickly check that everything is set up correctly. Open the cmd/api/main.go file in your text editor and add the following code:
package main import "fmt" func main() { fmt.Println("Hello world!") }
Save this file, then use the go run command in your terminal to compile and execute the code in the cmd/api package. All being well, you will see the following output:
$ go run ./cmd/api Hello world!