Making CLIs in Golang with Cobra and PTerm

Making CLIs in Golang with Cobra and PTerm

Introduction

Hi everyone, welcome back to the blog! Today, we’re diving into the world of Command Line Interfaces (CLIs) using Go. Go is an excellent language for building CLIs due to its simplicity and efficiency. Many popular tools like Docker, Kubernetes, and Hugo are built using Go. In this tutorial, we’ll create a simple CLI application that accepts user input, saves it to a SQLite database, and lists the stored entries in a neat table.

Tools We’ll Use

  1. Cobra CLI: A widely-used framework for building CLIs in Go. It powers many well-known tools like Docker and Kubernetes.
  2. Pterm: A library that provides components and utilities for accepting user input and displaying information in a visually appealing way.

Getting Started

Step 1: Install Cobra-cli

To be able to use Cobra-cli in your machine, you need to install it. Cobra-cli makes using Cobra super simple. Cobra will also help us add commands, sub-commands and much more. We will explore this soon. For now let’s intall cobra-cli first. First, we need to install Cobra CLI. Run the following command:

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

This will install Cobra CLI as an executable on your machine. This also requires you have Go installed on your machine. If you don’t refer to go docs for insturctions on how to do that.

Step 2: Create a Go Project

Starting out our CLI is a go project after all. So we need to add a new project folder and init a go module. I have called this folder go-cli-basics. Feel free to call it whatever you want.

So let’s create a new directory for the project and initialize a Go module:

mkdir go-cli-basics
cd go-cli-basics
go mod init github.com/yourusername/go-cli-basics

Step 3: Initialize Cobra CLI

Cobra-cli will allow us to scaffold a CLI application super easily. This means Cobra-cli will do the heavy work on your behalf and get things started. This will save us time. Cobra will take care of many things for us. Including author details and license. We won’t cover that here, but you can learn more at cobra website.

For now let’s initialize Cobra CLI in your project directory:

cobra-cli init

After this command is run you should see a cmd directory and a main.go file.

.
├── cmd
│   └── root.go
├── go.mod
└── main.go

The cmd directory is the main module in this project. It will includeall your commands and subcommands; it mainly has business logic and all the code that will run in your CLI. You can also add other modules when needed like utils - which I usually do - or anything else. This is the basic skeleton for your CLI application. What does this root.go file has? Let’s explore more.

Step 4: Explore the Root Command

Open the cmd/root.go file. You’ll see the root command setup. You’ll see the root command defined with properties like Use, Short, and Long. These properties define the name and descriptions of your CLI. They will be used to describe your CLIs when you call the CLI command and passing the help flag -h Open the root.go file. You’ll see the root command, which is the entry point of your CLI. Here’s a brief overview of the important parts:

  • Use: The name of the CLI, which users will type to run it.
  • Short and Long Descriptions: Help descriptions that describe what the CLI does.
  • Run: The function that runs whenever the root command is triggered.

By default, the root command displays help information. Let’s modify it to print a simple message:

var rootCmd = &cobra.Command{
    Use:   "cli-basics",
    Short: "CLI Basics",
    Long:  `A simple CLI application built with Cobra.`,
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("Root command executed")
    },
}

Run the CLI with:

go run main.go

You should see “Root command executed” printed to the terminal.

Using Flags

Flags allow users to pass options to commands. The root command has a default flag called toggle. Let’s access this flag’s value:

toggle, _ := cmd.Flags().GetBool("toggle")
if toggle {
    fmt.Println("Toggle is true")
}

Now, run the CLI with the toggle flag:

go run main.go --toggle

You should see “Toggle is true” printed to the terminal.

Step 5: Add a New Command with Cobra-cli

Cobra also makes it very easy to add commands to your CLI. You don’t to have to worry about boilerplat code at all and you can focus on your core logic. Let’s add a new command called add:

cobra-cli add add

This creates a new file cmd/add.go. Open it and you’ll see a similar structure to root.go. Notice this part of the file:

func init() {
    rootCmd.AddCommand(addCmd)
}

This means this command is being added to the root command. So when your CLI is installed, you can call this using CLI_NAME add

Modify the Run function to accept user input using Pterm.

Step 6: Install Pterm

We can install Pterm very easily as a go package like so. Install Pterm by running:

go get github.com/pterm/pterm

Make sure to visit Pterm website to learn more about the library. It can do a lot more than you think. For this post we will use it to accept input from the user and display data in a table only.

Step 7: Accept User Input using Pterm

So let’s add a command called add 🃟 . In this command we will add new entries to our database. This command will use pterm to accept input from the user.

Modify cmd/add.go to accept user input:

package cmd

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

var addCmd = &cobra.Command{
    Use:   "add",
    Short: "Add a new entry",
    Long:  `Add a new entry to the database.`,
    Run: func(cmd *cobra.Command, args []string) {
        firstName, _ := pterm.DefaultInteractiveTextInput.WithDefaultText("Please enter your first name").Show()
        lastName, _ := pterm.DefaultInteractiveTextInput.WithDefaultText("Please enter your last name").Show()
        fmt.Println("First Name:", firstName)
        fmt.Println("Last Name:", lastName)
    },
}

func init() {
    rootCmd.AddCommand(addCmd)
}

Notice we used DefaultInteractiveTextInput from pterm. This is default text input that will pause execution and wait for user to input text. You can change many options about this default input. For now we simply changed the text that is displayed and we ignored the errors. Make sure you handle errors in your application tho! 😄 running commans Awesome so now we can take user input and store the value. Let’s do something useful with this value now

Step 8: Setup SQLite Database

Let’s setup a super simple Sqlite database. I have another post on how to use SQlite Database with go. You can read that here

If you already know how to do this, we can just keep going for now. We will need to install gorm and sqlite adapter for go. Gorm is the most widely used ORM for go. It’s a fantastic resource that we can cover in another blog post soon. For now you can learn more about gorm on their website. Anyways, let’s intall those two needed packages:

go get gorm.io/gorm
go get gorm.io/driver/sqlite

Modify cmd/add.go to save user input to the database:

package cmd

import (
    "fmt"
    "github.com/pterm/pterm"
    "github.com/spf13/cobra"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

type User struct {
    gorm.Model
    FirstName string
    LastName  string
}

var addCmd = &cobra.Command{
    Use:   "add",
    Short: "Add a new entry",
    Long:  `Add a new entry to the database.`,
    Run: func(cmd *cobra.Command, args []string) {
        firstName, _ := pterm.DefaultInteractiveTextInput.WithDefaultText("Please enter your first name").Show()
        lastName, _ := pterm.DefaultInteractiveTextInput.WithDefaultText("Please enter your last name").Show()

        db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
        if err != nil {
            panic("failed to connect database")
        }

        db.AutoMigrate(&User{})

        user := User{FirstName: firstName, LastName: lastName}
        db.Create(&user)

        pterm.Info.Println("Saved info to database")
    },
}

func init() {
    rootCmd.AddCommand(addCmd)
}

Notice how we added this part here to define the shape of the data in the database:

type User struct {
    gorm.Model
    FirstName string
    LastName  string
}

Also notice that we run AutoMigrate. This command will make the tables in our database in the same way to match our data that we described in this struct. Those basics are important. Once we run this command, our database is ready to accept entries. We do that by running db.Create function and passing in a variable of the correct struct that matches the structure in the database.

Now that we can accept input from the user and save it in our database, let’s make another command to list the entries in a nice table from pterm

Step 9: List Entries

Add a new command to list entries:

cobra-cli add list

Modify cmd/list.go to fetch and display entries from the database: To do this we will use the default display table in pterm. You can always customise the table, but for our case here just using the simple table is suffient.

package cmd

import (
    "github.com/pterm/pterm"
    "github.com/spf13/cobra"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

var listCmd = &cobra.Command{
    Use:   "list",
    Short: "List all entries",
    Long:  `List all entries in the database.`,
    Run: func(cmd *cobra.Command, args []string) {
        db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
        if err != nil {
            panic("failed to connect database")
        }

        var users []User
        db.Find(&users)

        tableData := pterm.TableData{{"First Name", "Last Name"}}
        for _, user := range users {
            tableData = append(tableData, []string{user.FirstName, user.LastName})
        }

        pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
    },
}

func init() {
    rootCmd.AddCommand(listCmd)
}

Notice how we added .WithHasHeader().WithData(tableData) Notice the WithHasHeader this makes sure we have the header or First Name and Last Name in the table. Notice also how we have to loop over the data and append it to the table data in an elegant way in go. I really love the go syntax although it doesn’t have many bills and whisles. 󰦶

Running the CLI

To run the CLI, use the following commands:

  1. Add a new entry:

    go run main.go add
    
  2. List all entries:

    go run main.go list
    

You wil be able to see now your data in the table: Final working CLI

Keen to get your hands on the code? 💻

Enter your details below and we will send you the code. You can also subscribe to get our latest posts. Don't worry, you can unsub anytime. 👊

Conclusion

In this tutorial, we built a simple CLI application using Go, Cobra, and Pterm. We learned how to:

  • Set up a Go project with Cobra CLI.
  • Accept user input using Pterm.
  • Save user input to a SQLite database using Gorm.
  • List entries from the database in a table format.

This is just the beginning. You can expand this project by adding more commands, handling errors, and improving the user interface.

If you enjoyed this post, you will probably find the ones below interesting too.

Did find this post helpful?

Understanding Bubble Sort Slowly

Understanding Bubble Sort Slowly

Sorting algorithms, I know, I know. Everybody keeps telling you that you have to learn them, no idea why they are important, and they seem like such a hu ge hassle to you.

Read More
Building a SPA-like Website with Go and HTMX

Building a SPA-like Website with Go and HTMX

Are you a tired JavaScript developer? Are you sick of having to learn so many different libraries that need to be pieced together to make something work?

Read More
Nextjs Not Fullstack Framework

Nextjs Not Fullstack Framework

Welcome back to another exciting blog post! Today, we’re diving into the world of Next.

Read More