The simplest way to build HTMX apps in Elixir
Nex is a minimalist web framework for indie hackers, startups, and teams who want to ship real products fast without enterprise complexity. Build modern web applications with server-side rendering, zero JavaScript complexity, and instant hot reloading.
Nex is designed for building real applications that work in production:
- 🚀 Rapid development - Ship features fast, not prototypes
- 🎯 Indie hackers & startups - Build profitable products without enterprise complexity
- 📊 Internal tools & dashboards - Admin panels, data dashboards, operational tools
- 🔄 Real-time applications - Live dashboards, chat apps, streaming data with SSE
- 🌐 Server-side rendering done right - Modern web apps without JavaScript overhead
Nex is not:
- ❌ A Phoenix competitor (use Phoenix for enterprise apps)
- ❌ A full-stack framework (no built-in ORM, auth, or asset pipeline)
- ❌ For complex SPAs (use LiveView or React for that)
# Install the project generator
mix archive.install hex nex_new
# Create a new project
mix nex.new my_app
cd my_app
# Start development server
mix nex.devVisit http://localhost:4000 to see your app running.
- Locality of Behavior (LoB) - UI and logic in one file, perfect for AI agents.
- Unified Interface - Single
use Nexfor Pages, APIs, and Components. - Zero-Config Routing - Paths are routes, reducing AI hallucinations.
- File-based Routing - Drop a file in
src/pages/, get a route automatically. - 🔀 Dynamic Routes - Support for
[id],[slug],[...path]patterns. - 🎯 Convention over Configuration - No route configuration needed.
- HTMX-first - Built-in HTMX integration, SSR without JavaScript complexity.
- 🛡️ Built-in Security - Automatic CSRF validation on all state-changing requests.
- 📝 HTML Templates - Type-safe HEEx templates.
- 🌊 SSE Streaming - Native
Nex.stream/1API for AI responses and live updates. - 📡 JSON APIs - Clean API routes with Next.js-aligned
reqobject. - Integrated State - Page-scoped state management with
Nex.Store.
my_app/
├── src/
│ ├── pages/ # Page modules (auto-routed)
│ │ ├── index.ex # GET /
│ │ └── [id].ex # GET /:id
│ ├── api/ # API endpoints (JSON)
│ │ └── todos/
│ │ └── index.ex # GET/POST /api/todos
│ ├── components/ # Reusable components
│ └── layouts.ex # Layout template
├── mix.exs
└── Dockerfile # Production deployment
defmodule MyApp.Pages.Index do
use Nex
def mount(_params) do
%{count: Nex.Store.get(:count, 0)}
end
def render(assigns) do
~H"""
<div class="text-center py-12">
<h1 class="text-4xl font-bold mb-4">Counter</h1>
<div id="counter-display" class="text-6xl font-bold mb-8">{@count}</div>
<div class="space-x-2">
<button hx-post="/decrement" hx-target="#counter-display" hx-swap="outerHTML">-</button>
<button hx-post="/reset" hx-target="#counter-display" hx-swap="outerHTML">Reset</button>
<button hx-post="/increment" hx-target="#counter-display" hx-swap="outerHTML">+</button>
</div>
</div>
"""
end
def increment(_params) do
count = Nex.Store.update(:count, 0, &(&1 + 1))
~H"<div id="counter-display" class="text-6xl font-bold mb-8">{count}</div>"
end
def decrement(_params) do
count = Nex.Store.update(:count, 0, &(&1 - 1))
~H"<div id="counter-display" class="text-6xl font-bold mb-8">{count}</div>"
end
def reset(_params) do
Nex.Store.put(:count, 0)
~H"<div id="counter-display" class="text-6xl font-bold mb-8">0</div>"
end
enddefmodule MyApp.Pages.Todos do
use Nex
def mount(_params) do
%{todos: fetch_todos()}
end
def render(assigns) do
~H"""
<h1>My Todos</h1>
<form hx-post="/add_todo" hx-target="#todos" hx-swap="beforeend">
<input type="text" name="title" required />
<button>Add</button>
</form>
<ul id="todos">
<li :for={todo <- @todos}>{todo.title}</li>
</ul>
"""
end
# HTMX POST handler
def add_todo(%{"title" => title}) do
todo = create_todo(title)
~H"<li>{@todo.title}</li>"
end
enddefmodule MyApp.Api.Chat.Stream do
use Nex
def get(%{query: %{"message" => msg}}) do
Nex.stream(fn send ->
# Stream response character by character
msg
|> String.graphemes()
|> Enum.each(fn char ->
send.(%{event: "message", data: char})
Process.sleep(50)
end)
end)
end
enddefmodule MyApp.Api.Todos.Index do
use Nex
def get(_req) do
Nex.json(%{data: fetch_todos()})
end
def post(req) do
title = req.body["title"]
todo = create_todo(title)
Nex.json(%{data: todo}, status: 201)
end
endEvery Nex project includes a production-ready Dockerfile:
docker build -t my_app .
docker run -p 4000:4000 my_appDeploy to any platform that supports Elixir:
- Railway - Easiest option, auto-deploy from Git
- Fly.io - Global deployment with edge computing
- Render - Simple and straightforward
- Traditional VPS - Full control with Elixir installed
Check out the examples/ directory for complete working applications:
- chatbot - AI chat with streaming responses using SSE
- chatbot_sse - Real-time streaming with HTMX SSE extension
- todos - Classic todo app with HTMX interactions
- guestbook - Simple guestbook with persistence
- dynamic_routes - Comprehensive showcase of all routing patterns
- Official Documentation
- GitHub Repository
- Hex Package: nex_core
- Hex Package: nex_new
- HexDocs: nex_core
- HexDocs: nex_new
MIT