README.md

# Aino

[![Discord](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/wVDSWJ2EjE)

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.

```elixir
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,
      host: config.host,
      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)
  end
end
```

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.

```elixir
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),
      order_routes()
    ]
  end

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

  @impl true
  def handle(token) do
    middleware = [
      Aino.Middleware.common(),
      &Aino.Middleware.Routes.routes(&1, routes()),
      &Aino.Middleware.Routes.match_route/1,
      &Aino.Middleware.params/1,
      &Aino.Middleware.Routes.handle_route/1,
    ]

    Aino.Token.reduce(token, middleware)
  end
end
```

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.

```elixir
defmodule Index do
  alias Aino.Token

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

defmodule Index.View do
  require Aino.View

  Aino.View.compile [
    "lib/index/index.html.eex"
  ]
end
```

## 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.Middleware.common/0`.

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

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

  Map.put(token, :headers, headers)
end
```

[elli]: https://github.com/elli-lib/elli