Learning Go by examples: part 3 - Create a CLI app in Go
Tags: go, beginners, tutorial
Published: 2021-07-27 08:11:10

---
title: Learning Go by examples: part 3 - Create a CLI app in Go 
published: true
description: Serie of article in order to learn Golang language by concrete applications as example
serie: Learning Go by concrete examples
tags: Golang, beginners, tutorial
series: Learning Go by examples
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/np90qtung2fn2b4nd7ph.png
---

In the [first article](https://dev.to/aurelievache/learning-go-by-examples-introduction-448n) we setted up our environment, then we created an HTTP REST API server in the [second article](https://dev.to/aurelievache/learning-go-by-examples-part-2-create-an-http-rest-api-server-in-go-1cdm) and today, we will create our first CLI (Command Line Interface) application in Go.

## Initialization

We created our [Git repository](https://github.com/scraly/learning-go-by-examples) in the previous article, so now we just have to retrieve it locally:

```
$ git clone https://github.com/scraly/learning-go-by-examples.git
$ cd learning-go-by-examples
```

We will create a folder `go-gopher-cli` for our CLI application and go into it:

```
$ mkdir go-gopher-cli
$ cd go-gopher-cli
```

Now, we have to initialize Go modules (dependency management):

```
$ go mod init github.com/scraly/learning-go-by-examples/go-gopher-cli
go: creating new go.mod: module github.com/scraly/learning-go-by-examples/go-gopher-cli
```

This will create a `go.mod` file like this:

```
module github.com/scraly/learning-go-by-examples/go-gopher-cli

go 1.16
```

Before to start our super CLI application, as good practices, we will create a simple code organization.

Create the following folders organization:

```
.
├── README.md
├── bin
├── go.mod
```

That's it? Yes, the rest of our code organization will be created shortly ;-).

## Cobra

![Cobra](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0umvshn8uf92zdyphfy.png)

[Cobra](https://github.com/spf13/cobra) is both a library for creating powerful modern CLI applications and a program for generating applications and batch files.

Using Cobra is easy. First, you can use the `go get` command to download the latest version. This command will install the cobra library and its dependencies:

```
$ go get -u github.com/spf13/cobra@latest
```
Then install the Cobra CLI:

```
$ go install github.com/spf13/cobra-cli@latest
```

The `cobra` binary is now in the `bin/` directory of your **$GOPATH**, which is itself in your **PATH**, so you can use it directly.

We will start by generating our CLI application with the `cobra init` command followed by the package. The command will generate the application with the correct file structure and imports:

```
$ cobra-cli init
Your Cobra application is ready at
/workspace/learning-go-by-examples/go-gopher-cli
```

Our application is initialized, a `main.go` file and a `cmd/` folder has been created, our code organization is now like this:

```
.
├── LICENSE
├── bin
├── cmd
│   └── root.go
├── go.mod
├── go.sum
└── main.go
```

## Let's create our CLI

![Gopher 5th element](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/80x5n8t4dw3h3cj3h8p1.png)

What do we want?

Personally, what I like is that when I use a CLI, I want someone to explain to me the goal of the CLI and how to use it.

So, first of all, at the execution of our CLI, we want to display:

* a short description
* a long description
* using our app

In order to do this, we have to modify the `cmd/root.go` file:

```
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "go-gopher-cli",
	Short: "Gopher CLI in Go",
	Long:  `Gopher CLI application written in Go.`,
	// Uncomment the following line if your bare application
	// has an action associated with it:
	// Run: func(cmd *cobra.Command, args []string) { },
}
```

In the `root.go` file we have two external imports:
```
import (
	"fmt"
	"os"

	"github.com/spf13/cobra"

	"github.com/spf13/viper"
)
```

We already get cobra so you need now to get **viper** dependency:
```
$ go get github.com/spf13/viper@v1.8.1
```

Our `go.mod` file should be like this one:
```
module github.com/scraly/learning-go-by-examples/go-gopher-cli

go 1.16

require (
	github.com/spf13/cobra v1.2.1 // indirect
	github.com/spf13/viper v1.8.1 // indirect
	golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
	golang.org/x/text v0.3.6 // indirect
)
```

Now, we want to add a **get** command in our CLI application. For that, we will use the `cobra add` command of the cobra CLI:

```
$ cobra-cli add get
get created at /workspace/learning-go-by-examples/go-gopher-cli
```

![Warning: command names must be in camelCase format](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yq4b7paiho3e17zq7yvx.png)

This command add a new `get.go` file. Now our application structure should be like this:
```
.
├── LICENSE
├── bin
├── cmd
│   ├── get.go
│   └── root.go
├── go.mod
├── go.sum
└── main.go
```

It's time to execute our application:

```
$ go run main.go
Gopher CLI application written in Go.

Usage:
  go-gopher-cli [command]

Available Commands:
  completion  generate the autocompletion script for the specified shell
  get         A brief description of your command
  help        Help about any command

Flags:
      --config string   config file (default is $HOME/.go-gopher-cli.yaml)
  -h, --help            help for go-gopher-cli
  -t, --toggle          Help message for toggle

Use "go-gopher-cli [command] --help" for more information about a command.
```

By default, an usage message is displayed, perfect!

```
$ go run main.go get
get called
```

OK, the get command answered too.

## Let's implement our get command

What do we want?
Yes, good question, we want a gopher CLI which will retrieve our favorite Gophers by name!

We will now modify the `cmd/get.go` file like this:

```golang
var getCmd = &cobra.Command{
	Use:   "get",
	Short: "This command will get the desired Gopher",
	Long:  `This get command will call GitHub respository in order to return the desired Gopher.`,
	Run: func(cmd *cobra.Command, args []string) {
		var gopherName = "dr-who.png"

		if len(args) >= 1 && args[0] != "" {
			gopherName = args[0]
		}

		URL := "https://github.com/scraly/gophers/raw/main/" + gopherName + ".png"

		fmt.Println("Try to get '" + gopherName + "' Gopher...")

		// Get the data
		response, err := http.Get(URL)
		if err != nil {
			fmt.Println(err)
		}
		defer response.Body.Close()

		if response.StatusCode == 200 {
			// Create the file
			out, err := os.Create(gopherName + ".png")
			if err != nil {
				fmt.Println(err)
			}
			defer out.Close()

			// Writer the body to file
			_, err = io.Copy(out, response.Body)
			if err != nil {
				fmt.Println(err)
			}

			fmt.Println("Perfect! Just saved in " + out.Name() + "!")
		} else {
			fmt.Println("Error: " + gopherName + " not exists! :-(")
		}
	},
}
```

### Let's explain this block code step by step

When `get` command is called:

* we first initialize a variable **gopherName** with our default Gopher name
* then we retrieve the gopher name passed in parameter
* we initialize the URL of the gopher
* and finally we log it

```golang
		var gopherName = "dr-who"

		if len(args) >= 1 && args[0] != "" {
			gopherName = args[0]
		}

		URL := "https://github.com/scraly/gophers/raw/main/" + gopherName + ".png"

		fmt.Println("Try to get '" + gopherName + "' Gopher...")
```

Then, we:

* try to retrieve the gopher thanks to **net/http** package
* if gopher is retrieved, we create a file and put the image content into it
* else, we log an error message

```golang
		// Get the data
		response, err := http.Get(URL)
		if err != nil {
			fmt.Println(err)
		}
		defer response.Body.Close()

		if response.StatusCode == 200 {
			// Create the file
			out, err := os.Create(gopherName + ".png")
			if err != nil {
				fmt.Println(err)
			}
			defer out.Close()

			// Writer the body to file
			_, err = io.Copy(out, response.Body)
			if err != nil {
				fmt.Println(err)
			}

			fmt.Println("Perfect! Just saved in " + out.Name() + "!")
		} else {
			fmt.Println("Error: " + gopherName + " not exists! :-(")
		}
```

### Defer???

Wait a minute please, what is it?

There is a simple but important rule that is language agnostic: if you **open** a connection, you must **close** it! :-)

![Close if you open something](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/agrg79t1y8q6v5c7ksai.png)

Forgetting to close a connection / a response body... can cause resource/memory leaks in a long running programs.

That's why the **defer** exists. So in our code, we open a connection to the file when we create it and then we **defer** the execution of **out.Close()** which will be executed at the end of the function:

```golang
// Create the file
out, err := os.Create(gopherName + ".png")
if err != nil {
    fmt.Println(err)
}
defer out.Close()
```

So the best practice is to add your closing statement with a **defer** word right after your opening, so you don't forget it.

## Test it!

Let's test the help of our `get` command right now:
```
$ go run main.go get -h
This get command will call GitHub respository in order to return the desired Gopher.

Usage:
  go-gopher-cli get [flags]

Flags:
  -h, --help   help for get

Global Flags:
      --config string   config file (default is $HOME/.go-gopher-cli.yaml)
```

And now it's time to test our get command:
```
$ go run main.go get friends
Try to get 'friends' Gopher...
Perfect! Just saved in friends.png!
```

We can also test with an unknown Gopher:
```
$ go run main.go get awesome
Try to get 'awesome' Gopher...
Error: awesome not exists! :-(
```

## Build it!

Your application is now ready, you just have to build it.
For that, like the previous article, we will use [Taskfile](https://taskfile.dev/#/) in order to automate our common tasks.

So, for this app too, I created a `Taskfile.yml` file with this content:
```yaml
version: "3"
    
tasks:
    build:
        desc: Build the app
        cmds:
        - GOFLAGS=-mod=mod go build -o bin/gopher-cli main.go 

    run: 
        desc: Run the app
        cmds:
        - GOFLAGS=-mod=mod go run main.go

    clean:
        desc: Remove all retrieved *.png files
        cmds:
        - rm *.png
```

Thanks to this, we can build our app easily:
```
$ task build
task: [build] GOFLAGS=-mod=mod go build -o bin/gopher-cli main.go

$ ll bin/gopher-cli
-rwxr-xr-x  1 aurelievache  staff   9,7M 16 jul 21:10 bin/gopher-cli
```

Let's test it again with our fresh executable binary:
```
$ ./bin/gopher-cli get 5th-element
Try to get '5th-element' Gopher...
Perfect! Just saved in 5th-element.png!

$ file 5th-element.png
5th-element.png: PNG image data, 1156 x 882, 8-bit/color RGBA, non-interlaced
```

Cool! :-) 

## ... And clean it!

Each time you will execute the CLI app, an image file will be created locally, so if you want to clean your folder after gophers retrieving, I created a clean task :-)

```
$ task clean
task: [clean] rm *.png
```

Goodbye Gophers!

## Conclusion

As we have seen in this article, it's possible to create a simple CLI application in few minutes, thanks to **cobra** and **viper**, two awesome Go libraries.

All the code is available in: https://github.com/scraly/learning-go-by-examples/tree/main/go-gopher-cli

In the following articles we will create others kind/types of applications in Go.

Hope you'll like it.