zhenfeng-zhu

zhenfeng-zhu

Nex - 0.3.x Design Philosophy: The Evolution of Minimalism

Nex - A minimalist web framework for indie hackers and startups

A Note Before We Begin

This document records my thoughts during the development of Nex.

Special thanks to the friends from Reddit (r/elixir) and Elixir Forum. Your feedback, questions, suggestions, and even criticism have helped me gradually understand what Nex should be. Without you, there would be no 0.3.x refactor.

I also want to thank the teams behind Next.js, Phoenix, and HTMX. Your work has been an endless source of inspiration.


TL;DR

Simply put, 0.2.x was too complex: 4 different use statements, confusing API parameters, and unconventional directory naming.

With 0.3.x, I decided to subtract:

  • Only one use Nex.
  • API parameters reduced to just query and body, like Next.js.
  • Directories renamed back to the familiar components/.
  • Streaming responses became a simple function Nex.stream/1.

The result: less code, fewer concepts, and a smoother development experience.


The Core Problem: Finding Our Identity

While developing Nex 0.2.x, I was genuinely conflicted. I kept asking myself: What should Nex actually be? I tried many approaches, referenced many frameworks, but always felt something was missing.

The Dilemma of 0.2.x

Look at this 0.2.x code:

defmodule MyApp.Pages.Index do
  use Nex.Page  # Explicitly declare this is a Page
end

defmodule MyApp.Api.Users do
  use Nex.Api  # Explicitly declare this is an Api
end

defmodule MyApp.Partials.Card do
  use Nex.Partial  # Explicitly declare this is a Partial
end

This design looked rigorous, but it was exhausting to write. Since files are already in the pages/ directory, why should I have to tell the framework again, “This is a Page”?

Learning from Next.js

Later, I revisited Next.js. Its greatest strength is convention over configuration. You don’t need to write any configuration code—just put files in the right place.

This is what Nex should be: let developers focus on business logic and write less boilerplate.


Decision 1: One use Nex for Everything

The Awkwardness Before

In 0.2.x, not only did you have to remember 4 different modules, you also had to understand the differences between them. This was entirely artificial cognitive burden.

The Approach Now

0.3.x uses Elixir’s macro system to automatically infer module types based on file paths.

# 0.3.x - Much cleaner
defmodule MyApp.Pages.Index do
  use Nex
end

defmodule MyApp.Api.Users do
  use Nex
end

While this is a Breaking Change, it makes the code look much cleaner.


Decision 2: Rename partials/ to components/

This was actually a long-overdue correction.

I initially used partials because I was influenced by Rails and thought it had more of a “server-side rendering” flavor. But the reality is, the modern frontend world (React, Vue, Svelte) and Phoenix 1.7+ all use components.

Forcing partials only confused new users and offered no benefits. So we embraced the change and switched back to the familiar components.


Decision 3: Writing REST APIs Is Finally Easy

The Pain Point

Writing APIs in 0.2.x was torture. I had 4 places to put parameters: params, path_params, query_params, body_params. Developers had to think about where each parameter came from, adding cognitive burden.

Every time you wrote code, you had to wonder:

  • “Is this id in the path or in the query?”
  • “Should I use params or query_params?”
  • “What’s the difference between body_params and params?”

Learning from Next.js

I looked at how Next.js does it. They offer only two options, yet cover all scenarios:

  1. req.query: Handles all GET request parameters (whether in the path or after the ?)
  2. req.body: Handles all POST/PUT data

This is brilliantly simple. Developers only care about “Do I need to fetch data from the URL (Query)” or “Do I need to fetch data from the body (Body)”.

So in 0.3.x, we do the same. The framework automatically unifies path parameters (like /users/:id) and query parameters (like ?page=1) into req.query:

def get(req) do
  # Both path parameter :id and query parameter ?page are here
  id = req.query["id"]
  page = req.query["page"]
end

def post(req) do
  # All submitted data is here
  user = req.body["user"]
end

This “no-brainer” experience is what a good framework should provide.


Decision 4: Streaming Responses Are First-Class Citizens

In 2025, if a web framework requires effort to support SSE (Server-Sent Events), it’s definitely outdated.

In 0.2.x, you needed use Nex.SSE and had to follow specific function signatures. But in the age of AI applications, streaming responses should be a standard capability available everywhere.

Now you can return a stream from anywhere:

def get(req) do
  Nex.stream(fn send ->
    send.("Hello")
    Process.sleep(1000)
    send.("World")
  end)
end

Simple and direct, no magic tricks.


Summary: Finding Our Identity

When developing 0.1.x and 0.2.x, I was a bit greedy. I wanted to combine Phoenix’s power, Next.js’s simplicity, and Rails’s classics all together. The result was a “Frankenstein” framework.

By 0.3.x, I finally figured it out: Nex should not try to be another Phoenix.

The Elixir community already has Phoenix, a perfect industrial-grade framework. Nex’s mission should be to provide a simple and lightweight alternative (core code < 500 lines). It should be like Next.js, enabling developers (especially indie developers) to rapidly build usable products.

This is the entire point of Nex 0.3.x: Embrace simplicity, return to developer intuition.


Future Outlook

This refactor is not just about API changes, but a shift in design philosophy

Next Steps

  1. Exploring Datastar Integration

    • Monitor Datastar’s development as a Hypermedia framework
    • Evaluate whether it can provide finer-grained state updates than HTMX
    • Stay open to emerging technologies, but prioritize core DX improvements
  2. Ultimate Developer Experience (DX)

    • Make the framework “better to use”, not “more features”
    • More comprehensive documentation and real-world examples

Core Values Remain Unchanged

No matter how Nex evolves, these core principles won’t change:

  • :white_check_mark: Minimal: Least code, maximum productivity
  • :white_check_mark: Modern: Aligned with modern framework best practices
  • :white_check_mark: Practical: Solving real-world problems

A Word to Developers

About Breaking Changes

Nex is currently in an early, fast-moving iteration phase. To pursue the ultimate developer experience, breaking changes may happen at any time.

But I promise: I will document the thinking and reasoning behind every refactor in detail.

It’s not about change for change’s sake, but about exploring the best development experience for Elixir. I hope that by sharing these thoughts, we can communicate, learn together, and collectively refine a truly great framework.

Rather than giving a cold “upgrade guide”, I prefer to tell you “why I’m doing this”.

My Promise to Users

  1. Minimal API: Only need to learn use Nex and a few response functions
  2. Familiar Developer Experience: If you know Next.js, Nex’s API design will feel natural
  3. Comprehensive Documentation: Complete tutorials from beginner to advanced
  4. Active Community: We will continue to improve and support

Nex 0.3.x - Minimal, Modern, Practical Elixir Web Framework

Let’s build better web applications together! :rocket:

Nex Github Repo: GitHub - gofenix/nex: Nex – A minimalist web framework for indie hackers and startups · GitHub

Quick Start:

# 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.dev

Most Liked

derek-zhou

derek-zhou

I love minimalism, but minimalism never sells. As a indie hacker, why would I adopt a simple and lightweight framework (core code < 500 lines) when I can write the 500 lines myself and have it exactly my way? I would suggest you find a niche and optimize for it.

Minimalism in code size is nice, but only to the author himself. Minimalism on the API surface area will be more valuable to the users.

derek-zhou

derek-zhou

I actually think it is a great mentality to design a framework. Your framework has to serve someone, and that someone would have to be yourself in the beginning. (Dogfooding). My point was: evangelizing should come after you have dogfood’ed yourself for a few diverse projects, so you can prove that you didn’t design yourself into a corner, or you have rescued yourself from the original corner. At that stage, you most likely cannot claim the doctrine of minimalism anymore.

tfwright

tfwright

Maybe I didn’t sufficiently highlight the definition I think is broadly accepted:

I’m not sure the claim that it’s broadly accepted is really that debatable tbh, but is there a reason it doesn’t seem clear/useful?

Where Next?

Popular in News & Updates Top

bartblast
Hologram v0.8.0 is out! This release brings JavaScript interoperability - the most requested feature since the project’s inception. You c...
New
hugobarauna
This post announces the Livebook desktop app, a way to install Livebook on your machine without the requirement to have Elixir installed ...
New
zachdaniel
Had a great time at Jax.Ex! Gave a talk called “Model your Domain, Derive the Rest”. Thanks to the folks at HashRocket for having me!
New
DominikWolek
We have released Membrane Core v0.12.1 :tada::tada::tada: This release will help smooth the transition to the upcoming v1.0.0. See the r...
New
zachdaniel
Hey folks! I’ve released the first version of our new AI policy. Accepting it is now part of our issue and pull request templates. Feel ...
New
jjcarstens
The Raspberry Pi is a very popular hardware platform for Nerves users and the official systems are getting an upgrade! :tada: :beers: Wh...
New
fhunleth
We’ve released Nerves v1.3.0 with support for Elixir 1.7 and Distillery 2.0! We encountered a few bumps over the past week, but it’s look...
New
DominikWolek
Hi everyone! After about 3 months of really hard work, we have released the very first version of Jellyfish Media Server! The best star...
New
mobileoverlord
Action Advised: NervesHub Sunset In 2018, NervesHub was launched to deliver first-class support for hardware deployments directly from th...
New
zachdaniel
Hey folks! I’ll be teasing some interesting bits going into Ash 3.0 while I work on it, and this is post #1! You can follow along with t...
New

Other popular topics Top

hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a &gt; b) do {:ok, "a"} end if (a &lt; b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41989 114
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New

We're in Beta

About us Mission Statement