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.
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
On Linux:
# First install Erlang
sudo apt-get install erlang
# Then install Gleam using precompiled binary
# Visit gleam.run for latest installation instructions
On Windows:
- First, open PowerShell as Administrator and run:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
Install Scoop package manager:
irm get.scoop.sh | iex
After Scoop is installed, install Erlang first:
scoop install erlang
Then install Gleam:
scoop install gleam
Verify the installation:
gleam --version
Creating Our Project
gleam new weather_cli
cd weather_cli
This creates our project structure:
weather_cli/
├── src/
│ └── weather_cli.gleam
├── test/
│ └── weather_cli_test.gleam
└── gleam.toml
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!")
}
}
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)
Part 3: Creating the API Client
First, add dependencies to your gleam.toml
:
[dependencies]
gleam_http = "~> 3.0"
gleam_json = "~> 0.5"
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
}
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)
}
}
}
}
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)
}
}
}
Running the Application
gleam run
Example usage:
> Enter city name:
London
=== Weather Report ===
Temperature: 18.5°C
Conditions: partly cloudy
Humidity: 72%
Wind Speed: 4.1 m/s
How It Works
Let's break down the key concepts:
- Type Safety: Gleam's type system catches errors at compile time
- Pattern Matching: We use it extensively for error handling
- Result Type: Handles success and error cases elegantly
- Immutable Data: Makes our code more predictable
Advanced Features to Try
- Add support for multiple cities
- Include forecast data
- Save favorite locations
- Add temperature unit conversion
- Include weather alerts
Common Pitfalls and Solutions
- API Key Management: Store in environment variables
- JSON Parsing: Handle missing fields gracefully
- Error Messages: Provide user-friendly error information
- Rate Limiting: Add delays between API calls
Why Gleam?
- Type Safety: Catch errors before they reach production
- Erlang Compatibility: Access to the entire Erlang ecosystem
- Modern Syntax: Familiar to JavaScript/TypeScript developers
- Great Documentation: Despite being newer, well-documented
- Growing Community: Active development and community support
Next Steps
- Add error handling for network issues
- Implement caching for API responses
- Create a configuration file for settings
- Add colorful terminal output
- Deploy as a standalone binary
Resources
X handle: https://x.com/dev_ayomide
Discord handle: https://discordapp.com/users/1165333488933810336
Top comments (0)