README.md

# nine

A library providing a data driven router compiler. 

The goal of `nine` is to allow developers to compose middleware and handlers.
This should significantly decrease boiler plate and give a web development experience competitive with other
languages and ecosystems.

## Build

    rebar3 compile

## Usage

`nine` functions as a router compiler library. Meaning you give it a router config and it compiles a router module.
`nine` does not have it's own web server, it is meant to be paired with a backend.

`nine` provides one single function to be used: `compile`. Compile can be given one map argument or 3 arguments.

    nine:compile(#{routes => Routes, router => Router, generator => Generator})

OR

    nine:compile(Routes, Router, Generator).

The generator is provided by the backend, and is a module required to have at least one function `generate/2`.
The name of the `Router` module and the normalized routes after nine compiles the routes config given.

`nine:compile` takes a router config and compiles it into an Erlang module at runtime using [forms](https://www.erlang.org/doc/apps/erts/absform).
   

## Routes

A route is a map with these keys:

    [{<<"path">> => ...,
      <<"method">> => ...,
      <<"pre">> => ...,
      <<"post">> => ...,
      <<"handle">> => ...}]

path: The URL path for the request to be handled. Example: <<"/todo">>.
method: The request method to be handled. Example: <<"GET">>.
pre: The middleware to be called before the request handler. Example: [{todo_mid, json_request}]
post: The middleware to be called after the request handler. Example: [{todo_mid, json_response}]
handle: The request handler function.

A minimum config might look like this:

    [#{<<"path">> => <<"/">>,
       <<"handle">> => {index_handler, index}}]

A full config for a todo application would look like this:

    [#{<<"path">> => <<"/">> =>
       <<"method">> => <<"GET">>,
       <<"handle">> => {todo_handler, index}},
     #{<<"path">> => <<"/api">>,
       <<"post">> => [{todo_mid, json_response}],
       <<"handle">> => [
         #{<<"path">> => <<"/todo">>,
           <<"pre">> => [{todo_mid, json_request}],
           <<"handle">> => [
             #{<<"method">> => <<"POST">>,
               <<"handle">> => {todo_handler, post_todo}},
             #{<<"method">> => <<"DELETE">>,
               <<"handle">> => {todo_handler, delete_todo}}
           ],
         #{<<"path">> => <<"/todos">>,
           <<"method">> => <<"GET">>,
           <<"handle">> => {todo_handler, get_todos}}
       ]
     },
     #{<<"path">> => <<"*">>, 
       <<"handle">> => {todo_handler, not_found}}
    ]

There is a lot going on here with this config which we will break down in the next sections.

### Handler

A handler is specified as {module, function}. Example:

    {todo_handler, get_todos}

`todo_handler` being the module, and `get_todos` is the function.

The handler function is expected to look like this:

    get_todos(Context) ->
        Context#{resp => thoas:encode([#{id => 1, body => <<"Write more Erlang">>}])}.

#### Nested Handlers

It is possible to compose complex router configs by nesting handlers. The `handle` key can either take a 
tuple or a list of route configs.

For example an `api` handler can branch off with multiple other request paths.

    [{<<"path">> => <<"/api">>,
      <<"handle">> => [
        #{<<"path">> => <<"todo">>,
          <<"method">> => <<"POST">>,
          <<"handle">> => {todo_handler, post_todo}},
        #{<<"path">> => <<"todos">>,
          <<"method">> => <<"GET">>,
          <<"handle">> => {todo_handler, get_todos}}
    ]}]

This means when a POST request goes to `/api/todo` the function `todo_handler:post_todo/1` will be called. While
a request going to `/api/todos` will trigger the function `todo_handler:get_todos/1`.

#### URL Path Params

`nine` builds in a way to have named parameters in the URL.

A path like `/todo/:id` will result in the context map including the params key.
The value of the params will be `#{id => <<"id1">>}`.
In case you are worried about atoms coming from user data, it is okay for `id` to be an atom because it is a static value set at compile time. 

nine also supports partial path params like `/person/num:ber` will result in a params map looking like `#{ber => <<"2">>}` for example. This is similar to how Phoenix works with routing.

#### Wildcard

`nine` supports catch all routes with `*` in the path. For example: `<<"/*">>` will match any route.

We can also put a wildcard at the suffix of a path: `<<"/foo/*">>` will match a route like `<<"/foo/bar">>`.
Wildcards can only be at the end of a path. This is similar to how Phoenix works with catch all routes.
The reason for this is the routing uses Erlang pattern matching, so it must follow the same rules.

### Middleware

Middleware are specified just like handlers, in fact they are the same thing! An example middleware might look like:

    {nine_mid, json_response}

Middleware are functions that take a Context as input and output a Context or an elli response. One could write a logging middleware like this:

    logging_middleware(Context) ->
        logger:debug(#{context => Context}),
        Context.

Or we could make a middleware that adds some data to the Context:

    message_middleware(Context) ->
        Context#{message => <<"Hello, World!">>}.

Middleware are helpful in all sorts of situations and allow developers to write web apps in a DRY way.

#### Middleware Chains

`nine` specifies middleware chaining with the `pre` and `post` keys. Nesting routes will also concatenate the `pre` and `post` keys
in the expected order.

For example:

    #{<<"pre">> => [{todo_mid, json_request}],
      <<"handle">> => {todo_handler, post_todo}}

Will generate a sequence of function calls where `todo_mid:json_request` is called first, and the result is passed to
`todo_handler:post_todo`.

Allowing `post_todo` to be implemented like so:

    post_todo(Context=#{json := #{<<"body">> := Body}}) ->
        todo_db:insert(Body),
        nine_util:redirect(Context, <<"/">>).

`post_todo` can expect the `json` key to be filled with data because `json_request` is called first.

#### Halting

There are situations where we want to return a response immediately without finishing the middleware chain. This is known as halting.

`nine` makes this possible because each middleware and handler call is wrapped in a case statement checking for the `resp` key.

If a handler or middleware returns a Context map with the `resp` key it will immediately be sent without triggering further middleware.

## Inspirations

`nine` was inspired by other composable middleware tools.

- [ring](https://github.com/ring-clojure/ring/wiki/Concepts) - Standard Clojure HTTP abstraction for web servers
- [ataraxy](https://github.com/weavejester/ataraxy) - data driven routing library for Clojure
- [Plug.Router](https://hexdocs.pm/plug/readme.html#plug-router) - Ecosystem defining Elixir HTTP middleware
- [golang http middleware](https://dev.to/theghostmac/understanding-and-crafting-http-middlewares-in-go-3183) - Standard Library Golang Middleware Pattern
- [Cowboy Router](https://ninenines.eu/docs/en/cowboy/2.10/guide/routing/) - cowboy router is compiled into a lookup table

## Fun Facts

- The name `nine` comes from "nine nines".
- Middleware was originally intended to look like Ring's, but wasn't compatible with Erlang's pattern matching lookups.