Nex (nex_core v0.4.3)

Copy Markdown

Nex - A minimalist Elixir web framework powered by HTMX.

Unified Interface

All Nex modules use the same simple statement:

defmodule MyApp.Pages.Index do
  use Nex  # ← One statement for everything

  def render(assigns) do
    ~H"""
    <h1>Hello, Nex!</h1>
    """
  end
end

Nex automatically detects the module type based on its path:

  • *.Api.* → API module (no imports needed)
  • *.Pages.* → Page module (imports HEEx + CSRF)
  • *.Components.* → Component module (imports HEEx + CSRF)
  • *.Layouts → Layout module (imports HEEx + CSRF)

Examples

Page Module

defmodule MyApp.Pages.Users.Index do
  use Nex

  def render(assigns) do
    ~H"""
    <div>Users List</div>
    """
  end
end

API Module

defmodule MyApp.Api.Users.Index do
  use Nex

  def get(req) do
    Nex.json(%{data: users})
  end
end

Component

defmodule MyApp.Components.Users.Card do
  use Nex

  def render(assigns) do
    ~H"""
    <div class="card">{@user.name}</div>
    """
  end
end

Layout Module

defmodule MyApp.Layouts do
  use Nex

  def render(assigns) do
    ~H"""
    <!DOCTYPE html>
    <html>
      <body>{raw(@inner_content)}</body>
    </html>
    """
  end
end

Summary

Functions

Unified macro for all Nex modules.

Constructs an HTML response.

Constructs a JSON response.

Constructs a redirect response.

Constructs a response with custom status code.

Constructs a Server-Sent Events (SSE) streaming response.

Constructs a text response.

Types

json_data()

@type json_data() :: map() | list() | String.t()

response_options()

@type response_options() :: [
  status: non_neg_integer(),
  headers: %{required(String.t()) => String.t()}
]

Functions

__using__(opts)

(macro)

Unified macro for all Nex modules.

Automatically detects module type based on path and imports appropriate functions.

html(html, opts \\ [])

@spec html(String.t(), response_options()) :: Nex.Response.t()

Constructs an HTML response.

Commonly used in HTMX scenarios to return HTML fragments.

Examples

def get(req) do
  Nex.html("""
  <div class="user-card">
    <h2>User Profile</h2>
  </div>
  """)
end

Options

  • :status - HTTP status code (default: 200)
  • :headers - Additional headers (default: %{})

json(data, opts \\ [])

@spec json(json_data(), response_options()) :: Nex.Response.t()

Constructs a JSON response.

Options

  • :status - HTTP status code (default: 200)
  • :headers - Additional headers (default: %{})

redirect(to, opts \\ [])

@spec redirect(String.t(), response_options()) :: Nex.Response.t()

Constructs a redirect response.

status(code, body \\ "")

@spec status(non_neg_integer(), String.t()) :: Nex.Response.t()

Constructs a response with custom status code.

stream(callback)

@spec stream((%{event: String.t(), data: term()} | term() -> term())) ::
  Nex.Response.t()

Constructs a Server-Sent Events (SSE) streaming response.

Similar to Python's StreamingResponse with generators and Next.js's ReadableStream.

Example

Nex.stream(fn send ->
  send.("Thinking...")
  send.("Processing...")
  send.("Done!")
end)

Data Formats

The send function accepts:

  • String: send.("Hello")data: Hello\n\n
  • Map/List: send.(%{user: "Alice"})data: {"user":"Alice"}\n\n
  • Event with data: send.(%{event: "message", data: "Hello"})event: message\ndata: Hello\n\n

AI Streaming Example

def post(req) do
  message = req.body["message"]

  Nex.stream(fn send ->
    # Stream from OpenAI
    accumulated = ""

    Req.post!("https://api.openai.com/v1/chat/completions",
      json: %{model: "gpt-3.5-turbo", messages: [...], stream: true},
      into: fn {:data, chunk}, acc ->
        case parse_chunk(chunk) do
          {:ok, content} ->
            new_acc = acc <> content
            send.(new_acc)
            {:cont, new_acc}
          _ -> {:cont, acc}
        end
      end
    )
  end)
end

text(text, opts \\ [])

@spec text(String.t(), response_options()) :: Nex.Response.t()

Constructs a text response.