Gleam is a statically typed functional language that compiles to both Erlang (BEAM) and JavaScript — giving you type safety, friendly errors, and access to the most battle-tested concurrency platform in existence.
Why Gleam Matters
Erlang and Elixir power WhatsApp (2B users, 50 engineers), Discord, and telecom systems with 99.9999999% uptime. But they are dynamically typed. Gleam brings static types to this ecosystem without sacrificing any of it.
What you get for free:
- Static types with full type inference (rarely need type annotations)
- Compiles to Erlang AND JavaScript — same code, two platforms
- Use ANY Erlang/Elixir library (OTP, Phoenix, Ecto — all compatible)
- Use ANY JavaScript/TypeScript library when targeting JS
- Friendly compiler errors (inspired by Elm and Rust)
- No null, no undefined, no exceptions — Result types everywhere
- Fault-tolerant OTP processes out of the box
Quick Start
# Install
curl -fsSL https://gleam.run/install | sh
# Create project
gleam new my_app
cd my_app
# Run
gleam run
# Test
gleam test
# Build for JavaScript
gleam build --target javascript
The Basics
import gleam/io
import gleam/int
import gleam/list
import gleam/result
pub fn main() {
// Type inference — no annotations needed
let name = "World"
io.println("Hello, " <> name <> "!")
// Pattern matching
let numbers = [1, 2, 3, 4, 5]
let doubled = list.map(numbers, fn(n) { n * 2 })
io.debug(doubled) // [2, 4, 6, 8, 10]
// Pipe operator for readable chains
[1, 2, 3, 4, 5]
|> list.filter(fn(n) { n > 2 })
|> list.map(fn(n) { n * 10 })
|> io.debug // [30, 40, 50]
}
Result Types (No Exceptions!)
import gleam/result
pub type AppError {
NotFound
DatabaseError(String)
ValidationError(String)
}
pub fn find_user(id: Int) -> Result(User, AppError) {
case id > 0 {
True -> Ok(User(id: id, name: "Alice"))
False -> Error(ValidationError("ID must be positive"))
}
}
pub fn get_user_name(id: Int) -> String {
find_user(id)
|> result.map(fn(user) { user.name })
|> result.unwrap("Unknown")
}
Web Server (Wisp Framework)
import wisp.{type Request, type Response}
import gleam/http
pub fn handle_request(req: Request) -> Response {
case req.method, wisp.path_segments(req) {
http.Get, [] -> wisp.ok() |> wisp.string_body("Welcome to Gleam!")
http.Get, ["users", id] -> get_user_handler(id)
http.Post, ["users"] -> create_user_handler(req)
_, _ -> wisp.not_found()
}
}
fn get_user_handler(id: String) -> Response {
case int.parse(id) {
Ok(user_id) -> {
let user = find_user(user_id)
wisp.ok() |> wisp.json_body(user_to_json(user))
}
Error(_) -> wisp.bad_request()
}
}
OTP Actors (Erlang Processes)
import gleam/otp/actor
import gleam/erlang/process
pub type Message {
Increment
GetCount(process.Subject(Int))
}
fn handle_message(message: Message, count: Int) -> actor.Next(Message, Int) {
case message {
Increment -> actor.continue(count + 1)
GetCount(reply_to) -> {
process.send(reply_to, count)
actor.continue(count)
}
}
}
pub fn start_counter() {
let assert Ok(counter) = actor.start(0, handle_message)
process.send(counter, Increment)
process.send(counter, Increment)
// Counter is now 2, running in its own lightweight process
}
Useful Links
Building scalable data systems? Check out my developer tools on Apify for ready-made web scrapers, or email spinov001@gmail.com for custom solutions.
Top comments (0)