For the last few weeks I’ve been playing around with a new HTTP framework I’m calling Aino. See it on GitHub.
Aino is built on top of Elli and loosely based around Ring from Clojure. It doesn’t use Plug or Phoenix, though I’m sure some concepts were pulled from it given that’s my day job!
Aino is an experiment to try out a new way of writing HTTP applications on Elixir. It uses elli instead of Cowboy like Phoenix and Plug. I wanted to see if there were different ways of writing web apps in Elixir. Is there a better way than MVC? Is there a better view layer? What would happen if we try a different HTTP library? Is there a better router? These are the sorts of questions I wanted to play around with.
With the current trajectory of Phoenix, I’ve been thinking a lot on how to keep “dead views” still alive. I personally think they have a seat at the table. I have listened to several mentors of mine from REST Fest talk about how we shouldn’t keep state on the server for web/API clients. It’s conversations and lessons like these that are guiding me towards exploring something like Aino.
Aino will likely stay as a very fast server side renderer and API framework. I have ideas on what to do for websockets if I ever need or want to add them in, elli has the capability through another library. These ideas come from a lot of experimenting in Grapevine and Kalevala with cowboy websockets directly, so that’s my general plan if I end up there.
Aino is a character in the Kalevala. She’s also the wife of Jean Sibelius, a composer that wrote works involving the Kalevala.
It’s loosely themed around my other project Kalevala, a text world builder. Plus my wife picked the name.
Aino is built around a handler. The handler receives a token for the request and then reduces over a list of middleware to generate a response. Tokens are simple maps with no defined structure, middleware are functions that take a single argument of the token.
Aino ships with a handful of common middleware, including parsing request POST body of a few MIME types (url encoded and JSON at the moment), header parsing, a tiny view wrapper around EEx, a very simple router, and simple session stored in cookies.
The Aino.Token
is taken from René Föhring’s excellent blog post and ElixirConf 2018 talk. I went with a simple map instead of a struct because this enables anyone to add any key in middleware without needing to extend a token struct. Using a simple map was also fits more in line with what Ring uses for passing data between middleware.
If you want to see a more realistic example, I’ve written an RSS reader using Aino and it turned out well. You can view the source here on GitHub.
Below is a simple example of Aino using most of it’s pieces to render “Hello, World”.
defmodule AinoExample.Application do
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children = [
{Aino, callback: AinoExample.Handler, port: 3000}
]
opts = [strategy: :one_for_one, name: AinoExample.Supervisor]
Supervisor.start_link(children, opts)
end
end
defmodule AinoExample.Handler do
@behaviour Aino.Handler
import Aino.Middleware.Routes, only: [get: 2]
@impl true
def handle(token) do
routes = [
get("/", &AinoExample.Pages.index/1)
]
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
defmodule AinoExample.Pages do
alias Aino.Token
def index(token) do
token
|> Token.response_status(200)
|> Token.response_header("Content-Type", "text/plain")
|> Token.response_body("Hello, world!\\n")
end
end
Aino doesn’t do a lot for you at the moment, and I’m not sure how much I want to abstract it away. I like the raw internals of HTTP right in your face. I have a few helper functions around redirecting and rendering HTML, so this will probably be the path forward for making things easier to use.
For initial benchmarks I was slightly blown away. Aino is only a few hundreds of lines of code, so being this fast makes sense. It’s not doing a lot of work for you.