How to create a CLI app with Go

Go provides lot of tools for building great CLI apps. Some of the great tools available in the go ecosystem are tools like Cobra, Bubbletea. In this tutorial we will look at how we can build a command line app in Cobra.

Create a new go project

Start by creating a new project folder in go. Create the main.go file.

mkdir <go-project-name>
cd <go-project-name>
touch main.go

Create a main.go file

A main.go file will be the entry point of your application. Create a main.go file in the root of your project and run go run main.go to check if everything is working fine.

package main

import "fmt"

func main(){

    fmt.Println("Hello")
}

Create a mod file

Let's begin by initializing a Go module. The go.mod file serves a purpose similar to package.json in JavaScript—it provides a blueprint of all the dependencies your application will use, ensuring a well-defined structure for package management.

go mod init example.com/cliapp

Add the latest Cobra package

You're now ready to install the Cobra CLI framework. Simply run the following command in your terminal to get it set up

go get -u github.com/spf13/cobra@latest

Install Cobra CLI

To use Cobra's commands, you'll need to install the Cobra CLI globally. After installation, update your Go path to ensure all packages are properly available before using any Cobra commands in your application.

go install github.com/spf13/cobra-cli@latest
export PATH=$PATH:$(go env GOPATH)/bin

Intialize a Cobra Project

Initialize a cobra project with the following command

cobra-cli init

Create a new command using Cobra CLI

We can create a new command with cobra cli as follows

cobra-cli add products

This will create some boilerplate code for us inside the cmd folder inside products.go file. The boilerplate code looks something like this.

/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
    "fmt"
    "github.com/spf13/cobra"
)

// productsCmd represents the products command
var productsCmd = &cobra.Command{
    Use:   "products",
    Short: "A brief description of your command",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("products called")
    },
}

func init() {
    rootCmd.AddCommand(productsCmd)

    productsCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

Create some variables and command flags to conditionally trigger the command

Next, let’s define some variables and set up command flags to control when and how our command is triggered. Command flags are a powerful way to allow users to modify the behavior of your CLI

var (
    all    bool
    single int
)
func init() {
    rootCmd.AddCommand(productsCmd)
    productsCmd.Flags().BoolVarP(&all, "all", "a", false, "display all products")
    productsCmd.Flags().IntVarP(&single, "single", "s", 0, "display just one product")
}

Create Products model

The below example is how you can create a products model using go structs. Products models will be needed to interact with the http call and save the data received from the Http endpoint.

// products/index.go
package products

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "products_cli_app/utils"
    "strconv"

    "github.com/olekukonko/tablewriter"
)

type Product struct {
    ID          int     `json:"id"`
    Title       string  `json:"title"`
    Price       float64 `json:"price"`
    Description string  `json:"description"`
    Category    string  `json:"category"`
    Image       string  `json:"_"`
}

Create a function that fetches all products from the http request

Now, let's create a function that fetches all products via an HTTP request. This function will act as a bridge between our application and the server, retrieving product data for us to use. Whether you're building an e-commerce site or just need to display some product listings, this function will get the job done.

func FetchProducts() {

    var p []Product
    table := tablewriter.NewWriter(os.Stdout)
    table.SetHeader([]string{"ID", "Title", "Price", "Description", "Category"})
    url := "https://fakestoreapi.com/products"

    response, err := http.Get(url)
    if err != nil {
        utils.Error("Cannot fetch products", err)
        os.Exit(1)
    }
    body, err := io.ReadAll(response.Body)
    if err != nil {
        utils.Error("Cannot read body", err)
        os.Exit(1)
    }

    defer response.Body.Close()

    err = json.Unmarshal(body, &p)
    if err != nil {
        utils.Error("Cannot unmarshal", err)
        os.Exit(1)
    }

    for _, v := range p {
        table.Append([]string{
            strconv.Itoa(v.ID),
            v.Title,
            fmt.Sprintf("%.2f", v.Price),
            v.Description,
            v.Category,
        })
    }

    table.Render()

}

Create a function that fetches single product from the http endpoint

In this section, we’ll write a function to fetch a single product from an HTTP endpoint. This is perfect when you need detailed information about a specific item, like when users click on a product for more details. By retrieving just one product, we keep things efficient and focused.

func FetchSingleProduct(param int) {

    var p Product
    table := tablewriter.NewWriter(os.Stdout)
    table.SetHeader([]string{"ID", "Title", "Price", "Description", "Category"})

    url := fmt.Sprintf("%s/%v", "https://fakestoreapi.com/products", param)

    response, err := http.Get(url)
    if err != nil {
        utils.Error("Cannot fetch products", err)
        os.Exit(1)
    }
    body, err := io.ReadAll(response.Body)
    if err != nil {
        utils.Error("Cannot read body", err)
        os.Exit(1)
    }
    defer response.Body.Close()

    err = json.Unmarshal(body, &p)
    if err != nil {
        utils.Error("Cannot unmarshal", err)
        os.Exit(1)
    }

    table.Append([]string{
        strconv.Itoa(p.ID),
        p.Title,
        fmt.Sprintf("%.2f", p.Price),
        p.Description,
        p.Category,
    })

    table.Render()

}

Modify the Run method to conditional trigger the products

We have made available the product functions which fetch all products and single products. Now we can create a custom method inside the products.go file to conditionally call the functions if the user provides flags like —a or —s depending on the scenario

// cmd/products.go
var productsCmd = &cobra.Command{
    Use:   "products",
    Short: "Fetches all products",
    Run:   executeCmd,
}

func executeCmd(cmd *cobra.Command, args []string) {

    if all {
        products.FetchProducts()
    } else if single > 0 {
        products.FetchSingleProduct(single)
    }

}

Save all your commands

Now that we’ve set up our commands and flags, it’s time to save them all. This step is crucial as it ensures that the commands we’ve defined are stored and ready to use. Think of this as saving your work after setting everything up—you’ll want to make sure all your hard work is preserved so you can run your commands smoothly whenever you need them.

package main

import "products_cli_app/cmd"

func main() {
    cmd.Execute()
}

Output

Execute the below command to get your output. This is how your output will look like. As we are using a tablewriter package. The output is formatted as a table.

Conclusion

In this article, we explored how to create a powerful command-line app using Go and the Cobra CLI framework. From setting up the project structure to adding commands and flags, you now have a solid foundation to build and customize your own CLI tools. Whether you are creating a utility for personal use or something that will help your team, Cobra makes it seamless and efficient.

If this guide helped you or sparked some new ideas, feel free to leave a comment below! Don’t forget to follow me on Hashnode for more articles like this. I'll be sharing more insights, tips, and tutorials on Go and other development topics. Let's keep building together!

1
Subscribe to my newsletter

Read articles from Mithilesh Tarkar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mithilesh Tarkar
Mithilesh Tarkar