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

First Post!

te_chris

te_chris

You got dragged a bit in the other thread but I think this really cool and a worthwhile addition to the library ecosystem

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)

Last Post!

taro

taro

Exactly! I’m using bare Plug, HTMX, and Temple now.

I’m not actively working on this topic. But I will when the time is right. Thanks!

edit: Datastar sounds like an upgrade from HTMX. Cool stuff, thanks!

Where Next?

Popular in Announcing Top

Hal9000
Here is my first stab at this. README pasted below. https://github.com/Hal9000/elixir_random Comments and critiques are welcome. Thank...
New
pkrawat1
Hey guyz We at @aviabird are working on a payment library in elixir/phoenix. We are targeting March 2018 to add 56 Gateways to it. Have...
New
sasajuric
I’d like to announce a small library called boundaries. This is an experimental project which explores the idea of enforcing boundaries ...
New
benlime
LiveMotion enables high performance animations declared on the server and run on the client. As a follow up to my previous thread A libr...
New
zachdaniel
Ash Framework What is Ash? Ash Framework is a declarative, resource-oriented application development framework for Elixir. A resource can...
New
woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
restlessronin
The repo is at GitHub - cyberchitta/openai_ex: Community maintained Elixir library for OpenAI API · GitHub. Docs are at OpenaiEx User Gu...
152 10739 134
New

Other popular topics Top

stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New

We're in Beta

About us Mission Statement