Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/elixir-web/weber

[WiP] Web framework for Elixir inspired by Rails [#WeberMVC at freenode]
https://github.com/elixir-web/weber

Last synced: about 2 months ago
JSON representation

[WiP] Web framework for Elixir inspired by Rails [#WeberMVC at freenode]

Awesome Lists containing this project

README

        

Weber
========

Weber - is a MVC Web framework for [Elixir](http://elixir-lang.org/).

[![Build Status](https://travis-ci.org/elixir-web/weber.svg?branch=master)](https://travis-ci.org/elixir-web/weber)

## Join the Community

[`#WeberMVC` on freenode IRC](http://webchat.freenode.net/?channels=%23webermvc&uio=d4)

[Mail listing](https://groups.google.com/forum/#!forum/webermvc)

[![Build Status](https://travis-ci.org/0xAX/weber.png)](https://travis-ci.org/0xAX/weber)

## 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](https://github.com/elixir-web/weber-contrib)

## Quick start

1. Get and install Elixir from master.
2. Clone this repository.
3. Execute `make && make test` in the weber directory.
6. 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/](http://localhost:8080/)

For more details see in `examples` directory and Weber's [API](http://0xax.github.io/weber/public/docs/index.html).

## 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:

```elixir
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:

```elixir
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:

```elixir
route resources(:Controller.Photos)
```

It will be the same as

```elxir
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:

```elixir
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:

```elixir
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](https://github.com/elixir-lang/plug) record

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:
```elixir
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](https://github.com/elixir-web/weber/wiki/Weber.Http.Params-API).

```elixir
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:

```elixir
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](https://github.com/0xAX/weber/wiki/Weber.Http.Params-API).

## 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

```elixir
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:

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

# Generates

test


def do_something do
tag(:p, "test")
end

# Generates

test


def do_something do
tag(:p, "test", [class: "class_test"])
end

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

Tags with blocks

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

# Generates

test


def do_something do
tag(:div, [id: "test"]) do
tag(:p, "test")
end
end
end
```

### Partials

Include html partials to the your template with:

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

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

```html
<%= @test %>
```

### Resource Helpers

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

```elixir
#
# Generates:
script("/static/test.js")
# If no value is passed for src it defaults to "/public/js/application.js"
script()

#
# Generates:
#
style("/static/test.css")
# If no value is passed for href it defaults to "/public/css/application.css"
style()

#
# Generates:
favicon("/public/img/favicon.ico")
# If no value is passed for href it defaults to "/public/img/favicon.ico"
favicon()

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

#
# Generates:
audio("/public/audio/sound")

#
# Generates
atom("my.atom", "My feed")

#
# Generates
rss("my.rss", "My feed")

#
# Generates:
#
#
#
#
#
audio(["/public/audio/sound1", "/public/audio/sound2"], [autoplay: true])

#
# Generates:
video("public/videos/trailer")

#
# Generates:
#
#
#
#
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:

```HTML



My Project





<%= @content_for_layout %>

```

Than declare `layout` helper in your controller:

```elixir
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](https://github.com/khia/exlager) for the logging. For using it just set up:

```elixir
log: true
```

in your config and use it:

```elixir
defmodule LogTest.Main do

require Lager

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

end
```

## Internationalization

**Important** Experemental now

See - [Weber Internationalization](https://github.com/0xAX/weber/tree/master/lib/weber/i18n#weber-i18n)

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

and you can use it like:

```html
<%= t(@conn, "HELLO_STR") %>
```

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:

```elixir
ws:
[
ws_mod: :Handler
]
```

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

```elixir
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](https://github.com/elixir-web/weber/wiki/Weber.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.] (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

* [cowboy](https://github.com/ninenines/cowboy)
* [ecto](https://github.com/elixir-lang/ecto)
* [postgrex](https://github.com/ericmj/postgrex)
* [exjson](https://github.com/guedes/exjson)
* [plug](https://github.com/elixir-lang/plug)
* [exlager](https://github.com/khia/exlager)

## Contributing

See [Contributing.md](https://github.com/0xAX/weber/blob/master/Contributing.md)

## Additional info

* Introduction to the Weber - [Weber](http://0xax.blogspot.com/2013/12/weber-high-performance-web-framework.html)
* Weber example for Heroku - [heroku_weber_example](https://github.com/tsloughter/heroku_weber_example)
* A template for using Vagrant for developing Elixir applications with Weber - [vagrant-weber](https://github.com/slogsdon/vagrant-weber)
* [ElixirSips. Episode 035: Weber](http://elixirsips.com/episodes/035_weber.html)
* [ElixirSips. Weber, Part 2 - Performance](http://elixirsips.com/episodes/036_weber_part_2.html)

## Author

[@0xAX](https://twitter.com/0xAX).