DEV Community

Taiwo Ayomide
Taiwo Ayomide

Posted on

Building Your First Gleam Application: A Weather CLI Tool

Ever wanted to learn a modern programming language that combines the reliability of Erlang with the simplicity of TypeScript? Meet Gleam - a type-safe language that makes building reliable applications a breeze. In this tutorial, we'll build a practical weather CLI tool while learning Gleam's core concepts.
Gleam Logo

What We're Building

We'll create a command-line tool that:

  • Fetches real-time weather data from OpenWeather API
  • Displays formatted weather information
  • Handles errors gracefully
  • Uses Gleam's powerful type system

Prerequisites

Before we start, you'll need:

  • Erlang OTP (24 or later)
  • Gleam (latest version)
  • A text editor
  • Basic command line familiarity

Setting Up Your Environment

Installing Gleam

On macOS:

brew install gleam
Enter fullscreen mode Exit fullscreen mode

On Linux:

# First install Erlang
sudo apt-get install erlang

# Then install Gleam using precompiled binary
# Visit gleam.run for latest installation instructions
Enter fullscreen mode Exit fullscreen mode

On Windows:

  1. First, open PowerShell as Administrator and run:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
Enter fullscreen mode Exit fullscreen mode

Install Scoop package manager:

irm get.scoop.sh | iex
Enter fullscreen mode Exit fullscreen mode

After Scoop is installed, install Erlang first:

scoop install erlang
Enter fullscreen mode Exit fullscreen mode

Then install Gleam:

scoop install gleam
Enter fullscreen mode Exit fullscreen mode

Verify the installation:

gleam --version
Enter fullscreen mode Exit fullscreen mode

Gleam Install

Creating Our Project

gleam new weather_cli
cd weather_cli
Enter fullscreen mode Exit fullscreen mode

This creates our project structure:

weather_cli/
├── src/
│   └── weather_cli.gleam
├── test/
│   └── weather_cli_test.gleam
└── gleam.toml
Enter fullscreen mode Exit fullscreen mode

Part 1: Understanding Gleam Basics

Let's start with some Gleam fundamentals. Create a new file src/basics.gleam:

pub fn main() {
  // Variables are immutable by default
  let message = "Hello, Gleam!"
  io.println(message)

  // Basic types
  let temperature = 23
  let is_sunny = True
  let coordinates = #(51.5074, -0.1278)  // Tuple

  // Pattern matching
  case is_sunny {
    True -> io.println("Wear sunscreen!")
    False -> io.println("Bring an umbrella!")
  }
}
Enter fullscreen mode Exit fullscreen mode

Part 2: Building Our Weather Types

Create src/types.gleam:

pub type Weather {
  Weather(
    temperature: Float,
    description: String,
    humidity: Int,
    wind_speed: Float,
  )
}

pub type WeatherError {
  ApiError(String)
  ParseError(String)
}

pub type WeatherResult =
  Result(Weather, WeatherError)
Enter fullscreen mode Exit fullscreen mode

Part 3: Creating the API Client

First, add dependencies to your gleam.toml:

[dependencies]
gleam_http = "~> 3.0"
gleam_json = "~> 0.5"
Enter fullscreen mode Exit fullscreen mode

Create src/api.gleam:

import gleam/http
import gleam/json
import gleam/result

pub fn fetch_weather(city: String) -> WeatherResult {
  let api_key = "your_api_key"
  let url = "http://api.openweathermap.org/data/2.5/weather?q=" 
    <> city 
    <> "&appid=" 
    <> api_key

  case http.get(url) {
    Ok(response) -> parse_response(response.body)
    Error(error) -> Error(ApiError(error))
  }
}

fn parse_response(json_string: String) -> WeatherResult {
  try json_data = json.decode(json_string)
  try temp = json.get_float(json_data, ["main", "temp"])
  try desc = json.get_string(json_data, ["weather", 0, "description"])
  try humidity = json.get_int(json_data, ["main", "humidity"])
  try wind = json.get_float(json_data, ["wind", "speed"])

  Ok(Weather(
    temperature: kelvin_to_celsius(temp),
    description: desc,
    humidity: humidity,
    wind_speed: wind,
  ))
}

fn kelvin_to_celsius(kelvin: Float) -> Float {
  kelvin -. 273.15
}
Enter fullscreen mode Exit fullscreen mode

Part 4: Creating the CLI Interface

Create src/cli.gleam:

import gleam/io
import gleam/string

pub fn display_weather(result: WeatherResult) {
  case result {
    Ok(weather) -> {
      io.println("\n=== Weather Report ===")
      io.println("Temperature: " <> string.from_float(weather.temperature) <> "°C")
      io.println("Conditions: " <> weather.description)
      io.println("Humidity: " <> string.from_int(weather.humidity) <> "%")
      io.println("Wind Speed: " <> string.from_float(weather.wind_speed) <> " m/s")
    }
    Error(error) -> {
      case error {
        ApiError(msg) -> io.println("API Error: " <> msg)
        ParseError(msg) -> io.println("Parse Error: " <> msg)
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Part 5: Putting It All Together

Update src/weather_cli.gleam:

import gleam/io
import gleam/string
import api
import cli

pub fn main() {
  io.println("Enter city name:")
  let city = io.get_line()

  case string.trim(city) {
    "" -> io.println("City name cannot be empty!")
    city -> {
      let weather = api.fetch_weather(city)
      cli.display_weather(weather)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Running the Application

gleam run
Enter fullscreen mode Exit fullscreen mode

Example usage:

> Enter city name:
London
=== Weather Report ===
Temperature: 18.5°C
Conditions: partly cloudy
Humidity: 72%
Wind Speed: 4.1 m/s
Enter fullscreen mode Exit fullscreen mode

How It Works

Let's break down the key concepts:

  1. Type Safety: Gleam's type system catches errors at compile time
  2. Pattern Matching: We use it extensively for error handling
  3. Result Type: Handles success and error cases elegantly
  4. Immutable Data: Makes our code more predictable

Advanced Features to Try

  1. Add support for multiple cities
  2. Include forecast data
  3. Save favorite locations
  4. Add temperature unit conversion
  5. Include weather alerts

Common Pitfalls and Solutions

  1. API Key Management: Store in environment variables
  2. JSON Parsing: Handle missing fields gracefully
  3. Error Messages: Provide user-friendly error information
  4. Rate Limiting: Add delays between API calls

Why Gleam?

  1. Type Safety: Catch errors before they reach production
  2. Erlang Compatibility: Access to the entire Erlang ecosystem
  3. Modern Syntax: Familiar to JavaScript/TypeScript developers
  4. Great Documentation: Despite being newer, well-documented
  5. Growing Community: Active development and community support

Next Steps

  1. Add error handling for network issues
  2. Implement caching for API responses
  3. Create a configuration file for settings
  4. Add colorful terminal output
  5. Deploy as a standalone binary

Resources

X handle: https://x.com/dev_ayomide
Discord handle: https://discordapp.com/users/1165333488933810336

Top comments (0)