From Idea to CLI Utility in Go: Writing, Compiling, Deploying

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why Go is the Ideal Language for CLI Utilities?

\n\n

Imagine: you need to write a tool that will run in the console, process files, interact with APIs, or simply automate routine tasks. You could choose Python, Node.js, or Bash. But if speed, binary size, and ease of distribution are critical — your choice is CLI utilities in Go.

\n\n

Go compiles into a single static binary. No dependencies, no "do you have Python 3.8 installed?" issues. Download it — run it. This makes Golang CLI tools the de facto standard in the DevOps world (remember Docker, Kubernetes, Hugo — all written in Go).

\n\n

In this article, we won't just talk about theory. We will build a real console utility in Go from scratch that will parse JSON, work with HTTP, and output beautiful tables. You will learn how to structure a project, handle errors, and compile for any platform.

\n\n

Environment Setup and Project Structure

\n\n

Before writing code, make sure you have Go installed (version 1.21+). Check it easily:

\n\n
go version
\n\n

Create a directory for our project. Let's name the utility json2table — it will read JSON from stdin or a file and output data in table format.

\n\n
mkdir json2table\ncd json2table\ngo mod init github.com/your-username/json2table
\n\n

The project structure will be classic for Go development:

\n\n
    \n
  • main.go — entry point
  • \n
  • internal/ — internal logic (parsing, output)
  • \n
  • cmd/ — command handling code (if using Cobra)
  • \n
\n\n

We'll start simple, without frameworks, and then add flags and help.

\n\n

Writing the Utility Core: JSON Parsing and Table Output

\n\n

Create the file internal/parser.go. Here we will parse JSON and convert it into a structure.

\n\n
package internal\n\nimport (\n    "encoding/json"\n    "fmt"\n    "io"\n)\n\n// Record represents one table row\ntype Record map[string]interface{}\n\n// ParseJSON reads JSON from reader and returns a slice of records\nfunc ParseJSON(r io.Reader) ([]Record, error) {\n    var data []Record\n    decoder := json.NewDecoder(r)\n    if err := decoder.Decode(&data); err != nil {\n        return nil, fmt.Errorf("JSON parsing error: %w", err)\n    }\n    return data, nil\n}
\n\n

Now create internal/table.go for formatted output:

\n\n
package internal\n\nimport (\n    "fmt"\n    "strings"\n    "text/tabwriter"\n    "os"\n)\n\n// PrintTable outputs records as a formatted table\nfunc PrintTable(records []Record) {\n    if len(records) == 0 {\n        fmt.Println("No data to display")\n        return\n    }\n\n    // Collect all unique keys (column headers)\n    headers := make([]string, 0)\n    seen := make(map[string]bool)\n    for _, rec := range records {\n        for key := range rec {\n            if !seen[key] {\n                headers = append(headers, key)\n                seen[key] = true\n            }\n        }\n    }\n\n    // Use tabwriter for beautiful output\n    w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)\n\n    // Header\n    fmt.Fprintln(w, strings.Join(headers, "\\t"))\n\n    // Separator\n    sep := make([]string, len(headers))\n    for i, h := range headers {\n        sep[i] = strings.Repeat("-", len(h))\n    }\n    fmt.Fprintln(w, strings.Join(sep, "\\t"))\n\n    // Data\n    for _, rec := range records {\n        row := make([]string, len(headers))\n        for i, h := range headers {\n            if val, ok := rec[h]; ok {\n                row[i] = fmt.Sprintf("%v", val)\n            } else {\n                row[i] = ""\n            }\n        }\n        fmt.Fprintln(w, strings.Join(row, "\\t"))\n    }\n\n    w.Flush()\n}
\n\n

Putting It All Together in main.

Blogs

Book Recommendations