Nine - data driven routing library for elli web server

This library was initially developed during the 2023 Spawnfest hackathon last month. I have since made updates, wrote extensive unit tests, and got it prepared for public usage.

Source
Hex

nine is a library to enable composing routes and middleware for elli webservers in a declarative way. If you use elli webservers this will provide a nice quality of life improvement. There were several influences on this library. Most importantly, if you are familiar with Plug.Router then you should feel right at home. I created this library because I wanted to precisely compose middleware at any level I wished. elli’s middleware features were not fine grained enough for me. While there may be some sharp edges, I have found it is already useful for basic applications.

The way it works is it takes a route configuration (just an Erlang map) and compiles it into an elli callback module. You can see this example module for how it looks to use nine in practice. For now, this only supports elli, but in theory it could be decoupled to support other web servers.

My future plans are to implement route config validations, as well as property based testing.

I hope others will enjoy this package as well!

12 Likes

@fancycade Hi, I would love to try out this library, may be possible to access the example again?

That would help a lot.

Thank you!
Cheers

2 Likes

Oh, thanks to the recent comment I saw this post on here.

It is great to see!

I was curious why so many binaries? No reason not to use atoms and looks a lot cleaner?

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

Also, since erlang:decode_packet and by extension Elli return an atom or charlist for method you probably want to use an atom there too to not deal with conversions?

[#{path => <<"/todo">>,
   method => 'GET',
   handle => ...}]

Just a side note on what I personally think is more readable, I’d go all in on OTP-27+ and use sigils in examples/code, but that is just me :slight_smile:

[#{path => ~b"/todo",
   method => 'GET',
   handle => ...}]

Hi there! Now that nine supports cowboy as well elli I’ve broken it out into 3 libraries, and made some API changes. I’ve been meaning to make a more public announcement after testing it a bit more. At minimum I should link the new libraries though in the README. I thought no one used this library so I figured had some more time :laughing:

nine_elli - I’ve moved the example to the example directory in this repo. Feel free to ask questions if you run into any issues.

I agree the atom’s are a lot cleaner, and something I might try. No reason I couldn’t support atoms and binaries. Over the years I’ve gotten it in my head to stay away from atoms, because of the issue of overusing them dynamically. However, that isn’t relevant in this case since the compile function is called once.

Once Debian supports OTP-27 I will go all in. As a heavy Debian user I personally hate having to download the latest version of whatever language to use a certain library, so I try to not instill that on others with my libraries.

In case anyone is curious, nine supports cowboy as well.

nine_cowboy.

2 Likes

Ah, as long as the usage doesn’t require a list_to_atom anywhere you are safe. Usage for a route’s keys would be safe.

Interesting you are using debian packages, I find those to be poorly suited for development (and they aren’t really meant for it but for running programs) – I’m on Fedora, so the same case here – so have relied on stuff like kerl (with tools like asdf/mise) for development – well, really I use (plug) beamup which I’ll hopefully soon have binary Erlang installs instead of just source in the near future for both debian and fedora – and eventually more, if not generic cross-distro builds.

1 Like

Thank you! Very nice lib!

1 Like

Yes, thank you for beamup I’ve been meaning to try it. I don’t have great reasons for my thinking (mostly laziness), but I appreciate the nudges in a better direction.

I’ve published a 0.2.1 release that supports optionally using atoms for the config keys instead of binaries. A few other niceties as well.

2 Likes

Published 0.3.0 to support compiling the module to a beam file since without this it doesn’t work with releases. I’m a bit embarrassed to admit this library didn’t work with releases till now. Apologies to anyone who might’ve run into this issue. I’ve also created a rebar plugin to accompany this process so the router module can be compiled automatically.

rebar3_nine

1 Like

Hey @fancycade,

I am using nine in 3 projects, all of them using releases.

One of them is open source, I had no issues with releases.

This is the command I use to assemble the release.

And this is where I build the router.

The other 2 projects do more or less the same.

1 Like

@fancycade Would you mind explaining how to use nine_cowboy with Cowboy v2.13.0 ?
My config looks like this

 Dispatch = [ {'_',
        [
        {"/media/[...]",   cowboy_static, {dir, filename:join(static(), "media"), cow_mime_types()}},
        {"/api/v1/user/[...]", aura_user_api, []}
        ]}
    ],
    cowboy_router:compile(Dispatch).

I’m already calling cowboy_router:compile.
Thanks.

1 Like

There’s an example app at: ~fancycade/todo - Example todo application in Erlang - sourcehut git
that uses cowboy and nine

2 Likes

@carlotm already checked that one. As you can see, we still use the old routes format (not maps). Also wondering if there is any benefit over cowboy_router:compile?

Hi @carlotm, first of all I can’t tell you how happy it makes me to see that you’ve been using nine. Sometimes it feels like I’m releasing these projects into the void. Also, holy cow your website is like a real thing?! All of my apps have been half finished toy projects so this is just wild for me to see.

I believe I know the reason your projects are working as is.

With nine_elli I hand rolled the ast as I wasn’t too familiar with erl_syntax at the time. When I made nine_cowboy I did end up using erl_syntax.

When I went to make a release with nine_cwoboy I would run into erl_syntax:atom undefined errors. Which is a weird and frustrating error because I guess there are just some builtin modules that don’t end up in releases? Anyways, the latest change to nine was to work around this issue. Since I kept it backwards compatible your code should still work with no changes.

I pinned 2.12.0 in nine_cowboy because I was getting a weird dependency error with cowlib in my projects. I think I noticed this when I switched to more recent versions of Erlang. If you might know how to fix this I’d be happy to get rid of the version pin in nine_cowboy.

So the whole point of nine_cowboy is to actually replace cowboy_router. That means your old routes config wouldn’t work. The big difference is that cowboy_router dispatches to a handler module whereas nine_cowboy dispatches to a handler function. Overall you should have to write a lot less boilerplate using nine over default cowboy.

Like @carlotm said you can check out the todo app how you might structure your code. I couldn’t give you the exact translation without seeing your handler modules.