Router recommendation needed

Initially posted on Clojurians, I realized the discussion might become larger, so I moved it here.

What would be a good recommendation for a routing library in 2023? I need to propose a few options to my team.

The criteria are as follows:

  • Relatively simple OpenAPI integration
  • There’s no tightly coupled front-end (no Clojurescript); it’s backend-only
  • And no GraphQL either; only REST

Personally, I favor Reitit, but some team members have experienced issues with it in the past, primarily due to the complexities of Malli coercion mechanics. Their arguments can be summarized as follows:

  1. Reitit has excessive functionality that promotes feature creep.

While I somewhat agree with this, Reitit can initially seem challenging to comprehend, but this is typically only in the beginning.

  1. Metosin is unresponsive when it comes to addressing defects, making it difficult to manage them without forking the project.

Since I’ve never used Reitit extensively to the point of encountering unusual bugs, I can’t provide a comment on this.

  1. Malli works well for CRUD websites but does not offer many robust features for more complex applications.

Again, I have never used Malli personally, so I can’t say anything about this. I’m also unsure if we’ll need type coercion at all – be that with Malli, Schema, or Spec, particularly in the early stages of the project.

While the team is not entirely against using Reitit, they would appreciate hearing convincing reasons. If I can’t persuade them to opt for Reitit, what other possible choices exist at present?

I have a prejudice against Compojure. Correct me if I’m wrong, but it appears that once you start using Compojure, you tend to want to extend it with CompojureAPI, which I’m not fond of. But at that point, why not simply use Reitit? Is that a fair assumption?

What about juxt/bidi? Perhaps rather than trying to convince everyone to go for Reitit, which may be regarded as an overkill for our relatively simple, non-complex app, maybe choosing bidi would make everyone happy?

1 Like

Yup. We use bidi and love it - we’ve done tens of projects with it in the last 9+ years, and it just works. We mainly use it for backend api routing, like you mention, but we also have deployed it to route on the front-end for SPAs, and also occasionally made use of the reverse routing, all great, zero problems.

Before that we used compojure too, but when we discovered bidi, moving away from the macros-for-macros sake design of compojure was a huge relief.

1 Like

We’re going to want to hook up OpenAPI/Swagger. How difficult would that be with bidi? Would we need to look into juxt/yada? If we decide to go that route, wouldn’t that somehow undo our efforts “to keep it simple” and it eventually may end up being more complex than simply starting with Reitit?

Based on your criteria, I would definitely recommend reitit over bidi, and I wouldn’t recommend Compjure at all. I’ve been using reitit for years and I’m fairly certain it’s the best all round routing library available for Clojure (I haven’t used it for ClojureScript so I couldn’t comment there.)

I also want to point out that juxt themselves seem to favour reitit over their own bidi:

(search for reitit on that page, the recommendation is in the Libraries section.)

To address your enumerated points:

  1. Can you give examples of “excessive functionality” and any feature creep?

  2. Again, can you give examples? I subscribe to github issue updates and I regularly see issues being closed.

  3. That doesn’t actually make sense and is stated very vaguely. If you require type coercion, you can very easily just use Clojure Spec to begin with and I’m reasonably certain that will cover the majority of simple cases. You absolutely don’t have to use Malli at all with reitit as it’s completely optional, and as you have stated you can use Spec or Schema instead.

There are a few gotchas which you need to keep in mind (for example handling of preflight requests), but once you are aware of these it really becomes a pleasure to use.

I also believe that reitit has the best performance of all Clojure routers, at least according to metosin video from a few years back and I’m reasonably certain that’s still the case today.

2 Likes

That I don’t know about - sorry. (:

Reitit has upcoming support for OpenAPI (it’s already there in the latest dev releases).
I agree that it might seem a bit intimidating to setup at first, but you basically need to do that once, and adding routes later is fairly simple.

full disclosure, I’ve recently started working at Metosin, but have been using reitit for a few years before, with no issues.

Hi @ilemming and thanks for bringing your concerns about reitit and malli. I’m the lead dev on both, so happy to answer and to discuss ovet slack huddle etc more.

  1. Reitit has excessive functionality that promotes feature creep.

If not promotes, enables at least :wink: Reitit is meant to be a low-lever routing library with no opinions. People with strong opinions seem happy with it, people who want to get shit done maybe not - It has a lot of features and options and this makes it not super friendly to newbies.

Supporting both middleware and interceptors, all of spec, schema and malli also make development of some common features (like openapi3-support) much more work. Price of no opinions.

  1. Metosin is unresponsive when it comes to addressing defects, making it difficult to manage them without forking the project.

Sad to hear that. I’m personally active in #malli slack and #reitit has 1000+ people, so help is usually available.

Most of the energy with reitit in the last year has gone to shipping the OpenAPI3 feature, which spans over 5 of libraries. We haven’t had time to look much over smaller issues while doing that, but contributions have always been welcome. OpenAPI3-support should land soon, latest alpha has already most of the things in it.

We have also adjusted how we work at open source, will write a blog post about that.

  1. Malli works well for CRUD websites but does not offer many robust features for more complex applications.

I’m eager to hear more of this. A lot of people (us included) are using malli as de facto modelling library for all projects. We have been early adopters for all of schema, spec and malli and have tried to make malli the library of our dreams for us and the community.

About Compojure and Compojure-api (also lead-dev of the latter): If you feel that is enough for you, go for it. It is still used a lot and I know there are people who think it’s still the best thing for Clojure web-apis. That said, we don’t recommend it for new projects as reitit is the successor for it and technically superior. Compojure-api has most of the batteries of the box, but you can do most of the things top of reitit. Compojure-api is not actively developed, doesn’t have openapi3 & malli integration.

We have a vision of creating a small opinionated framework on top of reitit, with reitit + async + ring + malli + openapi + all relevant mw packaged to quickly build web-apis - like c-api has. I have looked at most of the modern best-of-class web frameworks from other languages (js/ts, rust, elixir, kotlin) not to re-invent the wheel with this. Would use it myself in my current project and I think Clojure community would definitely need something like that. We already have things like kit and biff partially in this space, should talk to the guys before starting. Has been on the backlog for 4 years now :see_no_evil:.

8 Likes

Hey Tommi. Thank you for spending some time to write a detailed reply. Just to be clear, I personally love Reitit. In my opinion, as of 2023, it looks like the clear winner for production-grade apps. I honestly don’t know why my teammates are making such a fuss about it; I didn’t expect any pushback when I suggested using it. I only wish I knew it to the depths required to advocate for it better.

I’m someone who found reitit too confusing.

  • The way it loaded middleware just didn’t fit into my head. Every time I came back to the routing file, I needed to grok the code again to be able to make changes. Also, Reitit’s own code is not “outsider friendly”, in my opinion.
  • I was using their OpenAPI3 support when developing my router and all the annotation required for generating a beautiful Swagger UI was making the routing code unreadable. My initial thought was that maintaining OpenAPI documentation in the same place as maintaining the routes would make them easier to keep in sync, but I have since changed my opinion. Maintaining OpenAPI documentation is still the same amount of effort (whether in code or as a openapi.json file manually). In fact, a separate openapi.json file manages to separate concerns in my head, making it easier to maintain.
  • I needed to dig through Reitit code to see how to support various OpenAPI features, though in fairness this is not a problem with Reitit. The feature was in development and it was not fair to expect every OpenAPI spec feature would work.

Eventually, it got to a point where I was so unhappy that I decided to look for an alternative. I’d been avoiding Pedestal because it uses Interceptors which are different from the Ring Middleware wrapper style I was used to. But when I actually read through their guides, I felt it was a much better and cleaner fit for me.

As you have said in your parent post – Reitit can initially seem challenging to comprehend, but this is typically only in the beginning. – it might just be that Pedestal documentation made it easier for me to get started than Reitit documentation. But I’m really happy with Pedestal at this point in time.

Re: OpenAPI3 support, I am using Swagger-UI as a static resource on my project and maintaining the openapi.json file by hand. This is actually really easy, if you are starting a new project. The relevant process is described in this thread: Slack

I happily migrated from Compojure to Reitit in 2021.

There is a bit of a learning curve with Reitit, but no more than there was with Compojure originally.

Using reitit-ring has been a great way to use familiar concepts from Compojure and Compojure-Api, but using a nice hash-map to define everything

Reitit-ring can use the ring middleware very easily and Reitit provides lots of middleware itself. I added a mulog middleware by tweaking one line from the Compojure example

Compojure is simpler for simple things and there are some nice defaults and tools.

Reitit is define using a hash-map and I found it easier to do more with Reitit.

The :practicalli/service template from Practicalli Project Templates creates a working web service with an API using Reitit and Swagger

2 Likes