Type safe web services in Haskell with Servant
I enjoy writing web services and working with Haskell. So much so that I wrote about creating a REST API in Haskell a while ago. I am happy to say that I think there is a better way now.
I recently discovered Servant. It is one of the coolest libraries I have ever seen. It is for describing type-safe web services. That means you know the types of the inputs and outputs for every endpoint.
Although it appears to be primarily designed for implementing API servers, Servant can do a lot more. Since it is a way to formally describe an API, it can be used to generate documentation and clients as well. With a single API description, you can implement a server, then get documentation and clients for free.
All of that can be accomplished with hardly any boilerplate. Before I get into an example, I should point out that Servant’s tutorial is great — seriously. If this post piques your interest, go read the tutorial to get started.
So what does Servant look like? Here is the description for a simple API.
import Servant.API type API -- GET /things = "things" :> Get '[JSON] [Thing] -- GET /things/:id :<|> "things" :> Capture "id" Integer :> Get '[JSON] Thing
That describes two endpoints,
GET /things and
The former returns a list of
Things and the latter returns a single
Both endpoints return JSON.
Thing is a normal Haskell data type that can be converted to JSON.
Although the API is represented as a type, it is convenient to get at it from the value level. To do that, we can use a proxy.
import Data.Proxy api :: Proxy API api = Proxy
Now that we have a description of our API, we can go ahead and implement a server. We will need to define two handlers.
import Control.Monad.Trans.Either -- This is Servant's default handler type. type Handler a = EitherT ServantErr IO a getThings :: Handler [Thing] getThings = do things <- getThingsFromDB return things getThing :: Integer -> Handler Thing getThing thingID = do maybeThing <- getThingFromDB thingID case maybeThing of Just thing -> return thing Nothing -> left err404
Notice that the handlers don’t really have to think about HTTP. They get the input defined by the API and return something of the correct type. Servant handles converting the inputs and serializing the outputs as JSON.
By combining the handlers, we can create our complete server.
server :: Server API server -- GET /things = getThings -- GET /things/:id :<|> getThing
Note that we have to list the handlers in the same order as the API. (This connascence of position is the only thing I don’t like about Servant so far.) And to actually make the server available we need to serve it through Warp.
import Network.Wai.Handler.Warp main = run 8080 (serve api server)
As I mentioned before, you can get documentation and client code from your API for free. To see an example of this, check out Factory, my example Servant web service. It does everything that Servant is capable of right now, as of version 0.4.
If you are interested in learning more about Servant, I am giving a talk this Wednesday and broadcasting it over Hangouts on Air. If you can’t make it, the talk will be available afterwards on YouTube. (The slides are now available on this site.)