Dialup is a WebSocket-first, file-based routing framework for Elixir.
Each browser tab gets its own Elixir process (an internal UserSessionProcess), and all
user interactions flow through that process via WebSocket. The server renders HTML and
sends it to the client, where idiomorph
morphs the DOM efficiently.
Setup
Add use Dialup to your Application module:
defmodule MyApp do
use Application
use Dialup,
app_dir: __DIR__ <> "/app",
title: "My App",
lang: "en"
@impl Application
def start(_type, _args) do
children = [{Dialup, app: __MODULE__, port: 4000}]
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
end
endOptions for use Dialup
:app_dir— (required) path to the directory containingpage.ex/layout.exfiles. Typically__DIR__ <> "/app".:title— default<title>value used whenpage_title/1returnsnil. Defaults to"Dialup App".:lang— value for thehtml langattribute. Defaults to"en".:check_origin— WebSocket origin validation.:conn(default) validates against the HTTP host,falsedisables validation, or a list of allowed hosts.:agent_session— whentrue, enablesPOST /_dialup/agent-sessionfor headless MCP sessions. Defaults tofalse; opt in explicitly and protect with auth plugs in production.:secure_cookies— sets theSecureflag ondialup_sessioncookies.:auto(default) enables it when the request is HTTPS (conn.schemeorX-Forwarded-Proto: https);trueorfalseforce the flag on or off.:plugs— list of additionalPlugmodules inserted into the HTTP pipeline (e.g. auth).:auth_accounts— module implementingDialup.Auth.Accountsfor user resolution.:session_store—:memory(default) or:ets. Use:etsto persist session state across process restarts.
Child spec
Dialup implements child_spec/1 and start_link/1, so you can add it directly to a
supervision tree:
children = [{Dialup, app: MyApp, port: 4000}]File-based routing
Files placed under app_dir are automatically compiled into a route table at build time:
app/
├── layout.ex # root layout
├── page.ex # route: /
├── error.ex # error page
└── blog/
├── layout.ex # nested layout for /blog/*
└── [slug]/
└── page.ex # route: /blog/:slug (dynamic)No router.ex registration is required.