# Aino


An experimental HTTP framework built on top of [elli][elli]. Aino is pronounced as "eye no".

## Why Aino?

Aino is an experiment to try out a new way of writing HTTP applications on Elixir. It uses [elli][elli] instead of Cowboy like Phoenix and Plug. Instead of writing an Endpoint like Phoenix, you write a Handler. The handler's job is to reduce across a series of middleware that are simple functions to generate a response.

The handler also works on a token instead of a conn. The token is a simple map that you can add whatever keys you wish to it. Aino has a few standard keys but you can easily ignore them if you want to write your own processing.

## How to use Aino

In order to use Aino, you must add it to your supervision tree and provide a callback handler that Aino will call `handle/1` on.

defmodule Aino.Application do
  use Application

  def start(_type, _args) do
    # get your config somehow

    aino_config = %Aino.Config{
      callback: Example.Web.Handler,
      otp_app: :example,
      port: config.port,
      environment: config.environment,
      config: %{}

    children = [
      {Aino.Supervisor, aino_config}

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

In the handler, you process the incoming request (in the `token`) through a series of "middleware." The middleware all accept a single parameter, the `token`. A `token` is simply a map that you can store whatever you want on it.

The only thing that is initially pased in is the `:request`, and at the very end of the `handle/1` the token should include three keys, `:response_status`, `:response_headers`, and `:response_body`.

Aino ships with a common set of middleware that you can include at the top of processing, if you don't want them, simply don't include them! The list of middleware can be a list of lists as well.

Another built in middleware is a simple routing layer. Import the HTTP methods from `Aino.Middleware.Routes` that you're going to use in your routes. Then each HTTP method function takes the route and a middleware that should be run on the route.

defmodule MyApp.Handler do
  import Aino.Middleware.Routes, only: [get: 2, get: 3, post: 2]

  @behaviour Aino.Handler

  def routes() do
      get("/", &Index.index/1, as: :root),
      get("/about", &Index.about/1, as: :about),

  defp order_routes() do
      get("/orders", &Orders.index/1, as: :orders),
      get("/orders/:id", &, as: :order),
      post("/orders", &Orders.create/1)

  @impl true
  def handle(token) do
    middleware = [
      &Aino.Middleware.Routes.routes(&1, routes()),

    Aino.Token.reduce(token, middleware)

The route middleware take a token and generally should return the three keys required to render a response. You can also render EEx templates as shown below.

defmodule Index do
  alias Aino.Token

  def index(token) do
    |> Token.response_status(200)
    |> Token.response_header("Content-Type", "text/html")
    |> Token.response_body(Index.View.render("index.html"))

defmodule Index.View do
  require Aino.View

  Aino.View.compile [

## Concepts

### `Aino.Handler`

A handler processes an incoming request from Aino.

The `handle/1` function is passed an `Aino.Token`.

The handler _must_ return a token that contains three keys to return a response:

- `:response_status`
- `:response_headers`
- `:response_body`

If the token does not contain these three keys, a 500 error is returned.

Inside your handler, you may wish to use several `Aino.Middleware` including

### `Aino.Token`

The token is what flows through the entire web request. Tokens are simple maps
that contain no defined keys beyond `:request`. Several Aino middleware add
keys and they are documented in the functions.

### `Aino.Middleware`

Middleware are simple functions that take the token and return the token. They process
the request and add or modify existing keys on the token.

An example middleware is `Aino.Middleware.headers/1`:

def headers(%{request: request} = token) do
  headers =, fn {header, value} ->
      {String.downcase(header), value}

  Map.put(token, :headers, headers)