Tazjin's blog

Best of both worlds: Using Hamlet and web-routes with Happstack

As mentioned previously I’ve used the Happstack web framework for this blog (and some other things as well) and that has prompted the question of why I don’t use Yesod or Snap instead.

Yesod is a framework that takes care of a lot of things for you, it uses embedded DSLs for setting up your routes and the appropriate Handler functions, it has everything you need for handling forms, authentication, sessions, its database abstraction layer, Persistent, does automatic migrations, serialization and everything else you need to access your database. It also has the so-called “Shakespearean” template languages for generating HTML, CSS and JavaScript.

I’m not a fan of how Yesod takes a lot of low-level stuff out of your hands, I like to have control over every single aspect of the programs I write. That’s not to say that the Yesod-way is generally a bad idea, it’s just not the right idea for me.

As for Snap, I haven’t taken a serious look at it so I won’t comment too much on it. Its HTML templating system, Heist, does not appeal to me though.

The great thing about all three frameworks though is the fact that many of their individual features can be used with the other frameworks as well, and that’s what this blog entry is going to be about. Yesod uses a type-safe routing system by default which takes care of URL generation and parsing, thus making it possible for the compiler to catch errors like simple typos in URLs.

This type-safe routing system is in fact based on web-routes by Jeremy Shaw from the Happstack team, and it’s the first thing I’m going to demonstrate.

In from Yesod come Hamlet, the awesome HTML templating language, and Lucius for CSS.

So lets talk about imports and GHC extensions:

{-# LANGUAGE OverloadedStrings, QuasiQuotes, TemplateHaskell #-}
module Main where

import Prelude hiding    (reverse, show)
import Control.Monad     (liftM, msum)
import Data.List         (sort)
import Data.Text         (Text, pack, unpack, reverse, toUpper)
import Happstack.Server
import Text.Hamlet
import Text.Lucius
import Web.Routes
import Web.Routes.Happstack
import Web.Routes.TH

import qualified Prelude as P

OverloadedStrings allows using string literals in places where String-like data types are being used without explicitly packing and unpacking them. In this example this is merely a cosmetic thing, it’s not necessary but I like to use it as the default.
QuasiQuotes are used for domain-specific languages, in this example Hamlet and Lucius. You’ll see how they work later in case you’re not familiar with them.
TemplateHaskell adds functionality that is remotely similiar to Lisp macros, simply put it’s for functions that generate code.

I’ve hidden the show function from Prelude because we’ll use a simple modified form of it for Text instead of Strings.

show :: Show a => a -> Text
show = pack . P.show

Next are the data types which will hold our URLs:

data EchoType = Reverse| Sort | Upper

data Sitemap
    = Home
    | Plus Int Int
    | Echo EchoType Text

$(derivePathInfo ''EchoType)
$(derivePathInfo ''Sitemap)

This will generate the following sitemap:


(The # denotes variables).

The function derivePathInfo is a Template Haskell function from Web.Routes.TH which takes care of mapping URLs to the appropriate routes, the benefit of this is not having to do it manually but it doesn’t let us interfere with with how these routes are set up. If we wanted the Echo-subpage to be available at a different URL than /Echo we’d have to configure everything ourselves. There is a detailed explanation for how to do that in the Happstack Crash Course.

Next we need a function to map our routes to the handlers.

siteRoute :: Sitemap -> RouteT Sitemap (ServerPartT IO) Response
siteRoute url = 
    case url of
        Home         -> appRoot
        (Plus i1 i2) -> plusHandler i1 i2
        (Echo et t)  -> echoHandler et t

There is nothing extraordinary in here, just note that the type is different from the ServerPart Response you would usually see in Happstack applications, but they’re fully compatible because everything needed is defined in Web.Routes.Happstack.

Enter Lucius and Hamlet:

siteLayout :: HtmlUrl Sitemap -> HtmlUrl Sitemap
siteLayout body = [hamlet|
$doctype 5
    <link href="/site.css" rel="stylesheet" media="all" type="text/css">
    <div class="container">
      <p><a href=@{Home}>Home</a>

stylesheet :: Css
stylesheet = [lucius|
body {
    background: #EBEBEB;
    height: 100%;
.container {
    padding: 20px;
|] undefined -- no URL rendering in this css

A Hamlet template is of type HtmlUrl a with a being the route data type. This is because of the way Hamlet takes care of URL rendering, a Hamlet template is ‘run’ by supplying it with the rendering function. But we’ll get to that in a moment.

The function siteLayout takes another Hamlet template as an argument and inserts it as the body. I do know that the layout is not a serious layout, but I’m a backend guy and not a frontend guy and didn’t feel like putting together a longer layout example.

You can see that we have a link pointing to @{Home}, Hamlet automatically inserts the real URL here.

Nothing extraordinary is going on the stylesheet, Lucius is almost plain CSS. It allows for nesting of CSS elements and variable/URL interpolation as well but we’re not making use of that here which is why I’ve supplied it with undefined as the URL rendering function.

When writing the handlers for our site we will need to run the Hamlet template. The function needed for that is of type

render :: url -> [(Text, Text)] -> Text

which web-routes automatically generates for us. We can get it by calling askRouteFn inside the RouteT monad. For some reason though the returned function will be of type

render :: url -> [(Text, Maybe Text)] -> Text

instead, so we need to convert it. I hope that this gets taken care of in a future version of either Hamlet or web-routes.

convRender :: (url -> [(Text, Maybe Text)] -> Text)
           -> (url -> [(Text, Text)]-> Text)
convRender maybeF = 
  (\url params -> maybeF url $ map (\(t1, t2) -> (t1, Just t2)) params)

renderFunction :: MonadRoute m => m (URL m -> [(Text, Text)] -> Text)
renderFunction = liftM convRender $ askRouteFn

Lets get to the handler functions.

appRoot = do
    urlF <- renderFunction
    ok $ toResponse $ siteLayout ([hamlet|
    <p>Small demo application for Happstack, web-routes, Hamlet and Lucius.
    <p>Add two numbers by going to /plus/int1/int2, e.g. 
        <a href=@{Plus 13 37}> here.
    <p>Also other boring stuff, for example #
        <a href=@{Echo Reverse "Penguins!"}>this.
    |]) urlF

plusHandler n1 n2 = do 
    urlF <- renderFunction
    ok $ toResponse $ siteLayout ([hamlet|
    <p>Input numbers are #{n1} and #{n2}
    <p>Sum is: #{n1 + n2}
    |]) urlF

echoHandler et t = do
    urlF <- renderFunction
    ok $ toResponse $ siteLayout ([hamlet|
    <p>Input is #{t}
    $case et
        $of Reverse
            <p>Method is "reverse"
            <p>Result is #{reverse t}
        $of Upper
            <p>Method is "upper"
            <p>Result is #{toUpper t}
        $of Sort
            <p>Method is "sort"
            <p>Result is #{pack $ sort $ unpack t}
    |]) urlF

Note how it’s possible to execute Haskell code inside Hamlet templates. As you can see Hamlet also supports simple conditionals, in addition to case it also has if, forall and Maybe. Look at the Yesod book for more information on that.

Last, but not least, is the main function that runs our web server for us.

sitemapSite :: Site Sitemap (ServerPartT IO Response)
sitemapSite = setDefault Home $ mkSitePI (runRouteT siteRoute) 

main :: IO()
main = simpleHTTP nullConf $ msum [
      do dirs "site.css" $ nullDir
         setHeaderM "Content-Type" "text/css"
         ok $ toResponse $ renderCss stylesheet
    , implSite "http://localhost:8000" "/site" sitemapSite
    , seeOther ("/site" :: String) (toResponse ())

We embed a web-routes based site in the standard Happstack routing with implSite and define the domain and path that it is going to run on, because those are needed for URL rendering. To do that we first convert our finished routing function (siteRoute) to a Site and then map the default path, i.e. ‘/’ - the root of our site, to an arbitrary data type, in this case Home.

Our stylesheet is generated by using renderCss, which also takes care of minimization for us. Because we aren’t reading the CSS from a file it’s important to set the Content-Type manually.

So that’s how you use Hamlet and web-routes with Happstack. The full application code is available here as a complete file.