taro

taro

Mar - Simple Web in Elixir

I took lessons from the last discussion and cobbled together an example as a proof-of-concept.

Mar demonstrates a Flask-like web dev interface powered by Plug on Bandit.
use Mar immediately makes a module a route. The user modules don’t need to report to an entry-point plug in the app. Library handles routing.
Normal defs defines the actions to the requests. So you can compose them Elixir way. Router matches them by their names and the HTTP methods.

defmodule MyApp do
  use Mar

  def get(), do: "Hello, world!"
end

path can be set. Default is "/" otherwise.
params declares allowed parameters alongside path parameters with :. Later it takes matching keys from conn.params and puts it in the actions.
Actions can return a string, a map for JSON, or a tuple being {status, headers, body}.

defmodule MyApp do
  use Mar, path: "/post/:id", params: [:comment]

  def get(%{id: id}) do
    "You are reading #{id}"
  end

  def post(%{id: id, comment: comment}) do
    %{
      id: id,
      comment: comment
    }
  end

  def delete(%{id: _id}) do
    {301, [location: "/"], nil}
  end
end

Routes can interact with the library through Mar.Route protocol.

defmodule MyApp do
  use Mar

  def get(), do: "Hello, world!"

  defimpl Mar.Route do
    # Mar.Route.MyApp
    def before_action(route) do
      IO.inspect(route.conn.resp_body)
      # => nil
      route
    end

    def after_action(route) do
      IO.inspect(route.conn.resp_body)
      # => "Hello, world!"
      route
    end
  end
end

Intention

While there are many possible approaches to helping adoption and facilitating learning, the challenge I tackle here is to nicely encapsulate Plug and reduce cognitive load for the users. @taro lacks the technical capability for something production-ready, this project waxes on top of Bandit with an escape hatch in an attempt to help you envision a light and intuitive web framework for Elixir.

The insight and guidance from the community is much appreciated:

Design

This library relies on protocol consolidation to handle routes. use Mar injects a default defimpl of Mar.Route protocol. It lists up the user modules. At the same time, defstruct saves the path as a default struct value. Then the list of implementation maps to structs, which has information for path-matching.

case Mar.Route.__protocol__(:impls) do
  {:consolidated, modules} -> Enum.map(modules, &struct(&1))
  :not_consolidated -> []
end
# [ 
#  %MyApp{ path: "/", ...}, 
#  %MyApp.Post{ path: "/post/:id/", ...}, ,
#  ...
# ]

Mar.Router leaves escape hatches open with the Mar.Route protocol. The user modules redefine the functions with defimpl to access them.

# Mar.Router
def call(conn, _options) do
  # Match routes, load params
  route = Mar.Route.before_action(route)
  # Apply action
  route = Mar.Route.after_action(route)
  # Send response
end

Atom keys are preferred over string keys for the sake of nicer syntax. That’s also why params need to be declared so the library can prevent dynamic atom creation.

What do you think? I’m hoping to hear from you! :smile:

Reference

Most Liked

josevalim

josevalim

Creator of Elixir

Hi @taro!

Thank you for exploring new directions here. If your goal is to have something smaller, may I suggest something that builds on top of functions rather than modules? Modules impose more boilerplate than functions and relying on protocol consolidation means you can’t use Mar efficiently inside Mix.install/2 scripts (you have to disable consolidation or use a full-blown project).

Compare with the simplest hello world possible with Bandit:

Mix.install([:bandit])

Bandit.start_link(plug: fn conn, _opts ->
  Plug.Conn.send_resp(conn, 200, "hello world")
end)

There is probably a balance to be found between functions and modules here.

19
Post #4
josevalim

josevalim

Creator of Elixir

I didn’t mean smaller on the LoC size, I meant smaller on the conceptual/ergonomic side. The simplest abstraction in Elixir are functions. Asking the user to define modules per path or to customize routes is, in my opinion, pointing them towards the wrong (larger) abstraction. :slight_smile:

ityonemo

ityonemo

Heh, if you hate defimpls:

(Disclaimer, self-plug)

acalejos

acalejos

I just also wanted to chime in with some encouragement here. I think it’s a worthwhile effort, and I think your intuition is correct in that if you find it necessary then others would probably like it as well.

I’ve seen the likes of @wojtekmach get peppered with dissent about his Req library with questions like “why do we need another HTTP client?”, and now it’s becoming the defacto HTTP client and the only one I use.

Just food for though. Good luck going forward!

taro

taro

Hi @gregvaughn !

Apparently I haven’t made it yet, but I wanted Mar to be a wrapper on Plug offering opinions and ergonomics.

My experience was more like “Phoenix has a steep learning curve for absolute beginners”. The difficulty was not in the complexity of how the library works, but in the lengthy examples, generated files, and scattered informations that obfuscate the simple nature of Elixir.

The real problem is not that it has such a wall, but that the wall prevents newcomers casually marching into making web stuff with Elixir. At this point, I think they should be guided to a Plug project first. There’s a reason CS50 teaches Flask instead of Django. The less prepared junior devs, the less companies adopting Elixir.

When did it become about Phoenix itself? It surprises me when people take it as a derogation against Phoenix. Phoenix is great. It’s a complete project for production. I understand that the experienced developers might not resonate with this. They understand the entirety of Elixir, absolutely crush working with it. But there are infinitely more things Phoenix might have been missing, which is not its fault or anything.

Where Next?

Popular in Announcing Top

ityonemo
Currently just starting out on a new mini-project - getting zig NIFs to run in elixir. https://github.com/ityonemo/zigler The idea here...
New
BartOtten
This powerful library works together with Phoenix Router to provide the ultimate routing solution. It simplifies route manipulation, givi...
New
BartOtten
Phoenix Live Favicon Favicon manipulation for Phoenix Live A lib enabling dynamic favicons in Phoenix Live View applications. To sho...
New
MRdotB
Greetings Elixir community! Today, I’m thrilled to present you with resvg_nif, an open-source project that provides Elixir bindings for ...
New
kasvith
Hello Everyone, I was working with some HTML-to-Markdown libraries and ran into a few issues when converting a complex markup file to Ma...
New
garrison
Hobbes is a scalable, fault-tolerant transactional record store written in Elixir. Hobbes is designed to be: Scalable - Hobbes can sha...
New
mindreframer
ElixirProto: Protobuf-Inspired Serialization for Elixir Events I wanted to have an Elixir-native serialization for events, that also supp...
New
corka149
A JSON patch is a way to define a sequence of manipulating operations on a JavaScript object. The IETF published the RFC 6902 - found he...
New
Asd
Hi, I am happy to release the Repatch library for mocking and patching implementation in tests and anywhere else. It brings new possibili...
New
zoedsoupe
update (since 2025-07-24) the project got forked and rebranded to anubis-mcp, since i not on CloudWalk anymore and can’t ensure they will...
New

Other popular topics Top

chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30048 115
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
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...
273 38985 115
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New

We're in Beta

About us Mission Statement