Skip to content
[WiP] Web framework for Elixir inspired by Rails [#WeberMVC at freenode] http://elixir-web.github.io/weber/
Elixir JavaScript Shell
Find file
Latest commit 018d8b0 @rcdilorenzo rcdilorenzo Merge pull request #207 from teodor-pripoae/master
Fix dependency error on latest plug package

README.md

Weber

Weber - is a MVC Web framework for Elixir.

Build Status

Join the Community

#WeberMVC on freenode IRC

Mail listing

Build Status

Features

  • MVC web framework;
  • Project generation;
  • Json generation with exjson;
  • Websocket support;
  • HTML helpers;
  • Web controller Helpers.
  • i18n support;
  • Live code/templates update
  • Sessions support;
  • weber-contrib

Quick start

  1. Get and install Elixir from master.
  2. Clone this repository.
  3. Execute make && make test in the weber directory.
  4. Create new project with: mix weber.new /home/user/testWebApp.

Now go to the /home/user/testWebApp and execute there: mix deps.get && mix compile --all --force. Then you can try to run your testWeberApplication with:

./start.sh

or run it in daemon mode:

./start.sh --no-shell

and go to the http://localhost:8080/

For more details see in examples directory and Weber's API.

Directory structure

Dir/File Description
./start.sh Startup script
./lib/controllers Directory with web controllers
./lib/helpers Helper functions
./lib/models Directory for models (ecto)
./lib/views Directory with EEx views
./lib/app.ex Application startup settings
./lib/config.ex Configuration file.
./lib/route.ex File with routes declaration
./public Directory for static files (css, js ....)

Routing

Routing declaration is in route.ex files:

    route on("GET", "/", :Simpletodo.Main, :action)
       |> on("POST", "/add/:note", :Simpletodo.Main, :add)
       |> redirect("GET", "/redirect", "/weber")
       |> on("ANY", %r{/hello/([\w]+)}, :Simpletodo.Main, :action)

Also on supports following syntax:

    route on("GET", "/", "Simpletodo.Main#action")
       |> on("POST", "/add/:note", "Simpletodo.Main#add")

It is route macro which value is chain of on functions with 3 parametes:

  • Http method
  • Route path, can be binding (starts with ':' symbol);
  • Module name of controller;
  • Function name from this controller.

Http method can be:

  • "GET"
  • "POST"
  • "PUT"
  • "DELETE"
  • "PATCH"
  • "ANY"

You can set up resource in routing:

    route resources(:Controller.Photos)

It will be the same as

route on("GET",    "/controller/photos",            :Controller.Photos, :index)
   |> on("GET",    "/controller/photos/new",        :Controller.Photos, :new)
   |> on("POST",   "/controller/photos",            :Controller.Photos, :create)
   |> on("GET",    "/controller/photos/:id,         :Controller.Photos, :show)
   |> on("GET",    "/controller/photos/:id/edit,    :Controller.Photos, :edit)
   |> on("PUT",    "/controller/photos/:id,         :Controller.Photos, :update)
   |> on("DELETE", "/controller/photos/:id,         :Controller.Photos, :destroy)

Build url from code

You can build url from your elixir code with:

import Weber.Route

route on("GET", "/", "Simpletodo.Main#action")
   |> on("POST", "/add/:note", "Simpletodo.Main#add")

# generates: /add/1
link(:Elixir.Simpletodo.Main, :add, [note: 1])

Controllers

Every Weber's controller is just an elixir module, like:

defmodule Simpletodo.Main do

  import Simplemodel

  use Weber.Controller

  layout false

  def action(_, conn) do
    {:render, [project: "simpleTodo"], []}
  end

  def add([body: body], conn) do
    new(body)
    {:json, [response: "ok"], [{"Content-Type", "application/json"}]}
  end

end

Every controller's action passes 2 parameters:

Controller can return:

  • {:render, [project: "simpleTodo"], [{"HttpHeaderName", "HttpHeaderValheaderVal"}]} - Renders views from views/controller/action.html and sends it to response;
  • {:render, [project: "simpleTodo"]} - the same without headers;
  • {:render_inline, "foo <%= bar %>", [bar: "baz"]}} - Renders inline template;
  • {:file, path, headers} - Sends file in response;
  • {:file, path} - the same without headers;
  • {:json, [response: "ok"], [{"HttpHeaderName", "HttpHeaderValheaderVal"}]} - Weber converts keyword to json and sends it to response;
  • {:json, 200, [response: "ok"], [{"HttpHeaderName", "HttpHeaderValheaderVal"}]} - Allows a custom status;
  • {:json, [response: "ok"]} - the same without headers;
  • {:redirect, "/main"} - Redirects to other resource;
  • {:text, status, data, headers} - Sends plain text;
  • {:text, data, headers} - the same without status;
  • {:text, data} - the same without headers;
  • {:nothing, ["Cache-Control", "no-cache"]} - Sends empty response with status 200 and headers;
  • {:nothing, ["Cache-Control", "no-cache"], http_status :: integer} - Sends empty response with custom status.

Controllers can also raise at any point in the action and immediately render a response:

defmodule Simpletodo.Main do
  import Simplemodel

  # Add :unauthorized to list of known responses
  render_when_raise :unauthorized, {:text, 401, "Action prohibited.", []}

  def action([user_id: user_id], conn) do
    if unauthorized_user_id?(user_id) do
      # Immediately render the known response
      raise_and_render :unauthorized
    end
    {:render, [project: "simpleTodo"], []}
  end
end
  • render_when_raise(value, response) - macro that adds to the known responses to render if specific value is raised
  • raise_and_render(value) - raises a WeberControllerException and renders a response based on the known responses

Request params

Sometimes it is necessary for the request parameters in the controller. For this point can be used Weber.Http.Params API.

defmodule Simplechat.Main.Login do

  import Weber.Http.Params

  use Weber.Controller

  layout false

  def render_login([], conn) do
    # get body request
    body = get_body(conn)
    #
    # Do something with param
    #
    {:render, [project: "SimpleChat"]}
  end

end

If you need to get parameters from query string, it is easy to do with param/1 API. For example you got request for: /user?name=0xAX, you can get name parameter's value with:

defmodule Simplechat.Main.Login do

  import Weber.Http.Params

  use Weber.Controller

  def render_login([], conn) do
    name = param(:name, conn)
    #
    # Do something with param
    #
    {:render, [project: "SimpleChat", name: name]}
  end

end

You can find the full API at the wiki.

Before/After request hooks

You can define __before__ or after __after__ hooks in your controller. It will pass two parameters:

  • :action - action name
  • conn - connection parameter
defmodule Simplechat.Main.Login do

  def render_login([], conn) do
    {:render, [project: "SimpleChat", name: "WeberChat"]}
  end

  #
  # Executes before request
  #
  def __before__(:render_login, conn) do
    conn
  end

  #
  # Execute after response
  #
  def __after__(:render_login, conn) do
    conn
  end

end

Helper

Html Helper

Html helpers helps to generate html templates from elixir:

defmodule Simpletodo.Helper.MyHelper
  import Weber.Helper.Html

  # Generates <p>test</p>
  def do_something do
    tag(:p, "test")
  end

  # Generates <p class="class_test">test</p>
  def do_something do
    tag(:p, "test", [class: "class_test"])
  end

  # Generates <img src="path/to/file">
  def do_something do
    tag(:img, [src: "path/to/file"])
  end
end

Tags with blocks

defmodule Simpletodo.Helper.MyHelper
  import Weber.Helper.Html

  # Generates <div id="test"><p>test</p></div>
  def do_something do
    tag(:div, [id: "test"]) do
      tag(:p, "test")
    end
  end
end

Partials

Include html partials to the your template with:

<%= render "Partial", [test: "Hello"] %>

You must have "your_project_name/lib/views/partials/Partial.html" with:

<%= @test %>

Resource Helpers

You can include your static resources like javascript, css, favicon or image files with resource helpers:

#
# Generates: <script type="text/javascript" src="/static/test.js"></script>
script("/static/test.js")
# If no value is passed for src it defaults to "/public/js/application.js"
script()

#
# Generates: <link href="/static/test.css" rel="stylesheet" media="screen">
#
style("/static/test.css")
# If no value is passed for href it defaults to "/public/css/application.css"
style()

#
# Generates: <link href="/public/img/favicon.ico" rel="shortcut icon" type="image/png">
favicon("/public/img/favicon.ico")
# If no value is passed for href it defaults to "/public/img/favicon.ico"
favicon()

#
# Generates: <img src="/public/img/example.jpg" alt="Image" class="some-class" height="100" width="100">"
image("/public/img/example.jpg", [alt: "Image", class: "some-class", height: 100, width: 100])

#
# Generates: <audio src="/public/audio/sound">
audio("/public/audio/sound")

#
# Generates <link href="my.rss" type="application/atom+xml" title="My feed">
atom("my.atom", "My feed")

#
# Generates <link href="my.rss" type="application/rss+xml" title="My feed">
rss("my.rss", "My feed")

#
# Generates:
#  <audio autoplay="autoplay">
#    <souce src="/public/audio/sound1"></souce>
#    <souce src="/public/audio/sound2"></souce>
#  </audio>
#
audio(["/public/audio/sound1", "/public/audio/sound2"], [autoplay: true])

#
# Generates: <video src="public/videos/trailer">
video("public/videos/trailer")

#
# Generates:
#  <video height="48" width="48">
#    <souce src="/public/videos/video1"></souce>
#    <souce src="/public/videos/video2"></souce>
#  </video>
video(["/public/videos/video1", "/public/videos/video2"], [height: 48, width: 48])

Controller Helpers

content_for_layout and layout

NOTE: Now all views and layout files must start with capital letter.

All controllers got main.html by default for views, but you'd might change it.

You can create custom layout for you controller:

Create Layout.html in the lib/views/layouts directory and put there:

<!DOCTYPE html>
<html>
  <head>
    <title>
      My Project
    </title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8" />
  </head>
  <body>
    <div id="container">
    <%= @content_for_layout %>
    </div>
  </body>
</html>

Than declare layout helper in your controller:

defmodule TestController.Main do

  use Weber.Controller

  layout "Layout.html"

  #
  # Here are some actions
  #

end

And you have lib/views/Main.html with:

Hello World!

Weber puts lib/views/Main.html content inside <%= content_for_layout %> and renders it in the response.

Logging

Weber uses exlager for the logging. For using it just set up:

log: true

in your config and use it:

defmodule LogTest.Main do

  require Lager

  def action([], _conn) do
    Lager.info "New request"
    {:render, []}
  end

end

Internationalization

Important Experemental now

See - Weber Internationalization

{
  "HELLO_STR" : "Hello, It is weber framework!",
  "FRAMEWORK_DESCRIPTION" : "Weber - is a MVC Web framework for Elixir."
}

and you can use it like:

<span><%= t(@conn, "HELLO_STR") %></span>

in your html template.

Websocket

You can handle websocket connection and incoming/outcoming websocket message in your controllers.

First of all you need to designate websocket controller in your config.ex file in webserver: section, like:

ws:
  [
   ws_mod: :Handler
  ]

After it you must implement 3 callbacks in your controller like this:

defmodule Simplechat.Main.Chat do

  def websocket_init(pid, conn) do
    #
    # new websocket connection init
    #
  end

  def websocket_message(pid, message, conn) do
    #
    # handle incoming message here
    #
  end

  def websocket_terminate(pid, conn) do
    #
    # connection terminated
    #
  end

end

All websocket connections are must start with prefix /_ws/.

Session

Session API

Testing requests

Currently, one way to test requests is using exunit and the hackney http client as we do in our own tests.

This is not as convenient and expressive as more established frameworks like rspec for rails offer but we are planning to improve this in the future.

Mix tasks

Create new project

mix weber.new /home/user/projectName

Version

mix weber --version

Help

mix weber --help

Print all current routes

mix weber.routes

Dependencies

Contributing

See Contributing.md

Additional info

Author

@0xAX.

Something went wrong with that request. Please try again.