Write a client library for any web API in 5 minutes

servant lets us write request handlers for webservices in a quite straighforward way, without polluting your logic with encoding/decoding of all sorts. What may be less obvious is that you also get a somehow symmetric benefit too by being able to derive (without actually writing them) functions to query an API described by some servant API type. Here’s an example.

The Hackage API

Let’s write some functions to query a couple of endpoints of Hackage’s API. Let’s just consider the following ones:

/users/
GET: json -- list of users

/user/:username
GET: json -- user id info

/packages/
GET: json -- List of all packages

Let’s see what the output looks like by using curl:

This is enough to get us started.

Describing Hackage’s API as a type

First, some pragmas and imports:

Now, let’s write the API type that corresponds to those 3 endpoints we’re interested in.

Nothing fancy here, except that we clearly specify we are expecting the output to be in JSON (this will insert the appropriate Accept header).

Data types and JSON serialization

We also need some types to go with that: UserSummary, Username, UserDetailed, Package. Here they are, along with JSON deserialization instances.

Deriving functions to query hackage

Finally, we can automatically derive our client functions:

And here’s some runnable code to actually check that everything works as expected:

Here’s a sample run:

$ cabal run hackage
Preprocessing executable hackage for servant-examples-0.3...
Running hackage...
2460 users
Enter a valid hackage username
AlpMestanogullari
AlpMestanogullari maintains 20 packages
130 monad packages
Right ()

Code

The whole code is available in servant’s repo, under the servant-examples/hackage directory.