Yesod, Angular, Bower, and NodeJs Tips

By John Lenz. February 4, 2015.

A while back I posted about Yesod and Angular and testing, but those articles were written at the start of my main project with Yesod and Angular. For the most part what I described in those earlier posts works well, but here is a list of organizational tips after my experience writing a large Yesod and Angular project.

File Layout / Scaffold

I started with the yesod scaffold, but made several changes and additions. Here is the list of all the files and directories I am currently using.

First, the files from the yesod scaffold. The main change was move the code into the lib subdirectory, remove the static folder, and switch the static subsite to the embedded static subsite (see more details below).

To replace the static subdirectory, I use bower.

Angular unit testing is done through karma, and to install karma I use the following:

Now the angular code

Finally the End 2 End tests written in Haskell using the webdriver, hspec-webdriver, and webdriver-angular packages.

Bower and Yesod

Bower and the embedded static subsite work very well together. By listing in bower.json the exact dependencies, it means that whatever computer I develop on will have the exact versions of angular, moment.js, and so on. The embedded static subsite then allows picking out various files and directories and embedding them into the application. For example, a snippet of lib/StaticFiles.hs is

  #define DEV_BOOL True
  #define DEV_BOOL False
  mkEmbeddedStatic DEV_BOOL "staticSubsite" [

      -- local angular modules
      embedNgModules "ng" "angular" uglifyJs

      -- bootstrap
    , embedFileAt "css/bootstrap.min.css" "bower_components/bootstrap/dist/css/bootstrap.min.css"
    , embedDirAt "fonts" "bower_components/bootstrap/dist/fonts"

      -- angular
    , concatFiles "js/angular.js"
        [ "bower_components/angular/angular.js"
        , "bower_components/angular-bootstrap/ui-bootstrap-tpls.js"
        [ "bower_components/angular/angular.min.js"
        , "bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js"


For other libraries like spin.js which do not come pre-compressed, the concatFilesWith function with a compressor like uglifyJs can minify the library at compile time.

Development Practice

A big part of Angular is testing and my plan which I described in this post worked exceptionally well for a test-driven development process. I tend to focus only on one of three tasks at a time:

Hamlet variable interpolation

While rare, at the beginning I used a few variable interpolations while writing the view in the templates/*.hamlet files, mostly for information about the currently logged in user and what permissions/resources/pages they could access. But after a while I found this suboptimal. These values angular did not know about, couldn't be fed through filters, and especially felt weird since formatting for display was taking place in two places. So instead I removed all variable interpolation from these templates. Instead, I interpolate the values into an angular module and then access them via dependency injection. To do so, inside defaultLayout (actually navLayout, see below), I have code

toWidget [julius|angular.module("myapp:config", []);|]

This creates a new angular module (because of the second parameter) which can be relied upon to always exist (since it is in defaultLayout), so that later in my actual handlers, I can write code like

muser <- liftHandlerT maybeAuthId
toWidget [julius|angular.module("myapp:config").constant("user", #{maybe Null toJSON muser});|]

and similar interpolations for other values. Then my actual angular modules can depend on "myapp:config" and access the values via dependency injection. This way I can export the raw values from the server and keep all the formatting inside one place, in the angular filters.

Since this javascript is constantly changing, it makes no sense for it to be split into a separate file and should instead be embedded directly into the HTML file. Therefore, I have no implementation of addStaticContent in my Yesod instance, which causes Yesod to stick the javascript inline at the bottom of the body. This is actually perfect, since the above config code is essentially all the javascript code on each page. (The majority of the javascript is the angular modules, served by the embedded static subsite via the angular generator).

Angular ng-app

The preferred method of bootstrapping angular is via the ng-app directive. This was a slight problem, since the defaultLayout includes a responsive navigation bar. To collapse the navigation bar via angular-bootstrap (I use that instead of jquery and the bootstrap javascript), the navigation bar needs to be inside the ng-app. Also, some pages don't use angular (besides the bootstrap collapse code). Minimized and compressed, angular plus angular-bootstrap is only slightly larger than jquery and bootstrap.js, plus the angular code will be needed if the user navigates to a page which uses it, so I decided to just use angular even for the pages that don't require it. To do so, in lib/Foundation.hs I have a function

navLayout :: Text -- ^ angular module name
          -> Widget
          -> Handler Html
navLayout angularModule widget = do
    -- code similar to the scaffold defaultLayout function
    -- using $(widgetFile "default-layout") and default-layout-wrapper.hamlet
    -- where default-layout.hamlet has <div ng-app=#{angularModule}>

Then in the Yesod instance I have

instance Yesod App where
  defaultLayout widget =
      navLayout "default-mod" $ do
          toWidget [julius|angular.module('default-mod', ['ui.bootstrap']);|]

Since there is no addStaticContent (see the previous section), this little snippet of javascript is embedded at the bottom of the page.

Angular $http and XSRF

The angular $http service has a few security protections. The first is XSRF protection, which I take advantage of via the following two functions which live in lib/Foundation.hs

-- | XSRF protection to match angular's $http service.
-- Sets the XSRF-TOKEN cookie which will cause angular's $http service to include a header
-- X-XSRF-TOKEN.  Warning: the XSRF-TOKEN cookie itself cannot be relied upon since it will be sent
-- in any request so the attacker does not need to forge it.  We must check only the header, since
-- only javascript running on our domain will be able to read the cookie and so generate the header.
setXsrfCookie :: MonadHandler m => m ()
setXsrfCookie = do
    req <- getRequest
    setCookie $ def { setCookieName = "XSRF-TOKEN"
                    , setCookieValue = encodeUtf8 $ fromMaybe (error "No Token!") $ reqToken req
                    , setCookiePath = Just "/"
                    , setCookieSecure = production

-- | Use in every REST route to protect from XSRF attacks.
checkXsrfHeader :: MonadHandler m => m ()
checkXsrfHeader = do
    req <- getRequest
    case lookup "X-XSRF-TOKEN" $ WAI.requestHeaders $ reqWaiRequest req of
        Just token | Just (decodeUtf8 token) == reqToken req -> return ()
        Nothing -> invalidArgs ["XSRF token is missing"]
        _ -> permissionDenied "XSRF token is incorrect"

Secondly, to protect against attacks where the attacker redefines the array constructor and loads the JSON as a script, angular will strip )]}'\n from the start of any JSON response. We can take advantage of the TypedContent typeclass to do this automatically for us, via the following code in lib/Foundation.hs.

-- | A JSON value that will be protected against attackers attempting to treat the JSON response as
-- javascript code, by prepending )]}',\n.  Currently this is only known to be possible when the JSON
-- is an array, but there is little harm in being careful.  Angular's $http service will automatically
-- strip this prefix.  Use as follows
-- >getSomeRouteR :: Handler SafeValue
-- >getSomeRouteR = do
-- >    ....
-- >    returnSafeJson x
newtype SafeValue = SafeValue Value
    deriving (Eq, Show, FromJSON, ToJSON)

returnSafeJson :: (Monad m, ToJSON a) => a -> m SafeValue
returnSafeJson = return . SafeValue . toJSON

instance ToContent SafeValue where
    toContent (SafeValue v) =
        case toContent v of
            ContentBuilder b Nothing -> ContentBuilder (toBuilder (")]}',\n" :: ByteString) ++ b) Nothing
            _ -> error "Value instance produced a different content"

instance HasContentType SafeValue where
    getContentType _ = typeJson

instance ToTypedContent SafeValue where
    toTypedContent v = TypedContent typeJson (toContent v)

ui-router, URLs, and html5Mode

Using the html5Mode of the $location service and ui-router requires the server side to ignore the route component of the URL. This can be easily done by adding something like

/somepath/*[Text] SomePathR GET

to the routes and then in the handler for SomePathR just ignore the [Text] parameter and serve the same HTML. Next, we need to set &lt;base href="/somepath/"&gt; inside the HTML so that angular knows which part of the URL is the routes. While this could be hard coded, using hamlet url interpolation is better. The main issue is that the default rendering of SomePathR [] will not have a trailing slash but angular requires a trailing slash in the base tag. While you could do something like &lt;base href="@{SomePathR []}/"&gt;, I instead customized urlRenderOverride to add a trailing slash, but only for the SomePathR [] route, other routes (including routes where the list is non-empty, should be left for the default rendering.

This then leads to an annoyance because due to the default cleanPath, a get to /somepath/ will redirect to /somepath. This actually works because the route SomePathR will match /somepath (the trailing slash rules of yesod are pretty confusing) so the handler will be run. But then immedietly angular will change the url using $location to include the trailing slash plus the url of the state. So we have a useless redirect that just flashes briefly without a trailing slash and then it comes back. Therefore, I changed cleanPath to

cleanPath _ pieces =
    case pieces of
    ["somepath", ""] -> Right pieces
    _ | pieces == corrected -> Right pieces
    _ -> Left corrected
    corrected = filter (not . null) pieces

Note here we only allow the trailing slash exactly for the URL /somepath/ and all others strip the trailing (and other empty paths). Note that the default cleanPath also replaces - but since I wasn't using that I didn't add it.

The result is that from hamlet templates we can link to SomePathR [] and it will render as /somepath/, or even link directly to some route state via SomePathR ["some", "state"] and it will render as /somepath/some/state. GETs to both of these URLs return the HTML directly without redirects and angular then updates to the correct state. While a GET to /somepath also works and we could consider redirecting /somepath to /somepath/ inside cleanPath, nothing links to /somepath so I just left it.

Places for improvements

An incomplete list of places where an alternative design than what I picked would be nice.