Building My First Go Application: A Weather CLI Tool

Alex NyamburaAlex Nyambura
7 min read

I wanted to learn Go and build something useful at the same time. This article shows how I built a weather app from start to finish.

Why I Picked Go

I want to work with automation, networking, and cloud stuff. I found out that Go is used to build Docker, Kubernetes, and Terraform - all the tools I want to use. Go is also known for being simple but powerful for network apps.

How I Learned: Build While Learning

Instead of just reading tutorials, I built something useful, a weather app for the terminal. This way, I learned Go by actually using it.

What We'll Build

A simple weather app that:

  • 🌤️ Gets real weather data from a free API

  • 🌍 Works with any city in the world

  • 📊 Shows clean, easy-to-read output

  • ⚡ Runs fast

  • 🔧 Good starting point for automation scripts

Final output looks like this:

❯ go run main.go "Nairobi"
Weather for Nairobi:
Temperature: 16.0°C
Condition: Clear
Humidity: 88%
Wind: 4 km/h

Step by Step: How I Built It

1. Setting Up the Project

First thing I learned: Go uses modules like other languages use package files.

mkdir weather-cli
cd weather-cli
go mod init github.com/lxmwaniky/weather-cli

This creates a go.mod file that keeps track of what packages I use.

2. Hello World to Taking User Input

Started with the basic setup:

package main

import "fmt"

func main() {
    fmt.Println("Weather automation starting...")
}

What I learned: Every Go program needs package main and a main() function - this is where the program starts running.

Then I added code to take the city name from the user:

import (
    "fmt" 
    "os"
)

func main() {
    if len(os.Args) < 2 {
        fmt.Println("Usage: go run main.go <location>")
        os.Exit(1)
    }

    location := os.Args[1]
    fmt.Printf("Getting weather for: %s\n", location)
}

What I learned: os.Args gives you what the user typed. os.Args[0] is the program name, os.Args[1] is what comes after.

3. Making HTTP Requests

Go's built-in net/http package makes API calls surprisingly simple:

import (
    "fmt"
    "net/http" 
    "os"
)

func main() {
    // ... previous code ...

    resp, err := http.Get("https://httpbin.org/get")
    if err != nil {
        fmt.Printf("HTTP request failed: %v\n", err)
        os.Exit(1)
    }

    fmt.Printf("HTTP request successful! Status: %s\n", resp.Status)
}

What I learned: Go has a simple way to handle errors - functions return two things: the result and an error. Always check if the error happened.

4. Reading Response Bodies

To use the API response:

import (
    "fmt"
    "io"
    "net/http"
    "os"
)

func main() {
    // ... previous code ...

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("Failed to read response: %v\n", err)
        os.Exit(1)
    }

    fmt.Printf("Response: %s\n", string(body))
}

Key Learning: io.ReadAll() reads all data from an io.Reader (like HTTP response body). Convert []byte to string for printing.

5. Working with Real APIs

Switched to OpenWeatherMap API with URL building:

apiKey := os.Getenv("OPENWEATHER_API_KEY")
url := fmt.Sprintf("https://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=metric", location, apiKey)

What I learned: fmt.Sprintf() lets you build text with variables in it - useful for making URLs that change.

6. Environment Variables with .env Files

Added github.com/joho/godotenv for environment variable management:

go get github.com/joho/godotenv
import "github.com/joho/godotenv"

func main() {
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }
    // ... rest of code ...
}

Key Learning: go get automatically downloads and adds dependencies to go.mod. External packages use full URLs.

7. JSON Parsing with Structs

The most complex part - converting JSON to Go structs:

type WeatherResponse struct {
    Main struct {
        Temp     float64 `json:"temp"`
        Humidity int     `json:"humidity"`
    } `json:"main"`
    Weather []struct {
        Main string `json:"main"`
    } `json:"weather"`
    Wind struct {
        Speed float64 `json:"speed"`
    } `json:"wind"`
    Name string `json:"name"`
}

Key Learning:

  • Structs organise related data

  • JSON tags map JSON field names to Go field names

  • []struct represents arrays of objects

  • Capital letters make fields exportable

8. Unmarshaling JSON

Converting the JSON response to our struct:

var weather WeatherResponse
err = json.Unmarshal(body, &weather)
if err != nil {
    fmt.Printf("Failed to parse JSON: %v\n", err)
    os.Exit(1)
}

fmt.Printf("Weather for %s:\n", weather.Name)
fmt.Printf("Temperature: %.1f°C\n", weather.Main.Temp)
fmt.Printf("Condition: %s\n", weather.Weather[0].Main)
fmt.Printf("Humidity: %d%%\n", weather.Main.Humidity)
fmt.Printf("Wind: %.0f km/h\n", weather.Wind.Speed*3.6)

Key Learning:

  • json.Unmarshal() converts JSON bytes to Go structs

  • &weather passes the address where to store the data

  • Access struct fields with dot notation

  • Format strings with %.1f for decimals, %d for integers

Complete Final Code

Here's the complete working application:

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"

    "github.com/joho/godotenv"
)

type WeatherResponse struct {
    Main struct {
        Temp     float64 `json:"temp"`
        Humidity int     `json:"humidity"`
    } `json:"main"`
    Weather []struct {
        Main string `json:"main"`
    } `json:"weather"`
    Wind struct {
        Speed float64 `json:"speed"`
    } `json:"wind"`
    Name string `json:"name"`
}

func main() {
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }

    if len(os.Args) < 2 {
        fmt.Println("Usage: go run main.go <location>")
        os.Exit(1)
    }

    location := os.Args[1]
    apiKey := os.Getenv("OPENWEATHER_API_KEY")
    if apiKey == "" {
        fmt.Println("Please set OPENWEATHER_API_KEY in your .env file")
        os.Exit(1)
    }

    url := fmt.Sprintf("https://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=metric", location, apiKey)

    resp, err := http.Get(url)
    if err != nil {
        fmt.Printf("HTTP request failed: %v\n", err)
        os.Exit(1)
    }

    if resp.StatusCode != 200 {
        fmt.Printf("API request failed with status: %d\n", resp.StatusCode)
        os.Exit(1)
    }

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("Failed to read response: %v\n", err)
        os.Exit(1)
    }
    defer resp.Body.Close()

    var weather WeatherResponse
    err = json.Unmarshal(body, &weather)
    if err != nil {
        fmt.Printf("Failed to parse JSON: %v\n", err)
        os.Exit(1)
    }

    fmt.Printf("Weather for %s:\n", weather.Name)
    fmt.Printf("Temperature: %.1f°C\n", weather.Main.Temp)
    fmt.Printf("Condition: %s\n", weather.Weather[0].Main)
    fmt.Printf("Humidity: %d%%\n", weather.Main.Humidity)
    fmt.Printf("Wind: %.0f km/h\n", weather.Wind.Speed*3.6)
}

Running the Application

Setup

  1. Clone the repository:
git clone https://github.com/lxmwaniky/weather-cli.git
cd weather-cli
go mod tidy
  1. Get a free API key from OpenWeatherMap

  2. Create .env file:

echo "OPENWEATHER_API_KEY=your_api_key_here" > .env

Usage Examples

# Get weather for any city
go run main.go "Nairobi"
go run main.go "New York"
go run main.go "Tokyo"

Building Executables

# For your current system
go build -o weather main.go

# Cross-compilation for different platforms
GOOS=linux GOARCH=amd64 go build -o weather-linux main.go
GOOS=windows GOARCH=amd64 go build -o weather.exe main.go
GOOS=darwin GOARCH=amd64 go build -o weather-mac main.go

Key Go Concepts Learned

1. Package Management

  • go mod init creates modules

  • go get installs dependencies

  • go mod tidy cleans up dependencies

2. Error Handling

  • Functions return multiple values, including errors

  • Always check if err != nil

  • Use os.Exit(1) for fatal errors

3. HTTP Client

  • net/http package for HTTP requests

  • http.Get() for simple GET requests

  • io.ReadAll() to read response bodies

4. JSON Processing

  • Define structs that match the JSON structure

  • Use JSON tags to map field names

  • json.Unmarshal() converts JSON to structs

5. Command Line Tools

  • os.Args for command line arguments

  • os.Getenv() for environment variables

  • fmt.Printf() for formatted output

Why This Approach Works

Building a real project while learning provided:

  • Immediate feedback - I could see results right away

  • Practical context - Each concept had a clear purpose

  • Production-ready skills - The patterns I learned apply to larger applications

  • Confidence boost - I built something useful on my first try

What's Next?

This weather CLI demonstrates the foundation for:

  • Automation scripts - HTTP clients and JSON processing

  • Microservices - HTTP servers and API development

  • Cloud tools - Infrastructure automation and monitoring

  • Networking applications - TCP/UDP clients and servers

I'm excited to continue exploring Go's capabilities in these areas.

Try It Yourself!

The complete code is available on GitHub. Clone it, experiment with it, and build upon it.

Go's simplicity makes it accessible for learning, while its power makes it suitable for production systems. This weather CLI might be straightforward, but it uses the same patterns you'll find in major cloud infrastructure tools.


What would you build with Go? Share your ideas in the comments below!

#golang #beginners #cli #automation #webdev #tutorial

10
Subscribe to my newsletter

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

Written by

Alex Nyambura
Alex Nyambura