Building a Weather CLI in Rust (with Reqwest and Serde)

In this mini-project, you will build a simple command-line application that fetches real-time weather data from a public API. You will use reqwest to make HTTP requests, serde to parse the JSON response, and clap to accept city names from the terminal.

This is the kind of CLI I wish I had built earlier while learning Rust. It combines everything we have covered so far: arguments, HTTP requests, JSON parsing, and error handling. The full project fits in under 100 lines and is very practical.

What You Will Learn

  • Accepting city name from the command line
  • Making HTTP requests with reqwest
  • Parsing JSON responses using serde
  • Handling errors gracefully
  • Formatting output for the user

Step 1: Project Setup

In Cargo.toml:

[dependencies]
clap = { version = "4.4", features = ["derive"] }
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

Example:

Cargo.toml

Step 2: Create the Structs

Here is what the JSON response from the API looks like (Open-Meteo or wttr.in):

{
  "current_weather": {
    "temperature": 23.5,
    "windspeed": 15.4
  }
}

Define a struct to match it:

use serde::deserialize;

#[derive(Deserialize)]
struct WeatherResponse {
    current_weather: WeatherData,
}

#[derive(Deserialize)]
struct WeatherData {
    temperature: f64,
    windspeed: f64,
}

Step 3: Accept City as Argument

Use clap to get a city name:

use clap::Parser;

#[derive(Parser)]
struct Args {
    /// Name of the city
    city: String,
}

Step 4: Make the HTTP Request

Use the free Open-Meteo API:

let url = format!(
    "https://api.open-meteo.com/v1/forecast?latitude=28.61&longitude=77.20&current_weather=true"
);
let response = reqwest::get(&url).await?;
let weather: WeatherResponse = response.json().await?;

Note: Replace coordinates with real ones based on the city (hardcoded or lookup).

Step 5: Show the Output

println!(
    "Temperature: {}°C, Wind Speed: {} km/h",
    weather.current_weather.temperature,
    weather.current_weather.windspeed
);

Full Program

use clap::Parser;
use reqwest;
use serde::deserialize;
#[derive(Parser)]
struct Args {
    city: String,
}
#[derive(Deserialize)]
struct WeatherResponse {
    current_weather: WeatherData,
}
#[derive(Deserialize)]
struct WeatherData {
    temperature: f64,
    windspeed: f64,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = Args::parse();
    println!("Fetching weather for: {}", args.city);
    // Hardcoded location for now (New Delhi)
    let url = "https://api.open-meteo.com/v1/forecast?latitude=28.61&longitude=77.20&current_weather=true";
    let response = reqwest::get(url).await?;
    let weather: WeatherResponse = response.json().await?;
    println!(
        "Temperature: {}°C, Wind Speed: {} km/h",
        weather.current_weather.temperature,
        weather.current_weather.windspeed
    );
    Ok(())
}

Output:

Weather CLI Output

Exercise

Try modifying the app to:

  • Look up city coordinates using another API (like geocoding)
  • Handle network errors with custom messages
  • Support saving results to a file

Summary

You just built a working CLI app in Rust that fetches real-time data from the internet. This project tied together everything from clap, reqwest, and serde, and showed how easy it is to build practical tools in Rust.