DEV Community

Alex Spinov
Alex Spinov

Posted on

Gleam Has a Free Type-Safe Language That Runs on Erlang and JavaScript

Gleam compiles to both Erlang BEAM and JavaScript. You get Erlang's legendary fault tolerance with a modern, friendly type system.

Why Gleam

  • Type-safe — catches bugs at compile time (unlike Elixir/Erlang)
  • Runs on BEAM — battle-tested runtime (WhatsApp, Discord)
  • Compiles to JS — share code between server and browser
  • No null — uses Result type instead
  • Immutable — all data is immutable by default
  • Beautiful syntax — inspired by Rust, Elm, and Go

Hello World

import gleam/io

pub fn main() {
  io.println("Hello, Gleam!")
}
Enter fullscreen mode Exit fullscreen mode
gleam new my_app
cd my_app && gleam run
Enter fullscreen mode Exit fullscreen mode

Type System

// Custom types (like Rust enums)
pub type Shape {
  Circle(radius: Float)
  Rectangle(width: Float, height: Float)
  Triangle(base: Float, height: Float)
}

pub fn area(shape: Shape) -> Float {
  case shape {
    Circle(r) -> 3.14159 *. r *. r
    Rectangle(w, h) -> w *. h
    Triangle(b, h) -> 0.5 *. b *. h
  }
}
// Compiler ensures ALL cases are handled
Enter fullscreen mode Exit fullscreen mode

Error Handling (No Exceptions)

import gleam/result

pub type AppError {
  NotFound
  Unauthorized
  DatabaseError(String)
}

pub fn get_user(id: Int) -> Result(User, AppError) {
  case db.find_user(id) {
    Ok(user) -> Ok(user)
    Error(_) -> Error(NotFound)
  }
}

// Chain operations
pub fn get_user_email(id: Int) -> Result(String, AppError) {
  use user <- result.try(get_user(id))
  use profile <- result.try(get_profile(user))
  Ok(profile.email)
}
Enter fullscreen mode Exit fullscreen mode

The use keyword makes error handling clean — no nested case statements.

Concurrency (OTP)

import gleam/erlang/process
import gleam/otp/actor

pub type Message {
  Increment
  GetCount(process.Subject(Int))
}

pub fn counter() {
  actor.start(0, fn(message, count) {
    case message {
      Increment -> actor.continue(count + 1)
      GetCount(reply_to) -> {
        process.send(reply_to, count)
        actor.continue(count)
      }
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

Actors, supervisors, fault tolerance — all the OTP power.

Web Server (Wisp)

import wisp
import gleam/http

pub fn handle_request(req: wisp.Request) -> wisp.Response {
  case wisp.path_segments(req) {
    [] -> wisp.ok() |> wisp.string_body("Hello!")
    ["users", id] -> get_user_handler(req, id)
    _ -> wisp.not_found()
  }
}

fn get_user_handler(req: wisp.Request, id: String) -> wisp.Response {
  case req.method {
    http.Get -> {
      // Fetch and return user
      wisp.ok() |> wisp.json_body(user_json)
    }
    _ -> wisp.method_not_allowed([http.Get])
  }
}
Enter fullscreen mode Exit fullscreen mode

Compile to JavaScript

# Target JavaScript instead of Erlang
gleam build --target javascript
gleam run --target javascript
Enter fullscreen mode Exit fullscreen mode

Share types and logic between your BEAM backend and JS frontend.

Why Developers Love Gleam

  1. Friendly compiler — error messages are helpful, not cryptic
  2. Fast compilation — written in Rust
  3. Great tooling — formatter, LSP, package manager built-in
  4. Interop — call any Erlang/Elixir or JavaScript library
  5. Growing ecosystem — 1,500+ packages on Hex

Interested in robust backend systems? I build developer tools and data infrastructure. Email spinov001@gmail.com or check my Apify tools.

Top comments (0)