How to do it...

  1. Create a new project, working-with-snaplets, with the simple stack template:
       stack new working-with-snaplets simple
  1. Add a dependency on the snap-core library in the build-depends subsection of the executable section, as follows:
  executable working-with-snaplets
    hs-source-dirs:      src
    main-is:             Main.hs
    default-language:    Haskell2010
    build-depends:       base >= 4.7 && < 5
                       , snap-core
                       , snap-server
                       , snap
                       , lens
                       , bytestring
                       , text
                       , mtl
  1. Open src/Main.hs. We will add our source here. After the initial Main module definition, add the necessary imports. Enable the OverloadedStrings and TemplateHaskell extensions, as Snap uses the Lens Template Haskell library:
{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}
module Main where

import Control.Applicative
import Control.Lens
import Control.Lens.TH
import Control.Monad.State.Class (gets)
import Data.ByteString.Char8
import Data.Maybe
import Data.Monoid
import Snap
import Snap.Core
import Snap.Http.Server
import Snap.Snaplet.Session
import Snap.Snaplet.Heist
  1. Create a data type, MyData. It contains a ByteString list. This is done as follows:
data MyData = MyData { _someData :: [ByteString] }
  1. Create lenses for our data type. Note that lenses are covered in detail in Chapter 11Working with Lens and Prism. In the context of this recipe, it is sufficient to know that while creating lenses, template haskell will remove an underscore ("_") from the record field and will create a lens. In the aforementioned type, MyData, a lens called someData will be created.
makeLenses ''MyData
  1. Create a Snaplet for MyData. We create a snaplet that can be used in other Snap applications, as follows:
-- Initialize the snaplet 
myDataInit :: SnapletInit b MyData
myDataInit = makeSnaplet "myData" "Snaplet with MyData" Nothing $ do
  return (MyData ["My Data is initialized"])
  1. Create an application composed of the Heist and MyData snaplets, as shown here:
data MyApp = MyApp { _heist :: Snaplet (Heist MyApp)
                   , _myData :: Snaplet MyData
                   }
  1. Create lenses for MyApp:
makeLenses ''MyApp
  1. Create a Snap handler function, snapletName, which will access the current snaplet name, and will print it as a text:
snapletName :: Handler b MyData ()
snapletName = method GET $ do
  name <- getSnapletName
  let snapletname = fromMaybe "Cannot get snaplet name" name
  writeText $ "Name of the snaplet : " <> snapletname
  1. Create a Snap handler function, snapletData, which will access the data stored in MyData, and print it as a text:
snapletData :: Handler b MyData ()
snapletData = method GET $ do
  mydata <- gets _someData
  writeBS $ mconcat mydata
  1. Now create the snaplet for MyApp. This snaplet will initialize the heist and MyData snaplets, and will also add routes for getting the name of the snaplet and for accessing the data inside MyDatasnaplet. It will also allow static serving of templates through heist:
myAppInit :: SnapletInit MyApp MyApp
myAppInit = makeSnaplet "myApp" "My First Snaplet" Nothing $ do
  hst <- nestSnaplet "heist" heist $ heistInit "templates"
  myd <- nestSnaplet "mydata" myData $ myDataInit
  addRoutes [ ("/mysnaplet", with myData snapletName)
            , ("/mysnaplet/data", with myData snapletData)
            ]
  wrapSite (<|> heistServe)
  return (MyApp hst myd)
  1. Create an instance of the HasHeist type class. This will simplify accessing heist for binding templates, and so on:
instance HasHeist MyApp where
  heistLens = subSnaplet heist
  1. Use the MyApp snaplet to be served as the web application, as follows:
main :: IO ()
main = serveSnaplet defaultConfig myAppInit
  1. We will still need to add some templates for Heist. Create a directory, snapletsin the project directory, and create a heist/templates subpath inside the snaplets directory.
  2. Add the default template in the snaplets/heist/templates directory, as follows:
<html>
  <head>
    <title>Creating and composing Snaplets</title>
  </head>

  <apply-content/>
</html>
  1. Add the index template in the same templates directory. The index template uses the default template:
<apply template="default">
  <h1> Welcome to Heist </h1>
  <p>
    This page is displayed through <em>Heist</em> snaplet.
  </p>
</apply>
  1. Build and execute the project:
      stack build
      stack exec -- working-with-snaplets

The Snap server will serve at port 8000. Pointing the browser to http://localhost:8000, you should see the following HTML output:

If you enter http://localhost:8000/mysnaplet, you will see the name of the snaplet. The output should look like the following:



You can see the following output when you load the URL http://localhost:8000/mysnaplet/data: