Mastering CLI Applications with Cobra

As developers, we spend a significant amount of time in the terminal, and chances are high that you’ve used a command line interface (CLI) for your projects. CLI applications have become increasingly popular due to their ease of configuration and flexibility. One such popular Go library for building CLI applications is Cobra.

What is Cobra?

Cobra is a powerful tool that allows you to create CLI applications with ease. It’s widely used in popular developer tools like the Github CLI, Hugo, and more. In this tutorial, we’ll dive into the world of Cobra by building a simple accounting CLI application that bills users, stores information in a JSON file, records receipts, and tracks a user’s total balance.

Getting Started with Cobra

To begin, we need to install the Cobra Generator, which provides an easy way to generate commands that give life to the application. Run the following command to install the Cobra Generator:

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

Understanding CLI Commands and Flags

Before we start building our app, let’s understand the main components of a CLI application. A CLI application typically comprises the application’s name, the command, flags, and arguments. For example, when using Git to clone a project, we usually run the following command:

git clone url.to.project

Here, git is the application name, clone is the command, and url.to.project is the argument passed to the command.

Creating the Cobra App

To create a new Cobra application, run the following command:

cobra init accountant

This command creates a new folder, accountant, and creates a main.go file, a LICENSE file, and a cmd folder with a root.go file. Note that this command does not create a go.mod file, so we must initialize the Go module by ourselves:

go mod init accountant

The Cobra App Entry Point

The entry point to our Cobra app is main.go, and it’s essential to keep the main package lean so we can keep different aspects of the application separate. Looking at the Cobra-generated main.go file, we find that the main function has just one function: executing the root command.

Creating Commands in Cobra

Our application should be able to handle two main features: debiting and crediting users. Thus, we must create two commands: debit and credit. Run the following to generate those commands:

cobra add debit
cobra add credit

Adding a JSON Storage Layer

For this application, we’ll use a simple storage layer. We’ll store our data in a JSON file and access them via a Go module. In the root directory, create a database folder, then create a db.go file and a db.json file. Add the following to db.go to interact with the database:

“`go
type User struct {
Username string
Balance int64
Transactions []Transaction
}

type Transaction struct {
Amount int64
Type string
Narration string
}

func getUsers() ([]User, error) {
// load database file and return stored user data
}

func updateDB(users []User) error {
// write updated data to the database
}

func FindUser(username string) *User {
// find a user in the database with a username and return it
}

func FindOrCreateUser(username string) *User {
// find a user in the database with a username and return it; if no user, create a new user
}

func UpdateUser(user *User) error {
// update the corresponding entry in the database
}
“`

Implementing Credit Transactions with Cobra

Modify the credit command to create an adequate description for the command and add a usage section in the long description:

“`go
func init() {
creditCmd.Flags().StringP(“narration”, “n”, “”, “Transaction narration”)
creditCmd.Flags().Int64P(“amount”, “a”, 0, “Transaction amount”)
rootCmd.AddCommand(creditCmd)
}

func run(args []string) error {
// get the username from the args array
username := args[0]
user, err := FindOrCreateUser(username)
if err!= nil {
return err
}
// increment the user’s balance and add a new transaction
user.Balance += amount
transaction := Transaction{Amount: amount, Type: “credit”, Narration: narration}
user.Transactions = append(user.Transactions, transaction)
// update the database with the new user data
err = UpdateUser(user)
if err!= nil {
return err
}
return nil
}
“`

Implementing Debit Transactions with Cobra

The debit command looks similar to the credit command. The only difference is the run function, which reduces a user’s balance while crediting increases the user’s balance.

go
func run(args []string) error {
// get the username from the args array
username := args[0]
user, err := FindOrCreateUser(username)
if err!= nil {
return err
}
// check if the user has enough balance to perform the transaction
if user.Balance < amount {
return errors.New("insufficient balance")
}
// reduce the user's balance and add a new transaction
user.Balance -= amount
transaction := Transaction{Amount: amount, Type: "debit", Narration: narration}
user.Transactions = append(user.Transactions, transaction)
// update the database with the new user data
err = UpdateUser(user)
if err!= nil {
return err
}
return nil
}

With this, we have successfully implemented the credit and debit commands using Cobra. Our application can now be built by running go build.

Leave a Reply

Your email address will not be published. Required fields are marked *