Elixir runs on the Erlang virtual machine, and the thing that makes it special isn't the syntax — it's OTP: a battle-tested set of patterns for building systems that stay up. The core idea is almost subversive: instead of preventing every failure, you let parts crash and have supervisors restart them cleanly. This guide gives you the mental model — processes, GenServers, supervisors — and the project shape Falix expects.
| At a glance | |
|---|---|
| You need | A Falix server running the Elixir application |
| Plan | Any — on free it runs while your session timer has time left, premium runs 24/7 |
| Time | Twenty-five minutes |
| New to the Elixir app? | C#, Deno, Dart, Elixir, and Lua on Falix |
Three ideas, in order
1. Processes. Not OS processes — Erlang processes: incredibly lightweight, isolated, and communicating only by sending messages. You can run hundreds of thousands of them. Each holds its own state and can't corrupt another's, so one falling over doesn't scribble on the rest.
2. GenServer. Writing that message-handling loop by hand is tedious, so OTP gives you GenServer — a "generic server" process that holds state and responds to calls (synchronous) and casts (fire-and-forget). Here's a counter:
defmodule Counter do
use GenServer
# --- public API (runs in the caller) ---
def start_link(initial), do: GenServer.start_link(__MODULE__, initial, name: __MODULE__)
def increment, do: GenServer.cast(__MODULE__, :increment)
def value, do: GenServer.call(__MODULE__, :value)
# --- callbacks (run inside the process) ---
@impl true
def init(initial), do: {:ok, initial}
@impl true
def handle_cast(:increment, count), do: {:noreply, count + 1}
@impl true
def handle_call(:value, _from, count), do: {:reply, count, count}
end
The state (count) lives inside the process. increment sends a message and returns instantly; value sends a message and waits for the reply. No locks, no shared memory — just messages.
3. Supervisors. A supervisor is a process whose only job is to start child processes and restart them when they crash. This is the "let it crash" philosophy in practice: your GenServer doesn't need defensive code for every weird state, because if it dies, the supervisor gives you a fresh, known-good one.
defmodule MyApp.Application do
use Application
@impl true
def start(_type, _args) do
children = [
{Counter, 0}
]
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
end
end
:one_for_one means "if a child dies, restart just that child". Nest supervisors inside supervisors and you get a supervision tree — the structure that keeps a whole application alive part by part.
🎯 Good to know: This is why Elixir suits always-up workloads — a Discord bot, a background job runner, a stateful service. A transient failure restarts a small subtree in milliseconds instead of taking the process down. It's fault tolerance you get by structuring the app, not by catching every error.
What Falix runs — and why the project shape matters
The Elixir application starts your code with mix deps.get followed by mix run --no-halt. Two things follow from that:
- You need a real Mix project — a
mix.exsat the file root. A bare.exsscript won't do;mix runneeds the project. --no-haltkeeps the VM alive after your app boots, so a supervision tree that's finished starting doesn't immediately exit. This is exactly what a long-running OTP app wants.
To make mix run --no-halt actually start your supervision tree, wire the application module into mix.exs:
def application do
[
extra_applications: [:logger],
mod: {MyApp.Application, []}
]
end
Now booting the project starts MyApp.Application.start/2, which starts your supervisor, which starts your GenServers — and --no-halt keeps it all running.
⚠️ Heads up: There's no Packages page for the Elixir application. Declare dependencies in the
depslist inmix.exs;mix deps.getfetches them on every start. A reinstall wipes the fetcheddepsand_buildfolders, but the next start'smix deps.getrebuilds them — guard your own source and data, not those.
Serving web traffic
If your app serves HTTP (a Phoenix endpoint, or a plain Plug/Cowboy server), the universal Falix rules apply: read the port from System.get_env("SERVER_PORT") and bind 0.0.0.0. See Your first web app. A pure worker or bot needs no port at all.
Everything beyond this mental model is standard Elixir and OTP — the official documentation at elixir-lang.org covers GenServer, Supervisor, and the rest in depth.