Earlier this year, I announced the first version of servant. The goal was to speed up webservice development by getting rid of as much boilerplate as possible. It definitely was an improvement but not quite enough. Assisted this time with Sönke Hahn (of Nikki and the Robots fame) and Julian Arni, two of my now ex-coworkers, we completely rewrote everything and came up with a new way to describe webservices APIs in Haskell. This post introduces servant 0.2, which now lives in its own github org.

<section class="level1" id="webservice-apis-as-types">

Webservice APIs as types

The meat of the new servant is the fact that we now describe webservice APIs as types. And we write these types by combining “API combinators”, that each have a precise meaning. We use type-level string literals for static fragments of an endpoint.

Let’s build a webservice to manage an imaginary userbase.

data User = User
  { firstName :: !Text
  , lastName  :: !Text
  } deriving (Eq, Show, Generic)

instance ToJSON User
instance FromJSON User

-- corresponds to: GET /users
-- returning a JSON-encoded list of User's.
type UserAPI = "users" :> Get [User]

userAPI :: Proxy UserAPI
userAPI = Proxy

server :: Server UserAPI
server = listUsers

  where listUsers = return $ Data.Map.elems users

        users :: Data.Map.Map Int64 User
        users = Data.Map.fromList [ (0, User "john" "doe")
                                  , (1, User "jane" "doe")
                                  ]


-- spin up a warp server that serves our API
main :: IO ()
main = run 8080 (serve userAPI server)

Endpoints are separated from one another with :<|>, similar to Alternative’s <|> separating alternative parsers. URL Captures, GET parameters, request bodies and headers are strongly typed and only made available to server handlers by explicit mentions in the type for the corresponding endpoint. They automatically become arguments to the handler functions.

Here’s our previous example expanded to make use of these few features.

data User = User
  { firstName :: !Text
  , lastName  :: !Text
  } deriving (Eq, Show, Generic)

instance ToJSON User
instance FromJSON User

               -- list all users: GET /users
type UserAPI = "users" :> Get [User]

               -- view a particular user: GET /users/:userid
          :<|> "users" :> Capture "userid" Int64 :> Get User

               -- search for users: GET /users/search?q=<some text>
          :<|> "users" :> "search" :> QueryParam "q" Text :> Get [User]

userAPI :: Proxy UserAPI
userAPI = Proxy

server :: Server UserAPI
server = listUsers
    :<|> viewUserById
    :<|> searchUser

  where listUsers = return $ Data.Map.elems users

        -- the captured userid gets automatically passed to this function
        viewUserById userid = maybe (left (404, "user not found"))
                                    return
                            $ Data.Map.lookup userid users

        -- the 'q' GET parameter's value automatically passed to this function,
        -- if present.
        searchUser Nothing = return [] -- no ?q=something specified
        searchUser (Just q) = return
                            . Data.Map.elems
                            . Data.Map.filter (userLike q)
                            $ users

        userLike q usr = q `isInfixOf` firstName usr
                      || q `isInfixOf` lastName usr

        users :: Data.Map.Map Int64 User
        users = Data.Map.fromList [ (0, User "john" "doe")
                                  , (1, User "jane" "doe")
                                  ]

-- spin up a warp server that serves our API
main :: IO ()
main = run 8080 (serve userAPI server)

This combinator-based approach lets us define server-side behavior and data-dependency with types, letting the library handle all the GET parameter/capture/request body extraction and letting you worry only about what you need to do with these values. But that doesn’t stop there. servant can also generate Haskell and Javascript functions to query servant webservices and API docs.

</section> <section class="level1" id="want-to-read-more-about-this">

Want to read more about this?

  • We have a Getting Started slideshow that walks you through the basics of the library.
  • We have haddocks for the 4 packages that are full of examples and explanations.
    • servant, API types and serving
    • servant-client, for generating haskell functions to query APIs automatically
    • servant-jquery, for generating javascript functions to query APIs automatically
    • servant-docs, for assistance in API docs generation
</section>