Weber
Weber - is a MVC Web framework for Elixir.
Join the Community
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
- Get and install Elixir from master.
- Clone this repository.
- Execute
make && make test
in the weber directory. - 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:
- List of URL bindings
- Plug.Conn record
Controller can return:
{:render, [project: "simpleTodo"], [{"HttpHeaderName", "HttpHeaderValheaderVal"}]}
- Renders views fromviews/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 status200
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 raisedraise_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 nameconn
- 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
Testing requests
Currently, one way to test requests is using exunit
and the hackney
http client as we do in [our own tests.] (https://github.com/0xAX/weber/blob/master/templates/default/test/response_test.exs)
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
- Introduction to the Weber - Weber
- Weber example for Heroku - heroku_weber_example
- A template for using Vagrant for developing Elixir applications with Weber - vagrant-weber
- ElixirSips. Episode 035: Weber
- ElixirSips. Weber, Part 2 - Performance