servant 0.4 released

Since the last major release, a lot happened in and around servant. Definitely enough to justify a new release. This post announces new releases of all the servant packages, with many local changes but also some major ones that affect all packages. You can find the detailed changelogs at the end of this post, but here are a few major features you may want to learn about. This website also features a new tutorial that explains how to use servant from scratch.

Multiple content-type support

servant combinators are not JSON-centric anymore.

If you had an API type like the following with servant 0.2.x:

You now have to change it to:

Wherever applicable (i.e., ReqBody and all the combinators that correspond to an HTTP method), you can now specify all the content types in which you want to want to be able to encode/decode values. As you can see, we use the DataKinds GHC extension to let you specify a type-level list of content-types, which are simple dummy types:

In servant-server, a list of these content-types as the first argument of a method gets translated into a set of constraints on the return type:

Which have unsurprising instances:

Thus, servant checks at compile-time that it really can serialize your values as you describe. And of course, it handles picking the appropriate serialization format based on the request’s “Accept” header for you.

(For ReqBody, deserialization is involved. For servant-client, the logic goes the other way around - serialization for ReqBody, deserialization for methods.)

servant-blaze and servant-lucid

Declaring new content-types, and the associated constraints for them, is quite easy. But to make it easier still, we are also announcing two new packages: servant-blaze and servant-lucid. To use them, just import their HTML datatype:

And User will be checked for the appropriate (e.g. ToHtml) instance.

Response headers

There was no easy way so far to have handlers add headers to a response. We’ve since come up with a solution that stays true to the servant spirit: what headers your response will include (and what their types are) is still enforced statically:

servant-docs and servant-client are also response-header aware.

Our current solution isn’t something we are entirely happy with from an internal persepctive. We use overlapping instances for all the handlers, which some might think is already a problem. But more concretely, there’s the threat of an exponential blowup in the number of instances we have to declare. And that can be a problem for end users too, if they decide to further modify behavior via a similar mechanism. But these things thankfully don’t seem to pose any immediate problems.

Running handlers in other monads than EitherT

An often-requested feature has been easy use of datatypes/monads besides EitherT. Now we believe we have a good story for that (thanks in large part to rschatz). To convert from one datatype to another, all you need to do is provide a natural transformation between them. For example:

The new ServerT type synonym takes an extra paramer that represents what datatype/monad you are using over your handlers (instead of EitherT ServantErr IO).

(Note that we also provide a number of pre-existing Nats, which are an instance of Category. We could have used

readerServer = enter (generalizeNat . (runReaderTNat "hi")) readerServerT

instead (with . being from Control.Category).)

Note that the datatypes you can use now don’t even need to be monads!

Left

We also changed the default type of handlers from EitherT (Int,String) IO a to EitherT ServantErr IO a. Now it is possible to return headers and a response body in the Left case.

We also now export function errXXX (where XXX is a 300-599 HTTP status code) with sensible reason strings.

BaseUrl

We also changed the client function from servant-client so that, instead of returning various functions that each take a BaseUrl argument (often in inconvenient argument positions), the client function itself takes a BaseUrl argument, and the functions it returns don’t. So the type of client went from

To

Complete CHANGELOGs

Website

We also decided to switch to hakyll in order to be able to have a blog as well as some static pages that collect tips and tricks that people have found. We also used this opportunity to rewrite the getting started into a more informative tutorial, now available here.

Conclusions

As you can see, more and more information is getting encoded statically - the types are becoming a pretty rich DSL. In order to keep the noise down, do what you normally do: abstract away common patterns! If your endpoints always return the same content-types, make aliases:

There’s still an outstanding issue with the errors servant returns when a request doesn’t get handled. For example, if the path of a request, but not the method nor the request body, match, rather than returning a 405 (Method Not Allowed) we return a 400 (Bad Request), which is not the desired behavior. Andres Löh made some great suggestions for how to improve our routing time complexity, and hopefully we can integrate a fix for this issue when we tackle that.

We also merged our repos into servant. Please use that repo exclusively for PRs and issues (we’ll get rid of the others eventually).

Special thanks to the Anchor team from Australia, Matthew Pickering, Daniel Larsson, Phil Freeman, Matthias Fischmann, rschatz, Mateusz Kowalczyk, Brandon Martin and Sean Leather who’ve contributed from little fixes to whole new features. Several companies are now running servant-powered web applications.