Go CLI from the ground up (Part 1)

GoLang What different techniques and approaches should I follow when creating a Go app?. These articles will try to answer that.

The Why

I’ve been doing Java development for the better part of the last 15 years, and by now I have a pretty good sense of how to properly layout projects and apps of all sizes. Not so for Go. This will be my very public attempt to educate myself in the Virtuous Ways of the Gopher™️.

The Standard Go Project Layout certainly packs a punch, but it lacks a gradual approach on which I could base a basic POC.

Watching old GopherCon videos, I found Matt Ryer‘s presentation How I write Go HTTP services after seven years. It-Just-Makes-Sense; but it still didn’t provide enough context: how do I make it externally configurable? how do I layer middleware concerns into it? how do I …?.

That’s when I found AAF Engineering‘s series on building a Go Web Application Structure, 👌.

The code corresponding to this article can be found in my GitHub page, Go CLI sandbox - Part 1

The What

Let’s be honest, there’s a thousand useful apps out there, I won’t come up with an idea for a Unicorn 🦄 in a personal blog post, so I’ll do the next worst/best thing: a simple app that interacts with this very site and also demonstrates how to incrementally add features to it, following what I currently consider best-practices (⚠️ Dunning-Kruger effect strikes again.)

The How

My basic scaffolding will provide a EditorConfig template (to keep future edits consistent), a Makefile template (to make the building of the project repeatable and independant of what I type in the terminal) and a basic folder structure (see above):

cli-sandbox
│   README.md
│   Makefile
│   .editorconfig
│   main.go
└───cmd
    │   root.go
    │   version.go
    └── website.go

Cobra 🐍

See https://github.com/spf13/cobra for way more details:

I grouped all my publicly available functionality under the cmd package; there, a public Execute() function will initalize a “root” command, to which all others will attach themselves:

Root command setup:

var rootCmd = &cobra.Command{
	Use:   "cli-sandbox",
	Short: "Esmit's attempt to make sense of Cobra",
}

Additional version command example:

var versionCmd = &cobra.Command{
	Use:   "version",
	Short: "Displays this program's version",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("cli-sandbox 0.1")
	},
}

Then it’s simply a matter of executing cmd.Execute() from main.go:

func main() {
	cmd.Execute()
}

When invoked without any arguments, a list of all available commands, and its description is printed out:

❯ ./esmit-cli-sandbox
Esmit's attempt to make sense of Cobra

Usage:
  cli-sandbox [command]

Available Commands:
  help        Help about any command
  version     Displays this program's version
  website     Displays Esmit's personal site link

Flags:
  -h, --help   help for cli-sandbox

Use "cli-sandbox [command] --help" for more information about a command.

If you pass the parameters version or website, the command switches to executing the corresponding function. It currently prints out some string for each:

❯ ../esmit-cli-sandbox website

https://esmit.me

❯ ./esmit-cli-sandbox version
cli-sandbox 0.1

What’s next

In a future post, I’ll make this little app call my website and retrieve a list of posts using Go’s http package.