Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/oestrich/aino

An HTTP framework built on top of elli
https://github.com/oestrich/aino

elixir http

Last synced: 2 days ago
JSON representation

An HTTP framework built on top of elli

Awesome Lists containing this project

README

        

# 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