Build a CLI Task Manager with Go and MongoDB: A Step-by-Step Guide
CLI Task Manager with Go and MongoDB
Introduction
In this tutorial, we will create a simple command-line interface (CLI) task manager using Go and MongoDB. This application will allow users to manage tasks by adding, listing, completing, and deleting them.
Prerequisites
Before we begin, ensure you have the following installed:
Go (version 1.16 or later)
MongoDB Atlas Cluster
A code editor (e.g., Visual Studio Code)
The source code for this project can be found on GitHub.
Project Structure
Create a new directory for your project and set up the following structure:
task-manager/
├── cli/
│ ├── task.go
├── database/
│ ├── database.go
├── model/
│ ├── task.go
├── main.go
├── .env
├── go.mod
└── go.sum
Step 1: Initialize the Go Module
Navigate to your project directory and initialize a new Go module:
go mod init tasker-manager
Step 2: Install Dependencies
go get github.com/urfave/cli/v2
go get github.com/joho/godotenv
go get go.mongodb.org/mongo-driver/mongo
go get github.com/gookit/color
go get github.com/jedib0t/go-pretty/v6/table
Step 3: Set Up a MongoDB Atlas Cluster
To store our tasks, we will use MongoDB Atlas. A database connection URL is required to connect the database. Follow the guide to set up a MongoDB Atlas cluster and get a connection URL.
Next, in the .env
file, add the connection URL as an environment variable.
Step 4: Create the .env File
Create a .env
file in the root of your project to store environment variables:
MONGODB_URI= <mongodb_URI>
MONGODB_DB_NAME= <db_name>
MONGODB_COLLECTION= <collection>
Step 5: Set Up MongoDB Connection
In database/database.go
, we will set up the MongoDB connection.
package database
import (
"context"
"log"
"os"
"github.com/joho/godotenv"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Config struct {
MongoDBURI string
MongoDBName string
MongoDBCollection string
}
var config *Config
var DB *mongo.Database
func init() {
if err := godotenv.Load(); err != nil {
log.Fatal(err)
}
config = &Config{
MongoDBURI: os.Getenv("MONGODB_URI"),
MongoDBName: os.Getenv("MONGODB_DB_NAME"),
MongoDBCollection: os.Getenv("MONGODB_COLLECTION"),
}
}
func GetDBCollection() *mongo.Collection {
return DB.Collection(config.MongoDBCollection)
}
func NewDBInstance() error {
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(config.MongoDBURI))
if err != nil {
log.Fatal(err)
}
DB = client.Database(config.MongoDBName)
return nil
}
In main.go
, add the following code to initialize the database
package main
import (
"go-mongodb/database"
"log"
)
func main() {
// Initialize MongoDB database instance
err := database.NewDBInstance()
if err != nil {
log.Fatal(err)
}
Step 6: Define the Task Model
In model/task.go
, we will define the Task structure and the functions to interact with the MongoDB collection.
package model
import (
"context"
"errors"
"fmt"
"go-mongodb/database"
"time"
"github.com/gookit/color"
"github.com/jedib0t/go-pretty/v6/table"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
type Task struct {
ID primitive.ObjectID `bson:"_id"`
Title string `bson:"title"`
Completed bool `bson:"completed"`
CreatedAt time.Time `bson:"created_at"`
UpdatedAt time.Time `bson:"updated_at"`
}
// Function to get all tasks
func GetAllTasks(filter interface{}) ([]bson.M, error) {
var tasks []bson.M
ctx := context.TODO()
collection := database.GetDBCollection()
cursor, err := collection.Find(ctx, filter)
if err != nil {
return tasks, err
}
for cursor.Next(ctx) {
task := bson.M{}
err := cursor.Decode(&task)
if err != nil {
return tasks, err
}
tasks = append(tasks, task)
}
if err := cursor.Err(); err != nil {
return tasks, err
}
cursor.Close(ctx)
if len(tasks) == 0 {
return tasks, mongo.ErrNoDocuments
}
return tasks, nil
}
// Function to add a task
func AddTask(task Task) error {
collection := database.GetDBCollection()
_, err := collection.InsertOne(context.TODO(), &task)
if err != nil {
return err
}
return nil
}
// Function to complete a task
func CompleteTask(taskID string) error {
objectID, err := primitive.ObjectIDFromHex(taskID)
if err != nil {
return err
}
collection := database.GetDBCollection()
filter := bson.D{primitive.E{Key: "_id", Value: objectID}}
update := bson.D{primitive.E{Key: "$set", Value: bson.D{primitive.E{Key: "completed", Value: true}}}}
_, err = collection.UpdateOne(context.TODO(), filter, update)
return err
}
// Function to delete a task
func DeleteTask(taskID string) error {
objectID, err := primitive.ObjectIDFromHex(taskID)
if err != nil {
return err
}
collection := database.GetDBCollection()
filter := bson.D{primitive.E{Key: "_id", Value: objectID}}
result, err := collection.DeleteOne(context.TODO(), filter)
if err != nil {
return err
} else if result.DeletedCount == 0 {
return errors.New("task not found")
}
return nil
}
// Function to print tasks
func PrintTasks(tasks []bson.M) {
t := table.NewWriter()
t.SetTitle("All Tasks")
t.AppendHeader(table.Row{"#ID", "Title", "Completed"})
for _, task := range tasks {
id := task["_id"].(primitive.ObjectID).Hex()
title := task["title"]
completed := task["completed"]
if task["completed"] == true {
id = color.Green.Sprint(id)
title = color.Green.Sprint(title)
completed = color.Green.Sprint(completed)
} else {
id = color.Yellow.Sprint(id)
title = color.Yellow.Sprint(title)
completed = color.Yellow.Sprint(completed)
}
t.AppendRow(table.Row{id, title, completed})
}
fmt.Println(t.Render())
}
Step 7: Create the CLI Interface
In cli/task.go
, we will define the CLI commands for managing tasks.
package cli
import (
"fmt"
"go-mongodb/model"
"log"
"os"
"time"
"github.com/gookit/color"
"github.com/urfave/cli/v2"
"go.mongodb.org/mongo-driver/bson/primitive"
)
var taskApp *cli.App
func TaskCLI() {
taskApp = &cli.App{
Name: "Task Manager",
Version: "v1.0",
Compiled: time.Now(),
Usage: "Task management tool",
Commands: []*cli.Command{
{
Name: "list",
Aliases: []string{"ls"},
Action: func(ctx *cli.Context) error {
fmt.Println("List all tasks...")
tasks, err := model.GetAllTasks(bson.D{{}})
if err != nil {
log.Fatal(err)
}
model.PrintTasks(tasks)
return nil
},
Subcommands: []*cli.Command{
{
Name: "completed",
Action: func(ctx *cli.Context) error {
tasks, err := model.FilterTasks(true)
if err != nil {
log.Fatal(err)
}
model.PrintTasks(tasks)
return nil
},
},
{
Name: "uncompleted",
Action: func(ctx *cli.Context) error {
tasks, err := model.FilterTasks(false)
if err != nil {
log.Fatal(err)
}
model.PrintTasks(tasks)
return nil
},
},
},
},
{
Name: "add",
Aliases: []string{"a"},
Action: func(ctx *cli.Context) error {
if ctx.Args().Len() == 0 {
color.Red.Println("Please provide a task title!")
return nil
}
title := ctx.Args().First()
task := model.Task{
ID: primitive.NewObjectID(),
Title: title,
Completed: false,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if err := model.AddTask(task); err != nil {
color.Red.Println("Error adding task", err)
} else {
color.Green.Println("Task added successfully!")
}
return nil
},
},
{
Name: "complete",
Aliases: []string{"cpt"},
Action: func(ctx *cli.Context) error {
if ctx.Args().Len() == 0 {
color.Red.Println("Provide task id to complete task!")
return nil
}
id := ctx.Args().First()
if err := model.CompleteTask(id); err != nil {
color.Red.Println("Error completing task", err)
} else {
color.Green.Println("Task completed successfully")
}
return nil
},
},
{
Name: "delete",
Aliases: []string{"rm"},
Action: func(ctx *cli.Context) error {
if ctx.Args().Len() == 0 {
color.Red.Println("Provide task id to delete task!")
return nil
}
id := ctx.Args().First()
if err := model.DeleteTask(id); err != nil {
color.Red.Println("Error deleting task", err)
} else {
color.Green.Println("Task deleted successfully")
}
return nil
},
},
},
}
}
func Run() error {
if err := taskApp.Run(os.Args); err != nil {
return err
}
return nil
}
In main.go
, add the following code for the cli.
package main
import (
"go-mongodb/cli"
"go-mongodb/database"
"log"
)
func main() {
// Initialize MongoDB database instance
err := database.NewDBInstance()
if err != nil {
log.Fatal(err)
}
// Start the task CLI
cli.TaskCLI()
if err := cli.Run(); err != nil {
log.Fatal(err)
}
}
Step 8: Using the CLI
You can now use the following commands to manage your tasks:
Add a new task:
go run main.go add "Task Title"
List all tasks:
go run main.go list
Complete a task:
go run main.go complete <task_id>
Delete a task:
go run main.go delete <task_id>
Conclusion
Congratulations! You have successfully built a CLI task manager using Go and MongoDB. You can extend this application by adding more features such as task prioritization, due dates, or user authentication.