Build a Weather CLI App in Go: Step-by-Step Guide for Beginners

0
24
Build a Weather CLI App in Go
Build a Weather CLI App in Go

Today, we’re diving into an exciting project: build a weather CLI app in Go. In this guide, I’ll walk you through every step of creating your very own command-line weather application. Whether you’re new to Go or just looking for a fun project, this tutorial has got you covered.

We’ll cover everything from setting up the project and writing code to testing our application. And, I promise – I’ll explain every piece of code so that you know exactly what’s going on under the hood.

So, grab your favorite beverage, fire up your editor, and let’s get coding!

Project Setup

Before we start coding, let’s understand our goal. We want to build a weather CLI app in Go that will fetch weather data from an online API and display it in our terminal. We’ll use OpenWeatherMap as our weather data provider. Make sure to sign up for a free API key from them if you haven’t already.

Our app will perform the following steps:

  • Accept a city name as input.
  • Call the weather API.
  • Parse the JSON response.
  • Display the current weather information.

This project is simple yet powerful. By following along, you’ll get hands-on experience with HTTP requests, JSON parsing, and command-line interfaces in Go.

Understanding the Weather CLI App

Before jumping into the code, let’s break down what our weather CLI app in Go will do:

  1. User Input:
    The app will prompt the user for a city name or accept it as a command-line argument.
  2. HTTP Request:
    Using Go’s built-in net/http package, we will send an HTTP GET request to the OpenWeatherMap API.
  3. JSON Parsing:
    Once we receive a response, we’ll decode the JSON data into Go structs.
  4. Displaying Data:
    Finally, we’ll neatly display the weather details (like temperature, humidity, and description) on the terminal.

By working on this project, you’ll learn how to handle external APIs, work with JSON data, and build user-friendly CLI apps in Go.

Step 1: Creating the Project and Initializing Modules

First things first: let’s create our project directory and initialize a Go module.

Create a new directory for your project:

mkdir weather-cli-app
cd weather-cli-app

Initialize the Go module:

go mod init weather-cli-app

This command creates a go.mod file, which will track our project’s dependencies. Now, we have a clean slate to start building our weather CLI app in Go.

Step 2: Setting Up the API Client

Now that our project is set up, we need to create an API client to fetch weather data. We’ll write a simple function to make an HTTP GET request to OpenWeatherMap.

Create a new file called weather.go:

touch weather.go

Add the following code to weather.go:

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
)

// WeatherData holds the structure of the JSON response from OpenWeatherMap.
type WeatherData struct {
    Name string `json:"name"`
    Main struct {
        Temp     float64 `json:"temp"`
        Humidity int     `json:"humidity"`
    } `json:"main"`
    Weather []struct {
        Description string `json:"description"`
    } `json:"weather"`
}

// getWeatherData fetches weather data for a given city.
func getWeatherData(city string, apiKey string) (*WeatherData, error) {
    // Construct the API URL. We use the metric system for temperature (Celsius).
    url := fmt.Sprintf("http://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=metric", city, apiKey)

    // Make the HTTP request.
    resp, err := http.Get(url)
    if err != nil {
        return nil, fmt.Errorf("failed to make request: %v", err)
    }
    defer resp.Body.Close()

    // Check if the request was successful.
    if resp.StatusCode != http.StatusOK {
        bodyBytes, _ := ioutil.ReadAll(resp.Body)
        return nil, fmt.Errorf("error: received status code %d: %s", resp.StatusCode, string(bodyBytes))
    }

    // Read the response body.
    bodyBytes, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("failed to read response: %v", err)
    }

    // Decode the JSON data into our WeatherData struct.
    var weather WeatherData
    err = json.Unmarshal(bodyBytes, &weather)
    if err != nil {
        return nil, fmt.Errorf("failed to decode JSON: %v", err)
    }

    return &weather, nil
}

Code Explanation:

  • We define a WeatherData struct to match the JSON structure from OpenWeatherMap. This will help us decode the API response easily.
  • This function accepts a city name and an API key. It builds the request URL, sends an HTTP GET request, and handles the response. If successful, it unmarshals the JSON into a WeatherData struct.

By following these steps, you now have a working API client component in your weather CLI app in Go.

Step 3: Building the CLI Interface

Next up, let’s build the command-line interface. We’ll let users pass the city name directly when running the app. If they don’t, we’ll ask for input interactively.

Create a file called main.go:

touch main.go

Add the following code to main.go:

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    // Read the API key from an environment variable.
    apiKey := os.Getenv("OPENWEATHER_API_KEY")
    if apiKey == "" {
        fmt.Println("Error: Please set the OPENWEATHER_API_KEY environment variable.")
        os.Exit(1)
    }

    // Check if a city name is provided as a command-line argument.
    var city string
    if len(os.Args) > 1 {
        city = strings.Join(os.Args[1:], " ")
    } else {
        // Prompt the user for a city name if no argument is provided.
        fmt.Print("Enter the city name: ")
        reader := bufio.NewReader(os.Stdin)
        input, err := reader.ReadString('\n')
        if err != nil {
            fmt.Println("Error reading input:", err)
            return
        }
        city = strings.TrimSpace(input)
    }

    // Fetch the weather data.
    weather, err := getWeatherData(city, apiKey)
    if err != nil {
        fmt.Println("Error fetching weather data:", err)
        return
    }

    // Display the weather information.
    displayWeatherData(weather)
}

Code Explanation:

  • The API key is read from an environment variable (OPENWEATHER_API_KEY). This is a secure way to manage sensitive information.
  • The program checks if the user provided a city name as a command-line argument. If not, it prompts the user to enter one.
  • After obtaining the city name, the program calls getWeatherData and then hands over the data to be displayed.

This part of the code ties together the CLI aspect of our weather CLI app in Go.

Step 4: Parsing and Displaying the Weather Data

Now, we need a function to neatly format and display the weather data. This function will take the data retrieved from the API and print it to the terminal.

In the same main.go file, add the following function:

// displayWeatherData prints the weather information in a user-friendly format.
func displayWeatherData(weather *WeatherData) {
    fmt.Printf("\nWeather for %s:\n", weather.Name)
    if len(weather.Weather) > 0 {
        fmt.Printf("Description: %s\n", strings.Title(weather.Weather[0].Description))
    }
    fmt.Printf("Temperature: %.2f°C\n", weather.Main.Temp)
    fmt.Printf("Humidity: %d%%\n\n", weather.Main.Humidity)
}

Code Explanation:

  • The function prints the city name, weather description, temperature, and humidity. Using fmt.Printf allows us to format the output neatly.
  • By using strings.Title, the description is nicely capitalized. This ensures that the output is both informative and visually appealing.

This function completes the final step of our weather CLI app in Go – making sure users get clear and useful weather information.

Step 5: Testing and Running the App

Before we wrap up, it’s time to test our application. Here’s how you can run your weather CLI app in Go:

Set your API key as an environment variable:

For Linux/Mac:

export OPENWEATHER_API_KEY=your_api_key_here

For Windows (Command Prompt):

set OPENWEATHER_API_KEY=your_api_key_here

Run the application by providing a city name:

go run main.go London

If you don’t provide a city name, the app will prompt you to enter one.

You should see something like this:

Weather for London:
Description: Clear Sky
Temperature: 15.00°C
Humidity: 67%

Congratulations! You’ve successfully built a weather CLI app in Go. Your application can now fetch and display current weather information based on user input.

Complete Source Code

For your convenience, here is the complete source code for the weather CLI app in Go:

// main.go
package main

import (
    "bufio"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
    "strings"
)

// WeatherData holds the structure of the JSON response from OpenWeatherMap.
type WeatherData struct {
    Name string `json:"name"`
    Main struct {
        Temp     float64 `json:"temp"`
        Humidity int     `json:"humidity"`
    } `json:"main"`
    Weather []struct {
        Description string `json:"description"`
    } `json:"weather"`
}

// getWeatherData fetches weather data for a given city.
func getWeatherData(city string, apiKey string) (*WeatherData, error) {
    // Construct the API URL. We use the metric system for temperature (Celsius).
    url := fmt.Sprintf("http://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=metric", city, apiKey)

    // Make the HTTP request.
    resp, err := http.Get(url)
    if err != nil {
        return nil, fmt.Errorf("failed to make request: %v", err)
    }
    defer resp.Body.Close()

    // Check if the request was successful.
    if resp.StatusCode != http.StatusOK {
        bodyBytes, _ := ioutil.ReadAll(resp.Body)
        return nil, fmt.Errorf("error: received status code %d: %s", resp.StatusCode, string(bodyBytes))
    }

    // Read the response body.
    bodyBytes, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("failed to read response: %v", err)
    }

    // Decode the JSON data into our WeatherData struct.
    var weather WeatherData
    err = json.Unmarshal(bodyBytes, &weather)
    if err != nil {
        return nil, fmt.Errorf("failed to decode JSON: %v", err)
    }

    return &weather, nil
}

// displayWeatherData prints the weather information in a user-friendly format.
func displayWeatherData(weather *WeatherData) {
    fmt.Printf("\nWeather for %s:\n", weather.Name)
    if len(weather.Weather) > 0 {
        fmt.Printf("Description: %s\n", strings.Title(weather.Weather[0].Description))
    }
    fmt.Printf("Temperature: %.2f°C\n", weather.Main.Temp)
    fmt.Printf("Humidity: %d%%\n\n", weather.Main.Humidity)
}

func main() {
    // Read the API key from an environment variable.
    apiKey := os.Getenv("OPENWEATHER_API_KEY")
    if apiKey == "" {
        fmt.Println("Error: Please set the OPENWEATHER_API_KEY environment variable.")
        os.Exit(1)
    }

    // Check if a city name is provided as a command-line argument.
    var city string
    if len(os.Args) > 1 {
        city = strings.Join(os.Args[1:], " ")
    } else {
        // Prompt the user for a city name if no argument is provided.
        fmt.Print("Enter the city name: ")
        reader := bufio.NewReader(os.Stdin)
        input, err := reader.ReadString('\n')
        if err != nil {
            fmt.Println("Error reading input:", err)
            return
        }
        city = strings.TrimSpace(input)
    }

    // Fetch the weather data.
    weather, err := getWeatherData(city, apiKey)
    if err != nil {
        fmt.Println("Error fetching weather data:", err)
        return
    }

    // Display the weather information.
    displayWeatherData(weather)
}

Summary

Building a weather CLI app in Go is not only fun but also a great way to sharpen your programming skills. You learned how to set up a project, make HTTP requests, parse JSON, and build a user-friendly CLI tool. As you continue your journey with Go, consider expanding this project by adding features like:

  • Caching weather data for quick retrieval.
  • Supporting multiple cities in one request.
  • Enhancing error handling and logging.

I hope you enjoyed this tutorial as much as I did creating it. Your next step? Experiment, tweak, and perhaps share your version of the weather CLI app with your network.

Happy coding, and may your weather always be clear!