Hunchentoot is a web server and not a heavy weight web development framework. It provides methods to implement URL paths for static files, directory paths and with regex’s, very much like the mainstream web servers. It does not provide an easy way to define routes for a REST API. When you use Hunchentoot without a web development framework you will probably want something to make route definition easier.

There are a few options for building REST APIs available in frameworks, Hunchentoot derivatives or other web servers but I wanted to implement REST routes with the original Hunchentoot web server. I found three libraries that can do this: simple-routes, Froute and easy-routes.

Simple-routes is the simplest and easiest to use. Routes are defined in a list similar to Hunchentoot’s *dispatch-table*. It supports variables in the URL segments but there is no support for middleware1 type functionality.

Froute is the most powerful of the three. It is based on CLOS and is designed so it can be used with any web server although only a Hunchentoot connector is currently implemented. Routes are defined as CLOS classes and even though middleware is not a specific feature the documentation gives an example on how to use class inheritance to implement such functionality. The power of being CLOS based also makes this library the most complex to use.

Easy-routes has the concept of decorators which are functions that execute before the route body so it can be used to implement middleware functionality. Unlike Clack’s middleware which are defined at a central place for all routes, decorators need to be applied to each route handler individually. It’s not quite there, but close enough.

The lack of middleware options disqualified simple-routes for me and Froute looked like it provides everything I need, and more, but with much greater complexity than easy-routes. I decided to use easy-routes with the option to switch to Froute when I needed the extra capability.

Hunchentoot takes an “acceptor” argument at startup. Easy-routes provides two options: easy-routes-acceptor and routes-acceptor. Easy-routes-acceptor first executes all the route handlers and if no suitable handler is found it executes the normal Hunchentoot request handlers. The routes-acceptor executes only the route handlers and returns an 404 NOT FOUND error if no suitable handler is found.

I use routes-acceptor because it ensures that only requests with explicitly defined handlers are handled. With the easy-routes-acceptor it is too easy to create a security hole with some default Hunchentoot request handler that catches non-existent routes. It can be burdensome to use this approach for handling static files but I run Hunchentoot behind Nginx which also handles the static files.

The table summarises the main features I investigated:

  simple-routes easy-routes Froute de.setf.http
Web server Hunchentoot Hunchentoot Hunchentoot (can be expanded to others) Hunchentoot
REST routes Yes Yes Yes Yes
Argument extraction from URL Yes Yes Yes Yes
Dispatch based on HTTP method Yes Yes Yes Yes
Middleware No Decorators CLOS inheritance Yes
Fallback for undefined routes Hunchentoot easy-handler framework None or Hunchentoot easy-handler framework None None
Learning curve Negligible Minimal Medium. Requires some CLOS knowledge. High. Requires CLOS knowledge.

Addendum

James Anderson introduced me to de.setf.http after the post’s initial publication. de.setf.http is based on CLOS and it seems to be at least as powerful as Froute, if not more.

The library implements HTTP processing using composable functions. While it does not have an explicit concept of middleware, obtaining middleware-like functionality forms a natural part of the request handlers.

The only documentation is the README file and two sparsely commented examples. This library works very different than most others and relies heavily on CLOS. The novel approach and the lack of documentation make for a steep learning curve which may be worthwhile if you like the library’s approach.

  1. Middleware are functions that run before or after the main request handler. They are called as part of the request handling process and not by the handler. This makes them ideal to handle general functions like setting up a database connection, performing authorisation or any task which is not part of a particular request handler.