packages feed

snap-web-routes-0.4.0.0: README.md

# snap-web-routes

## Type safe URLs for Snap

[`Snap web routes`](http://hackage.haskell.org/package/snap-web-routes) provides type safe URLs for Snap using [`web routes`](http://hackage.haskell.org/package/web-routes).

## How to use

The tutorial assumes you have a standard Snap app layout with an Application.hs and Site.hs. If your setup differs you'll need to adapt the instructions accordingly.

### `Application.hs`

To get going, you'll need to add a few things to `Application.hs`. This includes creating the URL data type and adding the routing function to our `App` data type.

```haskell
-- Enable a few extensions
{-# LANGUAGE FlexibleInstances #-} -- Needed by
{-# LANGUAGE TypeFamilies      #-} -- web-routes
{-# LANGUAGE DeriveGeneric     #-} -- Needed to derive Generic
                                   -- for our URL data type

-- Used in HasRouter instances
import Control.Monad.State (get)

-- Paths and params use Text.
import Data.Text (Text)

-- Snap.Snaplet.Router.Types exports everything you need to
-- define your PathInfo and HasRouter instances.
import Snap.Web.Routes.Types

-- Your URL data type.  Deriving a `Generic` allows you to
-- get a free `PathInfo` instance.
data AppUrl
    = Count Int
    | Echo Text
    | Paths [Text]
      deriving (Generic)

-- Extend your App type to include the router snaplet.
data App = App
    { _heist :: Snaplet (Heist App)
    , _router :: Snaplet RouterState
    }

-- Thanks to Generic, an empty instance definition is all
-- you need. Alternately, you can implement 'toPathSegments'
-- and 'fromPathSegments' yourself or use web-routes-th.
instance PathInfo AppUrl

-- You need to define a HasRouter instance for your app.
-- @type URL (Handler App App)@ must be set to the URL
-- data type you defined above.
-- @with router@ uses the lens for the @RouterState@ snaplet
-- you added to App.
instance HasRouter (Handler App App) where
    type URL (Handler App App) = AppUrl
    getRouterState = with router get

-- You also need to define a HasRouter instance for the
-- router snaplet.
-- @type URL (Handler b RouterState)@ must be set to the URL
-- data type you defined above.
instance HasRouter (Handler b RouterState) where
    type URL (Handler b RouterState) = AppUrl
    getRouterState = get
```

### `Site.hs`

Moving on to `Site.hs`, we'll setup handlers for each URL, as well initialise our app with the router snaplet..

```haskell
-- Snap.Snaplet.Router provides routing functions
import Snap.Snaplet.Router

-- Add your new routes using routeWith
routes :: [(ByteString, Handler App App ())]
routes = [ ("", routeWith routeAppUrl)
         , ("", serveDirectory "static")
         ]

-- Define handlers for each value constructor in your URL data type.
routeAppUrl :: AppUrl -> Handler App App ()
routeAppUrl appUrl =
    case appUrl of
      (Count n)   -> writeText $ ("Count = " `T.append` (T.pack $ show n))
      (Echo text) -> echo text
      (Paths ps)  -> writeText $ T.intercalate " " ps

-- You'll note that these are normal Snap handlers, except they can take
-- values from the value constructor as arguments. This is a lot nicer than
-- having to use getParam.
echo :: T.Text -> Handler App App ()
echo msg = heistLocal (bindString "message" msg) $ render "echo"

-- Add the router snaplet to your app.
app :: SnapletInit App App
app = makeSnaplet "app" "An example snap-web-routes app." Nothing $ do
    h <- nestSnaplet "" heist $ heistInit "templates"
    r <- nestSnaplet "router" router $ initRouter ""
    addRoutes routes
    return $ App h r
```

The prefix you pass to the router snaplet must match the prefix you specified in routes, e.g. if it was `("/prefix", routeWith routeAppUrl)`) then:

```haskell
r <- nestSnaplet "router" router $ initRouter "/prefix"
```

If you are having trouble figuring out why a particular request isn't routing as expected, try replacing `routeWith` with `routeWithDebug`. It'll display the available routes, as well as any failed route parses. Just remember that it's **not suitable for production** use, and only displays debugging information for local requests.

### Rendering URLs

Helper functions are provided for rendering URLs:

```haskell
echo :: T.Text -> Handler App App ()
echo msg = do
    if msg == "test" then redirectURL (Echo "test passed") else renderEcho
  where
    renderEcho = heistLocal (I.bindSplices echoSplices) $ render "echo"
    echoSplices = do
        "message"  ## I.textSplice msg
        "countUrl" ## urlSplice (Count 10)
```

In the example above you'll find we use `urlSplice` to turn a URL into a splice, and `redirectURL` to redirect to a URL. There is also `urlPath` to render a URL as Text, as well as params versions of all these functions (`urlParamsSplice`, `redirectURLParams` and `urlPathParams`) that take a params list to append as a query string.