DEV Community

Alex Spinov
Alex Spinov

Posted on

Gleam Has a Free API: Type-Safe Erlang/JS That's Actually Fun to Write

Gleam is a type-safe functional language that compiles to Erlang (BEAM) and JavaScript. It gives you Erlang's legendary fault tolerance with modern developer experience — great error messages, no null, no exceptions.

Why Gleam?

  • Type-safe — catches errors at compile time, not production
  • Dual target — compile to Erlang (backend) or JavaScript (frontend)
  • BEAM ecosystem — use any Erlang/Elixir library
  • No null, no exceptions — Result types instead
  • Great DX — fast compiler, helpful errors, LSP

Install

# macOS
brew install gleam

# Linux
curl -sSfL https://gleam.run/install | sh

# Or via asdf
asdf plugin add gleam
asdf install gleam latest
Enter fullscreen mode Exit fullscreen mode

Quick Start

gleam new my_app
cd my_app
gleam run
Enter fullscreen mode Exit fullscreen mode

Basic Gleam

// src/my_app.gleam
import gleam/io
import gleam/string
import gleam/list
import gleam/int

pub fn main() {
  // Variables are immutable
  let name = "World"
  io.println("Hello, " <> name <> "!")

  // Pattern matching
  let result = divide(10, 3)
  case result {
    Ok(value) -> io.println("Result: " <> int.to_string(value))
    Error(msg) -> io.println("Error: " <> msg)
  }

  // Pipe operator
  [1, 2, 3, 4, 5]
  |> list.map(fn(x) { x * 2 })
  |> list.filter(fn(x) { x > 4 })
  |> list.each(fn(x) { io.println(int.to_string(x)) })
}

fn divide(a: Int, b: Int) -> Result(Int, String) {
  case b {
    0 -> Error("Division by zero")
    _ -> Ok(a / b)
  }
}
Enter fullscreen mode Exit fullscreen mode

Web Server (Wisp Framework)

// gleam.toml — add dependencies
// gleam add wisp mist gleam_http gleam_json

import gleam/http/request.{type Request}
import gleam/http/response.{type Response}
import gleam/json
import mist
import wisp.{type Request as WispRequest, type Response as WispResponse}

pub fn main() {
  let assert Ok(_) =
    wisp.mist_handler(handle_request, "secret")
    |> mist.new
    |> mist.port(3000)
    |> mist.start_http

  process.sleep_forever()
}

fn handle_request(req: WispRequest) -> WispResponse {
  case wisp.path_segments(req) {
    [] -> wisp.ok() |> wisp.string_body("Welcome to Gleam!")
    ["api", "users"] -> get_users(req)
    ["api", "users", id] -> get_user(req, id)
    _ -> wisp.not_found()
  }
}

fn get_users(_req: WispRequest) -> WispResponse {
  let users = json.array([
    json.object([("id", json.string("1")), ("name", json.string("Alice"))]),
    json.object([("id", json.string("2")), ("name", json.string("Bob"))]),
  ])
  wisp.ok()
  |> wisp.json_body(json.to_string_tree(users))
}
Enter fullscreen mode Exit fullscreen mode

Custom Types (ADTs)

// No null — use Option
pub type User {
  User(id: String, name: String, email: String, role: Role)
}

pub type Role {
  Admin
  Editor
  Viewer
}

fn describe_role(role: Role) -> String {
  case role {
    Admin -> "Full access"
    Editor -> "Can edit content"
    Viewer -> "Read only"
  }
}

// The compiler ensures ALL cases are handled!
Enter fullscreen mode Exit fullscreen mode

Gleam for JavaScript

# Compile to JS instead of Erlang
gleam build --target javascript
gleam run --target javascript
Enter fullscreen mode Exit fullscreen mode

Key Features

Feature Details
Type system Static, no null, Result types
Targets Erlang (BEAM) + JavaScript
Concurrency OTP actors (via Erlang)
Package manager Hex (shared with Erlang/Elixir)
Interop Call Erlang/Elixir/JS directly
Compiler Fast, helpful error messages

Resources


Building reliable systems? I create custom data tools and integrations. Check my Apify actors or email spinov001@gmail.com.

Top comments (0)