Building My First Go Application: A Weather CLI Tool


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 objectsCapital 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 dataAccess 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
- Clone the repository:
git clone https://github.com/lxmwaniky/weather-cli.git
cd weather-cli
go mod tidy
Get a free API key from OpenWeatherMap
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 modulesgo get
installs dependenciesgo 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 requestshttp.Get()
for simple GET requestsio.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 argumentsos.Getenv()
for environment variablesfmt.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
Subscribe to my newsletter
Read articles from Alex Nyambura directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
