Tokumei

Tiny but MIGHTY Elixir webframework

defmodule MyApp.WWW do
  Use Tokumei

  route "/greeting/:name", {name} do
    get(_request, _config) ->
      ok("Hello, #{name}!")
  end
end

Dive in

mix archive.install https://github.com/crowdhailer/tokumei/raw/master/tokumei_new.ez
mix tokumei.new my_app
cd my_app
iex -S mix

visit localhost:8080

Usage

Routing

# Routes are created in blocks grouped by url...
route "/users" do

  # ...and by request method.
  get(_) ->
    ok("Dan, Lucy, Jane")

  # Methods accept a pattern that will be matched against the request.
  post(%{body: ""}) ->
    bad_request("Please provide a name")

  # Methods can be defined more than once for a multiheaded match.
  post(%{body: name}) ->
    created("Added #{name}")
end

# Any segment beginning with a colon is a path variable.
route "/users/:id", {id} do
  get(_) ->
    ok("This is user: '#{id}'")
end

# Multiple path variables can be matched.
route "/users/:id/carts/:id", {user_id, cart_id} do
  get(_) ->
    ok("This is cart: '#{cart_id}' for user: '#{user_id}'")
end

# Server configuration can be accessed from match.
route "/users/forgot-password" do
  post(request, %{mailer: mailer}) ->
    mailer.send_password_reset(request.body)
    ok("Reset sent")
end

Responses

# An action must return a response with status, body and headers.
route "/" do
  get(_) ->
    %{status: 200, body: "Home page", headers: [{"content-type", "text/plain"}]}
end

route "/users" do
  get(%{body: data}) ->

    # Helpers available for all response statuses.*(1)
    case MyApp.create_user(data) do
      {:ok, user} ->
        created("New user #{user}")
      {:error, :already_exists} ->
        conflict("sorry")
      {:error, :bad_params} ->
        bad_request("sorry")
      {:error, :database_fail} ->
        bad_gateway("sorry")
      {:error, _unknown} ->
        internal_server_error("Well that's weird")
    end
end

1 - Response helpers are imported from Raxx.Response.

Streaming

route "/updates" do
  get(_) ->

    # Return a streaming upgrade to change a server state to streaming.
    SSE.stream()
end

# Define behaviour for a server that has transitioned to streaming.
SSE.streaming do

  # match on messages received by server.
  {:update, update} ->
    {:send, update}
end

Static content

# Serve the contents of a directory, file path is relative to this file.
config :static, "./public"

- Files are served using Raxx.Static

Templates

# relative path to template ./templates/home_page.html.eex
# relative path to template ./templates/user_page.html.eex

# Provide a relative path to templates directory.
config :templates, "./templates"

route "/" do
  get(_) ->

    # View functions are compiled for each template.
    ok(home_page())
end

route "/users/:id", {id} do
  get(_) ->
    user = UsersRepo.fetch_by_id(id)

    # Variable are passed accessed in template as, `@user`.
    ok(user_page(%{user: user}))
end

- Templates must have an eex extension

- Content type is derived from first pary of template extension.

Error Handling

route "/checkout" do
  post(request) ->

    # Actions can return an exception as part of an error tuple
    {:error, :subscription_expired}
end

# Any error can be handled using an error block
error :subscription_expired do

  # payment_required/1 returns a 402 status Raxx.Response
  payment_required("Please pay up, :-)")
end

# Routing errors can also be intercepted
error %NotFoundError{path: path} do
  path = "/" <> Enum.join(path, "/")

  # For example, to send a custom response message.
  not_found("Could not find #{path}")
end

Server state

TODO

Request

route "/" do
  get(request) ->
    IO.inspect(request.scheme)  # "http"
    IO.inspect(request.host)    # "www.exxample.com"
    IO.inspect(request.port)    # 8080
    IO.inspect(request.method)  # :GET
    IO.inspect(request.mount)   # []
    IO.inspect(request.path)    # []
    IO.inspect(request.query)   # %{"page" => "5"}
    IO.inspect(request.headers) # [{"accept", "text/html"}]
    IO.inspect(request.body)    # "Lorem ..."
end

Model

Tokumei is a XVC framework.

Routing is provided for controllers, and templating for views. Applications bring their own model.

Testing

TODO - same as Raxx

Development Goals