DEV Community

Alex Spinov
Alex Spinov

Posted on

Gleam Has a Free API: A Friendly Type-Safe Language for the BEAM and JavaScript

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
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

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")
}
Enter fullscreen mode Exit fullscreen mode

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()
  }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)