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

asiniy
Hey there! I wrote a download elixir package which does exactly what its name about - an easy way to download files. I saw solutions ab...
New
deadtrickster
I’ve just released stable versions of my Prometheus Elixir libs: Elixir client [docs]; Ecto collector [docs]; Plugs instrumenter/Export...
New
josevalim
EDIT: since Ecto 3.0 final version is out, this post was amended to use the final versions in the instructions below. Hi everyone, We a...
New
Crowdhailer
I have been updating a library that allows you to pipe between functions that use the erlang result tuple convention. Assuming you have ...
New
Eiji
ExApi is a library that I’m developing now and hope release soon This library will allow to: list all apis list all api implementation...
New
nikokozak
Hello all, I’ve been working on Svonix - a library for quickly integrating Svelte components into Phoenix views. It’s a much-needed succ...
New
mindok
What is ContEx? A pure Elixir server-side data plotting/charting library outputting SVG. It has nice barcharts in particular and works g...
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
marcuslankenau
I feel kind of stuck with the absence of a proper xml library for Elixir. Currently I use SweetXML which was ok for me more or less to pa...
New
tmbb
I’ve decided to create this topic to discuss optimization possibilities for something like Phoenix LiveView. I’ve created this topic unde...
144 10187 141
New

Other popular topics Top

marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
marick
I had some trouble figuring out how to make many-to-many associations work. Once I got it working, I wrote a blog post. Because I’m a nov...
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