Tokumei
Tiny but MIGHTY Elixir webframework
defmodule MyApp.WWW do
Use Tokumei
route "/greeting/:name", {name} do
get(_request, _config) ->
ok("Hello, #{name}!")
end
endDive in
mix archive.install https://github.com/crowdhailer/tokumei/raw/master/tokumei_new.ez
mix tokumei.new my_app
cd my_app
iex -S mixvisit 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")
endResponses
# 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
end1 - 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}
endStatic 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}")
endServer 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 ..."
endModel
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
Route by url
Match segements with path variables
Routing by HTTP method
Access request in match
Access server config in match
Document chunked/SSE's
Document static
Document templates
Document configuration
Add remaining HTTP method matchers.
Test routing DSL
Add error handling within actions
Handle messages to server without sending a chunk
Test streaming
Match on rest of path, "/section/*rest", I currently have no usecase for this.
Advanced routing using array directly and when conditions
Make server configurable
Extract starting as application from starting as supervised process
layout and partials
Cookies, Sessions and Flash
chunked responses
Sending files from action
Sinatra layers [ExtendedRack, ShowExceptions, MethodOverride, HEAD, logging, sessions, protection]
Add wobserver
Deployment examples. Digital Ocean, Vagrant, Kuberneties
Add mounting of sub-apps
HTTP/2 promises API, integration with raxx_chatterbox
Add logging layer
Example with JS compilation, add to generator
named routes
mix task to list all routes in a module
tokumei.routes MyApp.WWW"/", GET, HEAD "/users", GET, HEAD, POST "/users/:id", GET, HEAD, PATCH, DELETEConsider before and after filters, implemented as a raxx middleware
Consider a Controller architecture for larger projects
```elixir route "/" do get(WelcomeController, :home) post(WelcomeController, :enquiry) end ```