Tokumei.Router

Focused routing layer for Raxx applications

  1. DSL for routing incomming HTTP requests by path and method.
  2. Extension to the Raxx interface to provide better error handling.
  3. Concept of a middleware stack.

Installation

If available in Hex, the package can be installed as:

  1. Add tokumei_router to your list of dependencies in mix.exs:

    def deps do
      [
        {:tokumei_router, "~> 0.3.0"},
        {:ace_http, "~> 0.1.2"}
      ]
    end
  2. Ensure tokumei_router is started before your application:

    def application do
      [applications: [:tokumei_router, :ace_http]]
    end

Ace.HTTP is a server that can host Raxx applications, adapters are also available for cowboy and elli.

Usage

# my_app/router.ex

defmodule MyApp.Router do
  use Tokumei.Router
  alias Raxx.Response

  # use middleware by specifying module and configuration.
  # ContentLength is included as an example.
  @middleware {Tokumei.Router.ContentLength, nil}

  # Define response actions inline using Raxx Helpers.
  route "/hello" do
    :GET -> Response.ok("Hello, World!")
  end

  # Match on string variables
  route "/hello/:name" do
    :GET ->
      greeting = "Hello, #{name}!"
      Response.ok(greeting)
  end

  # Define actions for multiple methods on a resource.
  route "/users/:id" do
    :GET -> UsersController.show(id, request)
    :POST -> UsersController.create(id, request)
    :DELETE -> UsersController.delete(id, request)
  end

  # Access the request and environment passed to the router
  route "/users" do
    :GET ->
      %{page: page_number} = request.query
      users_page = env.user_repo.all(page_number: page_number)
      Response.ok(users_page)
  end

  # Handle routing errors
  error %NotFoundError{request: request} do
    Response.not_found("not found: #{inspect(request.path)}")
  end

  error %MethodNotAllowedError{allowed: allowed} do
    Raxx.Response.method_not_allowed([{"allow", allowed |> Enum.join(" ")}])
  end

  # return custom errors
  route "/sign-up" do
    :POST ->
      case validate_sign_up_form(request.body) do
        {:ok, data} ->
          Response.created()
          # continue
        {:error, _reason} ->
          {:error, :bad_request}
      end
  end

  error :bad_request do
    Response.bad_request()
  end

  # Mount subapp
  mount "/api", APIRouter
end

Choose a server to host the application. Using Tokumei.Router will create a Raxx App that can be mounted using any of the adapters found in Raxx

defmodule MyApp do
  use Application

  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      worker(Ace.HTTP, [{MyApp.Router, []}, [port: 8080]])
    ]

    opts = [strategy: :one_for_one]
    Supervisor.start_link(children, opts)
  end
end

The Tokumei.Router provides a simple routing DSL that first routes on path and second on request method. This is done so that Tokumei.Router can return the correct responses for resources that will respond to only a subset of HTTP verbs.

import Raxx.Request

assert 200 == MyApp.Router.handle_request(get("/hello"), :env)
assert 405 == MyApp.Router.handle_request(put("/hello"), :env)
assert 404 == MyApp.Router.handle_request(put("/random"), :env)