Tommi Reiman

Welcome Reitit 0.2.0!

After a few months in the making (and a holiday in between), we are proud to release a new version of reitit, the fast routing library for Clojure/Script:

[metosin/reitit "0.2.0"]

It contains a lot of new things, improvements to the core router and some fixes too. Here's a quick tour:

reitit-frontendLink to reitit-frontend

A new module dedicated to frontend routing. It builds on top reitit-core adding helpers for parsing of query-string, fragment and html5 history and support for Keechma-style controllers. We have been using the controllers for some time now and are really happy how it simplifies the state management in the frontend. The module docs haven't been polished yet, but the example-apps should show how to use the module:

reitit-middlewareLink to reitit-middleware

A common question has been how do you download/upload a file with reitit?. Answer is that reitit-ring is fully compatible with Ring, so anything you can do with Ring, you can do with reitit-ring. To make things easier, we decided to lift some common ring middleware into data-driven middleware intoduced by reitit. These include:

  • content-negotiation, request and response formatting (via Muuntaja)
  • multipart-handling (via ring default middleware)
  • exception-handling (polished version from compojure-api)

reitit-middleware module makes reitit-ring feature wise more on par with popular web frameworks like compojure-api. Below is an example app with all the default middleware in place:

(ns example.server
  (:require [reitit.ring :as ring]
            [reitit.swagger :as swagger]
            [reitit.swagger-ui :as swagger-ui]
            [reitit.ring.coercion :as coercion]
            [reitit.ring.middleware.muuntaja :as muuntaja]
            [reitit.ring.middleware.exception :as exception]
            [reitit.ring.middleware.multipart :as multipart]
            [ring.middleware.params :as params]
            [ring.adapter.jetty :as jetty]
            [muuntaja.core :as m]
            [ :as io]))

(def app
        {:get {:no-doc true
               :swagger {:info {:title "my-api"}}
               :handler (swagger/create-swagger-handler)}}]

        {:swagger {:tags ["files"]}}

         {:post {:summary "upload a file"
                 :parameters {:multipart {:file multipart/temp-file-part}}
                 :responses {200 {:body {:name string?, :size int?}}}
                 :handler (fn [{{{:keys [file]} :multipart} :parameters}]
                            {:status 200
                             :body {:name (:filename file)
                                    :size (:size file)}})}}]

         {:get {:summary "downloads a file"
                :swagger {:produces ["image/png"]}
                :handler (fn [_]
                           {:status 200
                            :headers {"Content-Type" "image/png"}
                            :body (io/input-stream 
                                    (io/resource "reitit.png"))})}}]]

        {:swagger {:tags ["math"]}}

         {:get {:summary "plus with spec query parameters"
                :parameters {:query {:x int?, :y int?}}
                :responses {200 {:body {:total int?}}}
                :handler (fn [{{{:keys [x y]} :query} :parameters}]
                           {:status 200
                            :body {:total (+ x y)}})}
          :post {:summary "plus with spec body parameters"
                 :parameters {:body {:x int?, :y int?}}
                 :responses {200 {:body {:total int?}}}
                 :handler (fn [{{{:keys [x y]} :body} :parameters}]
                            {:status 200
                             :body {:total (+ x y)}})}}]]]

      {:data {:coercion reitit.coercion.spec/coercion
              :muuntaja m/instance
              :middleware [;; query-params & form-params
                           ;; content-negotiation
                           ;; encoding response body
                           ;; exception handling
                           ;; decoding request body
                           ;; coercing response bodys
                           ;; coercing request parameters
                           ;; multipart
        {:path "/"
         :config {:validatorUrl nil}})

(defn start []
  (jetty/run-jetty #'app {:port 3000, :join? false})
  (println "server running in port 3000"))

The syntax is more verbose than in compojure-api, but then again: there are no macros or hidden defaults. User is in control of everything.

reitit-httpLink to reitit-http

Part of the 0.2.0 release, but still considered work-in-progress. It builds on top of reitit-ring, but swaps the :middleware execution model with pedestal-style interceptors using the :interceptors key. Goal is to provide both an optional routing interceptor to Pedestal and a standalone implementation on top of ring-async.

reitit-sieppariLink to reitit-sieppari

Sieppari is a new super-alpha interceptor runner for Clojure (later also to ClojureScript). For the adventurous, it provides simple interceptor model with no external dependencies and supporting pluggable async (core.async, manifold and promesa for now).

As the version 0.0.0-alpha5 of sieppari suggests, it's not meant for production. Things like backpressure need to be addressed first.

Other StuffLink to Other Stuff

Besides the new modules, there are lot of impromevents to existing functionality. These include:

  • support for (duct-style) interceptor & middleware registries
  • new fast and correct path parameter decoder - for both jvm & js
  • improvements for swagger support for both schema & clojure.spec
  • automatic route name conflict resolution
  • guide for composable & dynamic routers
  • support for sequential routes

Next?Link to Next?

There are lot's of ideas what to do next, mostly written as gihub issues. Comments and contributions welcome on those. Here is a list of some of the topics:

But now, let's enjoy the 0.2.0 release. Big thanks to all the contributors!

You can also join #reitit channel in the Clojurians Slack.

Tommi Reiman