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:

https://github.com/ityonemo/protoss

(Disclaimer, self-plug)

Where Next?

Popular in Announcing Top

tmbb
I’ve published the first version of my Makeup library. It’s a syntax highlighter for Elixir in the spirit of Pygments, Currently it highl...
New
Crowdhailer
The latest release of Ace (0.10.0) includes serving content over HTTP/2. I have started writing a webserver to teach my self more about...
New
mspanc
I am pleased to announce an initial release of the Membrane Framework - an Elixir-based framework with special focus on processing multim...
New
pkrawat1
Presenting Aviacommerce, open source e-commerce platform in Elixir Aviacommerce is an open source e-commerce platform in Elixir. We at...
New
tfwright
After working on it for a couple of months and using it in production for most of that time, today I’ve released LiveAdmin, a LiveView ba...
New
riverrun
I’ve just released version 3 of Comeonin, a password hashing library. The following small changes have been made: changes to the NIF c...
New
Crowdhailer
Raxx is an alternative to Plug and is inspired by projects such as Rack(Ruby) and Ring(Clojure). 1.0-rc.1 is now available. To use it re...
New
treble37
Just looking for a little feedback on a tiny helper library I built - Sometimes I find the need to convert maps with atom keys to maps w...
New
josevalim
Hello everyone, We have just released NimbleCSV which is a small and fast CSV parsing library for Elixir. It allows developers to define...
New
achempion
Hi, I would like to tell about my initiative to further maintain and develop Waffle project which is the fork of Arc library. The progre...
New

Other popular topics Top

sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 42920 311
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
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
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
New

We're in Beta

About us Mission Statement