README.md

# Kob

Another way to compose "Plug"s.

## Rational

An experiment to seek another way (no macro?) for composing "Plug"s. The idea is based on my experience from [koajs](https://koajs.com/).

## Note

This package requires **OTP 21.2**, which was released on Dec 12, 2018.

## Installation

```elixir
def deps do
  [
    {:kob, "~> 0.1.1"},
  ]
end
```

Docs can be found at [https://hexdocs.pm/kob](https://hexdocs.pm/kob).

## Example

```elixir
defmodule Kob.Example.Demo do
  def run() do
    Kob.new()
    |> Kob.use(fn next ->
      fn conn ->
        IO.puts("start middleware 1")
        conn = next.(conn)
        IO.puts("finish middleware 1")
        conn
      end
    end)
    |> Kob.use(fn next ->
      fn conn ->
        IO.puts("start middleware 2")
        conn = next.(conn)
        IO.puts("finish middleware 2")
        conn
      end
    end)
    |> Kob.use(Kob.plug(Plug.Logger, log: :debug))
    |> Kob.use(fn _ ->
      fn conn ->
        conn
        |> Plug.Conn.put_resp_content_type("text/plain")
        |> Plug.Conn.send_resp(200, "Hello world")
      end
    end)
    |> Kob.register_plug(MyKobPlug)

    {:ok, _} = Plug.Cowboy.http(MyKobPlug, [])
  end
end
```

## Compare Kob and Plug

It's encouraged to take a look with Kob's [source code](https://github.com/gyson/kob/blob/master/lib/kob.ex) to have better understanding of it. The core part is `Kob.compose` function, which is only a few lines of code.

The key design of Kob is two types:

- `@type handler :: (Plug.Conn.t() -> Plug.Conn.t())` : This is similar to Plug, which is the function to handle `Plug.Conn` transformation.

- `@type middleware :: (handler -> handler)` : This is how Kob does composistion. It chains things together.

This design has a few benefits:

- It enables `middleware` to determine if continue to next one. For example,

  ```elixir
  fn next ->
    fn conn ->
      if for_some_case do
        # we want to pass `conn` to next middleware
        next.(conn)
      else
        # we want to response and stop pipeline
        conn
        |> Plug.Conn.send_resp(200, "OK)
      end
    end
  end
  ```
  Plug can deal with this case via [`Plug.Conn.halt`](https://hexdocs.pm/plug/Plug.Conn.html#halt/1).

- It enables `middleware` to do something work afterwards. For example,
  ```elixir
    fn next ->
      fn conn ->
        # pass it to next middleware and wait for its returning
        conn = next.(conn)

        # downstream middlewares are finished now. We can do some cleanup now.
        # For example, we can log time, we can clear session, etc.
        do_some_work()

        # pass it back to previous middleware
        conn
      end
    end
  ```
  Plug can deal with similar case via [`Plug.Conn.before_send`](https://hexdocs.pm/plug/Plug.Conn.html#t:before_send/0).

- It enables upstream `middleware` to handle errors from downstream `middleware`. For example,
  ```elixir
  Kob.compose([
    # upstream middleware
    fn next ->
      fn conn ->
        try do
          next.(conn)
        rescue
          RuntimeError ->
            handle_error()
        after
          cleanup()
        end
      end
    end,

    # downstream middleware
    fn next ->
      fn conn ->
        raise "something error"
      end
    end
  ])
  ```
  Plug can deal with similar case via [`Plug.ErrorHandler`](https://hexdocs.pm/plug/Plug.ErrorHandler.html).

## Kob and Plug are interchangeable

- We can convert a Plug to Kob middleware via `Kob.plug/2`.
- We can convert a Kob struct/middleware to a Plug via `Kob.register_plug/1`.

## License

MIT