Getting Started

Installation

Therapy.jl requires Julia 1.12 (for WasmTarget.jl IR compatibility).

using Pkg
Pkg.add(url="https://github.com/GroupTherapyOrg/Therapy.jl")

Project Structure

A Therapy.jl app uses file-based routing. Each file in routes/ becomes a page. Components in components/ are available to all pages.

my-app/
app.jl               # Entry point
routes/
  index.jl           # → /
  about.jl           # → /about
  examples/
    index.jl         # → /examples
components/
  Counter.jl         # @island component (compiled to WASM)
  Layout.jl          # SSR layout wrapper

SSR Components

Components are plain Julia functions that return HTML elements. They run at build time with full access to Julia packages. No macro needed.

# routes/index.jl — a simple page
() -> begin
    Div(
        H1("Hello, World!"),
        P("This is server-rendered at build time."),
        P("You can use any Julia package here — DataFrames, HTTP, etc.")
    )
end

Adding Interactivity

Use @island to make a component interactive. Island handlers, effects, and memos compile to WebAssembly via WasmTarget.jl. Only islands ship WASM to the browser — everything else is static HTML.

# components/Counter.jl
@island function Counter(; initial::Int = 0)
    count, set_count = create_signal(initial)
    doubled = create_memo(() -> count() * 2)

    return Div(
        Button(:on_click => () -> set_count(count() - 1), "-"),
        Span(count),
        Button(:on_click => () -> set_count(count() + 1), "+"),
        P("doubled: ", doubled)
    )
end

Signals become WASM globals. Handlers become WASM exports. Effects and memos compile via WasmTarget.compile_closure_body(). The browser receives a tiny WASM module (1-12 KB per island).

Browser APIs

Use js() to call browser APIs from WASM. Signal values are interpolated with $1, $2, etc.

# Console logging (re-runs on every signal change)
create_effect(() -> js("console.log('count:', $1)", count()))

# DOM manipulation
js("document.documentElement.classList.toggle('dark')")

# localStorage
js("localStorage.setItem('key', $1)", count())

Running Your App

# Development server with hot reload
julia +1.12 --project=. app.jl dev

# Build static site for deployment
julia +1.12 --project=. app.jl build

# Add --optim to either command to run Binaryen wasm-opt trim + dead-code
# elimination on every island. Slower to build, substantially smaller WASM.
julia +1.12 --project=. app.jl dev --optim
julia +1.12 --project=. app.jl build --optim

The dev server compiles islands on the fly and serves pages with hot reload. The build command generates static HTML + WASM files ready for deployment to GitHub Pages, Netlify, or any static host.