diff --git a/simple.cabal b/simple.cabal
--- a/simple.cabal
+++ b/simple.cabal
@@ -1,5 +1,5 @@
 name:                simple
-version:             0.7.0.1
+version:             0.8.0.0
 synopsis: A minimalist web framework for the WAI server interface
 description:
 
@@ -25,6 +25,7 @@
   .
   See "Web.Simple" for a more detailed introduction.
 homepage:            http://simple.cx
+Bug-Reports:         http://github.com/alevy/simple/issues
 license:             LGPL-3
 license-file:        LICENSE
 author:              Amit Levy, Daniel B. Giffin
@@ -82,6 +83,7 @@
     Web.Simple,
     Web.Simple.Auth,
     Web.Simple.Controller,
+    Web.Simple.Controller.Trans,
     Web.Simple.Responses,
     Web.Simple.Static,
     Web.Simple.Templates,
@@ -91,5 +93,5 @@
 
 source-repository head
   type: git
-  location: anonymous@gitstar.com:alevy/simple.git
+  location: http://github.com/alevy/simple.git
 
diff --git a/src/Web/Frank.hs b/src/Web/Frank.hs
--- a/src/Web/Frank.hs
+++ b/src/Web/Frank.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE Trustworthy #-}
 {- |
 Frank is a Sinatra-inspired DSL (see <http://www.sinatrarb.com>) for creating
 routes. It is composable with all 'ToApplication' types, but is designed to be used
@@ -30,29 +31,31 @@
   ) where
 
 import Network.HTTP.Types
-import Web.Simple.Controller
+import Web.Simple.Controller.Trans
 import qualified Data.ByteString as S
 
 -- | Helper method
-frankMethod :: StdMethod -> S.ByteString -> Controller r a -> Controller r ()
+frankMethod :: Monad m
+            => StdMethod -> S.ByteString -> ControllerT m r a
+            -> ControllerT m r ()
 frankMethod method pattern = routeMethod method . routePattern pattern . routeTop
 
 -- | Matches the GET method on the given URL pattern
-get :: S.ByteString -> Controller r a -> Controller r ()
+get :: Monad m => S.ByteString -> ControllerT m r a -> ControllerT m r ()
 get = frankMethod GET
 
 -- | Matches the POST method on the given URL pattern
-post :: S.ByteString -> Controller r a -> Controller r ()
+post :: Monad m => S.ByteString -> ControllerT m r a -> ControllerT m r ()
 post = frankMethod POST
 
 -- | Matches the PUT method on the given URL pattern
-put :: S.ByteString -> Controller r a -> Controller r ()
+put :: Monad m => S.ByteString -> ControllerT m r a -> ControllerT m r ()
 put = frankMethod PUT
 
 -- | Matches the DELETE method on the given URL pattern
-delete :: S.ByteString -> Controller r a -> Controller r ()
+delete :: Monad m => S.ByteString -> ControllerT m r a -> ControllerT m r ()
 delete = frankMethod DELETE
 
 -- | Matches the OPTIONS method on the given URL pattern
-options :: S.ByteString -> Controller r a -> Controller r ()
+options :: Monad m => S.ByteString -> ControllerT m r a -> ControllerT m r ()
 options = frankMethod OPTIONS
diff --git a/src/Web/REST.hs b/src/Web/REST.hs
--- a/src/Web/REST.hs
+++ b/src/Web/REST.hs
@@ -1,4 +1,10 @@
-{-# LANGUAGE FlexibleInstances, OverloadedStrings #-}
+{-# LANGUAGE Trustworthy, FlexibleInstances, OverloadedStrings #-}
+{- |
+
+REST is a DSL for creating routes using RESTful HTTP verbs.
+See <http://en.wikipedia.org/wiki/Representational_state_transfer>
+
+-}
 module Web.REST
   ( REST(..), RESTController, rest, routeREST
   , index, show, create, update, delete
@@ -7,24 +13,25 @@
 
 import Prelude hiding (show)
 
-import Control.Monad
 import Control.Monad.Trans.State
 import Data.Functor.Identity
 import Web.Simple.Responses
-import Web.Simple.Controller
+import Web.Simple.Controller.Trans
 import Network.HTTP.Types
 
-data REST r = REST
-  { restIndex   :: Controller r ()
-  , restShow    :: Controller r ()
-  , restCreate  :: Controller r ()
-  , restUpdate  :: Controller r ()
-  , restDelete  :: Controller r ()
-  , restEdit    :: Controller r ()
-  , restNew     :: Controller r ()
+-- | Type used to encode a REST controller.
+data REST m r = REST
+  { restIndex   :: ControllerT m r ()
+  , restShow    :: ControllerT m r ()
+  , restCreate  :: ControllerT m r ()
+  , restUpdate  :: ControllerT m r ()
+  , restDelete  :: ControllerT m r ()
+  , restEdit    :: ControllerT m r ()
+  , restNew     :: ControllerT m r ()
   }
 
-defaultREST :: REST r
+-- | Default state, returns @404@ for all verbs.
+defaultREST :: Monad m => REST m r
 defaultREST = REST
   { restIndex   = respond $ notFound
   , restShow    = respond $ notFound
@@ -35,12 +42,13 @@
   , restNew     = respond $ notFound
   }
 
-type RESTControllerM r a = StateT (REST r) Identity a
+-- | Monad used to encode a REST controller incrementally.
+type RESTControllerM m r a = StateT (REST m r) Identity a
 
-rest :: RESTControllerM r a -> REST r
+rest :: Monad m => RESTControllerM m r a -> REST m r
 rest rcontroller = snd . runIdentity $ runStateT rcontroller defaultREST
 
-routeREST :: REST r -> Controller r ()
+routeREST :: Monad m => REST m r -> ControllerT m r ()
 routeREST rst = do
   routeMethod GET $ do
     routeTop $ restIndex rst
@@ -55,33 +63,40 @@
 
   routeMethod PUT $ routeVar "id" $ restUpdate rst
 
-type RESTController r = RESTControllerM r ()
+type RESTController m r = RESTControllerM m r ()
 
-index :: Controller r a -> RESTController r
+-- | GET \/
+index :: ControllerT m r () -> RESTController m r
 index route = modify $ \controller ->
-  controller { restIndex = void route }
+  controller { restIndex = route }
 
-create :: Controller r a -> RESTController r
+-- | POST \/
+create :: ControllerT m r () -> RESTController m r
 create route = modify $ \controller ->
-  controller { restCreate = void route }
+  controller { restCreate = route }
 
-edit :: Controller r a -> RESTController r
+-- | GET \/:id\/edit
+edit :: ControllerT m r () -> RESTController m r
 edit route = modify $ \controller ->
-  controller { restEdit = void route }
+  controller { restEdit = route }
 
-new :: Controller r a -> RESTController r
+-- | GET \/new
+new :: ControllerT m r () -> RESTController m r
 new route = modify $ \controller ->
-  controller { restNew = void route }
+  controller { restNew = route }
 
-show :: Controller r a -> RESTController r
+-- | GET \/:id
+show :: ControllerT m r () -> RESTController m r
 show route = modify $ \controller ->
-  controller { restShow = void route }
+  controller { restShow = route }
 
-update :: Controller r a -> RESTController r
+-- | PUT \/:id
+update :: ControllerT m r () -> RESTController m r
 update route = modify $ \controller ->
-  controller { restUpdate = void route }
+  controller { restUpdate = route }
 
-delete :: Controller r a -> RESTController r
+-- | DELETE \/:id
+delete :: ControllerT m r () -> RESTController m r
 delete route = modify $ \controller ->
-  controller { restDelete = void route }
+  controller { restDelete = route }
 
diff --git a/src/Web/Simple.hs b/src/Web/Simple.hs
--- a/src/Web/Simple.hs
+++ b/src/Web/Simple.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE Trustworthy #-}
 {- |
 
 
diff --git a/src/Web/Simple/Controller.hs b/src/Web/Simple/Controller.hs
--- a/src/Web/Simple/Controller.hs
+++ b/src/Web/Simple/Controller.hs
@@ -1,5 +1,5 @@
+{-# LANGUAGE Trustworthy #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE FlexibleInstances #-}
 
 {- | 'Controller' provides a convenient syntax for writting 'Application'
@@ -24,7 +24,7 @@
   -- * Example
   -- $Example
   -- * Controller Monad
-    Controller(..), runController
+    Controller, T.ControllerT(..), ControllerState
   , controllerApp, controllerState, putState
   , request, localRequest, respond
   , requestHeader
@@ -32,7 +32,7 @@
   , routeHost, routeTop, routeMethod, routeAccept
   , routePattern, routeName, routeVar
   -- * Inspecting query
-  , Parseable
+  , T.Parseable
   , queryParam, queryParam', queryParams
   , readQueryParam, readQueryParam', readQueryParams
   , parseForm
@@ -40,140 +40,87 @@
   , redirectBack
   , redirectBackOr
   -- * Exception handling
-  , ControllerException
+  , T.ControllerException
   , module Control.Exception.Peel
   -- * Integrating other WAI components
-  , ToApplication(..)
   , fromApp
   -- * Low-level utilities
   , body
-  -- , guard, guardM, guardReq
+  , hoistEither, ask, local, pass
   ) where
 
-import           Control.Applicative
 import           Control.Exception.Peel
-import           Control.Monad hiding (guard)
 import           Control.Monad.IO.Class
-import           Control.Monad.IO.Peel
 import qualified Data.ByteString as S
 import qualified Data.ByteString.Char8 as S8
 import qualified Data.ByteString.Lazy as L
 import qualified Data.ByteString.Lazy.Char8 as L8
 import           Data.Conduit
 import qualified Data.Conduit.List as CL
-import           Data.List (find)
-import           Data.Maybe
-import           Data.Text (Text)
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as T
-import           Data.Typeable
 import           Network.HTTP.Types
 import           Network.Wai
 import           Network.Wai.Parse
+import           Web.Simple.Controller.Trans
+                  (ControllerT, ControllerState)
+import qualified Web.Simple.Controller.Trans as T
 import           Web.Simple.Responses
 
 
-type ControllerState r = (r, Request)
-
 -- | The Controller Monad is both a State-like monad which, when run, computes
 -- either a 'Response' or a result. Within the Controller Monad, the remainder
 -- of the computation can be short-circuited by 'respond'ing with a 'Response'.
-newtype Controller r a =
-  Controller (ControllerState r ->
-              IO (Either Response a, ControllerState r))
-
-instance Functor (Controller r) where
-  fmap f (Controller act) = Controller $ \st0 -> do
-    (eaf, st) <- act st0
-    case eaf of
-      Left resp -> return (Left resp, st)
-      Right result -> return (Right $ f result, st)
-
-instance Applicative (Controller r) where
-  pure a = Controller $ \st -> return $ (Right a, st)
-  (<*>) = ap
-
-instance Monad (Controller r) where
-  return = pure
-  (Controller act) >>= fn = Controller $ \st0 -> do
-    (eres, st) <- act st0
-    case eres of
-      Left resp -> return (Left resp, st)
-      Right result -> do
-        let (Controller fres) = fn result
-        fres st
-
-instance MonadIO (Controller r) where
-  liftIO act = Controller $ \st -> liftIO act >>= \r -> return (Right r, st)
+type Controller = ControllerT IO
 
 hoistEither :: Either Response a -> Controller r a
-hoistEither eith = Controller $ \st -> return (eith, st)
-
-instance MonadPeelIO (Controller r) where
-  peelIO = do
-    r <- controllerState
-    req <- request
-    return $ \ctrl -> do
-      res <- runController ctrl r req
-      return $ hoistEither res
+hoistEither = T.hoistEither
 
 ask :: Controller r (r, Request)
-ask = Controller $ \rd -> return (Right rd, rd)
+ask = T.ask
 
 -- | Extract the request
 request :: Controller r Request
-request = liftM snd ask
+request = T.request
 
 local :: ((r, Request) -> (r, Request)) -> Controller r a -> Controller r a
-local f (Controller act) = Controller $ \st@(_, r) -> do
-  (eres, (req, _)) <- act (f st)
-  return (eres, (req, r))
+local = T.local
 
 -- | Modify the request for the given computation
 localRequest :: (Request -> Request) -> Controller r a -> Controller r a
-localRequest f = local (\(r,req) -> (r, f req))
+localRequest = T.localRequest
 
 -- | Extract the application-specific state
-controllerState :: Controller r r 
-controllerState = liftM fst ask
+controllerState :: Controller r r
+controllerState = T.controllerState
 
 putState :: r -> Controller r ()
-putState r = Controller $ \(_, req) -> return (Right (), (r, req))
+putState = T.putState
 
 -- | Convert the controller into an 'Application'
 controllerApp :: r -> Controller r a -> Application
-controllerApp r ctrl req =
-  runController ctrl r req >>=
-    either return (const $ return notFound) 
-
-runController :: Controller r a -> r -> Request -> IO (Either Response a)
-runController (Controller fun) r req = fst `fmap` fun (r,req)
+controllerApp = T.controllerApp
 
 -- | Decline to handle the request
 --
 -- @pass >> c === c@
 -- @c >> pass === c@
 pass :: Controller r ()
-pass = Controller $ \st -> return (Right (), st)
+pass = T.pass
 
 -- | Provide a response
 --
 -- @respond r >>= f === respond r@
 respond :: Response -> Controller r a
-respond resp = Controller $ \st -> return (Left resp, st)
+respond = T.respond
 
 
 -- | Lift an application to a controller
-fromApp :: ToApplication a => a -> Controller r ()
-fromApp app = do
-  req <- request
-  resp <- liftIO $ (toApp app) req
-  respond resp
+fromApp :: Application -> Controller r ()
+fromApp = T.fromApp
 
 -- | Matches on the hostname from the 'Request'. The route only succeeds on
 -- exact matches.
 routeHost :: S.ByteString -> Controller r a -> Controller r ()
-routeHost host = guardReq $ \req -> host == (fromMaybe "" $ requestHeaderHost req)
+routeHost = T.routeHost
 
 -- | Matches if the path is empty.
 --
@@ -181,17 +128,15 @@
 -- is empty, so it works as expected in nested contexts that have
 -- popped components from the 'pathInfo' list.
 routeTop :: Controller r a -> Controller r ()
-routeTop = guardReq $ \req -> null (pathInfo req) ||
-                              (T.length . head $ pathInfo req) == 0
+routeTop = T.routeTop
 
 -- | Matches on the HTTP request method (e.g. 'GET', 'POST', 'PUT')
 routeMethod :: StdMethod -> Controller r a -> Controller r ()
-routeMethod method = guardReq $ (renderStdMethod method ==) . requestMethod
+routeMethod = T.routeMethod
 
 -- | Matches if the request's Content-Type exactly matches the given string
 routeAccept :: S8.ByteString -> Controller r a -> Controller r ()
-routeAccept contentType = guardReq (isJust . find matching . requestHeaders)
- where matching hdr = fst hdr == hAccept && snd hdr == contentType
+routeAccept = T.routeAccept
 
 -- | Routes the given URL pattern. Patterns can include
 -- directories as well as variable patterns (prefixed with @:@) to be added
@@ -204,35 +149,17 @@
 --  * \/:date\/posts\/:category\/new
 --
 routePattern :: S.ByteString -> Controller r a -> Controller r ()
-routePattern pattern route =
-  let patternParts = map T.unpack $ decodePathSegments pattern
-  in foldr mkRoute (route >> return ()) patternParts
-  where mkRoute (':':varName) = routeVar (S8.pack varName)
-        mkRoute name = routeName (S8.pack name)
+routePattern = T.routePattern
 
 -- | Matches if the first directory in the path matches the given 'ByteString'
 routeName :: S.ByteString -> Controller r a -> Controller r ()
-routeName name next = do
-  req <- request
-  if (length $ pathInfo req) > 0 && S8.unpack name == (T.unpack . head . pathInfo) req
-    then localRequest popHdr next >> return ()
-    else pass
-  where popHdr req = req { pathInfo = (tail . pathInfo $ req) }
+routeName = T.routeName
 
 -- | Always matches if there is at least one directory in 'pathInfo' but and
 -- adds a parameter to 'queryString' where the key is the first parameter and
 -- the value is the directory consumed from the path.
 routeVar :: S.ByteString -> Controller r a -> Controller r ()
-routeVar varName next = do
-  req <- request
-  case pathInfo req of
-    [] -> pass
-    x:_ | T.null x -> pass
-        | otherwise -> localRequest popHdr next >> return ()
-  where popHdr req = req {
-              pathInfo = (tail . pathInfo $ req)
-            , queryString = (varName, Just (varVal req)):(queryString req)}
-        varVal req = S8.pack . T.unpack . head . pathInfo $ req
+routeVar = T.routeVar
 
 --
 -- query parameters
@@ -246,70 +173,40 @@
 -- would return @Just "bar"@, but
 -- @queryParam \"zap\"@
 -- would return @Nothing@.
-queryParam :: Parseable a
+queryParam :: T.Parseable a
            => S8.ByteString -- ^ Parameter name
            -> Controller r (Maybe a)
-queryParam varName = do
-  qr <- liftM queryString request
-  return $ case lookup varName qr of
-    Just p -> Just $ parse $ fromMaybe S.empty p
-    _ -> Nothing
+queryParam = T.queryParam
 
 -- | Like 'queryParam', but throws an exception if the parameter is not present.
-queryParam' :: Parseable a
+queryParam' :: T.Parseable a
             => S.ByteString -> Controller r a
-queryParam' varName =
-  queryParam varName >>= maybe (err $ "no parameter " ++ show varName) return
+queryParam' = T.queryParam'
 
 -- | Selects all values with the given parameter name
-queryParams :: Parseable a
+queryParams :: T.Parseable a
             => S.ByteString -> Controller r [a]
-queryParams varName = request >>= return .
-                                  map (parse . fromMaybe S.empty . snd) .
-                                  filter ((== varName) . fst) .
-                                  queryString
-
--- | The class of types into which query parameters may be converted
-class Parseable a where
-  parse :: S8.ByteString -> a
-
-instance Parseable S8.ByteString where
-  parse = id
-instance Parseable String where
-  parse = S8.unpack
-instance Parseable Text where
-  parse = T.decodeUtf8
+queryParams = T.queryParams
 
 -- | Like 'queryParam', but further processes the parameter value with @read@.
 -- If that conversion fails, an exception is thrown.
 readQueryParam :: Read a
                => S8.ByteString -- ^ Parameter name
                -> Controller r (Maybe a)
-readQueryParam varName =
-  queryParam varName >>= maybe (return Nothing) (liftM Just . readParamValue varName)
+readQueryParam = T.readQueryParam
 
 -- | Like 'readQueryParam', but throws an exception if the parameter is not present.
 readQueryParam' :: Read a
                 => S8.ByteString -- ^ Parameter name
                 -> Controller r a
-readQueryParam' varName =
-  queryParam' varName >>= readParamValue varName
+readQueryParam' = T.readQueryParam'
 
 -- | Like 'queryParams', but further processes the parameter values with @read@.
 -- If any read-conversion fails, an exception is thrown.
-readQueryParams ::  Read a
+readQueryParams :: Read a
                 => S8.ByteString -- ^ Parameter name
                 -> Controller r [a]
-readQueryParams varName =
-  queryParams varName >>= mapM (readParamValue varName)
-
-readParamValue :: Read a => S8.ByteString -> Text -> Controller r a
-readParamValue varName =
-  maybe (err $ "cannot read parameter: " ++ show varName) return .
-    readMay . T.unpack
-  where readMay s = case [x | (x,rst) <- reads s, ("", "") <- lex rst] of
-                      [x] -> Just x
-                      _ -> Nothing
+readQueryParams = T.readQueryParams
 
 -- | Parses a HTML form from the request body. It returns a list of 'Param's as
 -- well as a list of 'File's, which are pairs mapping the name of a /file/ form
@@ -317,7 +214,7 @@
 -- upload.
 --
 -- @
---   myController = do
+--   myControllerT = do
 --     (prms, files) <- parseForm
 --     let mPicFile = lookup \"profile_pic\" files
 --     case mPicFile of
@@ -357,38 +254,6 @@
   case mrefr of
     Just refr -> respond $ redirectTo refr
     Nothing   -> respond def
-
--- guard
-
-guard :: Bool -> Controller r a -> Controller r ()
-guard b c = if b then c >> return () else pass
-
-guardM :: Controller r Bool -> Controller r a -> Controller r ()
-guardM b c = b >>= flip guard c
-
-guardReq :: (Request -> Bool) -> Controller r a -> Controller r ()
-guardReq f = guardM (liftM f request)
-
--- | The class of types that can be converted to an 'Application'
-class ToApplication r where
-  toApp :: r -> Application
-
-instance ToApplication Application where
-  toApp = id
-
-instance ToApplication Response where
-  toApp = const . return
-
-data ControllerException = ControllerException String
-  deriving (Typeable)
-
-instance Show ControllerException where
-  show (ControllerException msg) = "Controller: " ++ msg
-
-instance Exception ControllerException
-
-err :: String -> Controller r a
-err = throwIO . ControllerException
 
 {- $Example
  #example#
diff --git a/src/Web/Simple/Controller/Trans.hs b/src/Web/Simple/Controller/Trans.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Simple/Controller/Trans.hs
@@ -0,0 +1,373 @@
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE FlexibleInstances #-}
+
+{- | 'ControllerT' provides a convenient syntax for writting 'Application'
+  code as a Monadic action with access to an HTTP request as well as app
+  specific data (e.g. a database connection pool, app configuration etc.)
+  This module also defines some
+  helper functions that leverage this feature. For example, 'redirectBack'
+  reads the underlying request to extract the referer and returns a redirect
+  response:
+
+  @
+    myControllerT = do
+      ...
+      if badLogin then
+        redirectBack
+        else
+          ...
+  @
+-}
+module Web.Simple.Controller.Trans where
+
+import           Control.Monad.IO.Class
+import           Control.Monad.IO.Peel
+import           Control.Applicative
+import           Control.Exception.Peel
+import           Control.Monad hiding (guard)
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Char8 as S8
+import           Data.List (find)
+import           Data.Maybe
+import           Data.Text (Text)
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as T
+import           Data.Typeable
+import           Network.HTTP.Types
+import           Network.Wai
+import           Web.Simple.Responses
+
+
+type ControllerState r = (r, Request)
+
+-- | The ControllerT Monad is both a State-like monad which, when run, computes
+-- either a 'Response' or a result. Within the ControllerT Monad, the remainder
+-- of the computation can be short-circuited by 'respond'ing with a 'Response'.
+newtype ControllerT m r a = ControllerT
+  { runController :: ControllerState r ->
+                      m (Either Response a, ControllerState r) }
+
+instance (Monad m, Functor m) => Functor (ControllerT m r) where
+  fmap f (ControllerT act) = ControllerT $ \st0 -> do
+    (eaf, st) <- act st0
+    case eaf of
+      Left resp -> return (Left resp, st)
+      Right result -> return (Right $ f result, st)
+
+instance (Monad m, Applicative m) => Applicative (ControllerT m r) where
+  pure = return
+  (<*>) = ap
+
+instance Monad m => Monad (ControllerT m r) where
+  return a = ControllerT $ \st -> return $ (Right a, st)
+  (ControllerT act) >>= fn = ControllerT $ \st0 -> do
+    (eres, st) <- act st0
+    case eres of
+      Left resp -> return (Left resp, st)
+      Right result -> do
+        let (ControllerT fres) = fn result
+        fres st
+
+instance MonadIO m => MonadIO (ControllerT m r) where
+  liftIO = liftC . liftIO
+
+instance MonadPeelIO (ControllerT IO r) where
+  peelIO = do
+    r <- controllerState
+    req <- request
+    return $ \ctrl -> do
+      res <- fst `fmap` runController ctrl (r, req)
+      return $ hoistEither res
+
+liftC :: Monad m => m a -> ControllerT m r a
+liftC act = ControllerT $ \st -> act >>= \r -> return (Right r, st)
+
+hoistEither :: Monad m => Either Response a -> ControllerT m r a
+hoistEither eith = ControllerT $ \st -> return (eith, st)
+
+ask :: Monad m => ControllerT m r (r, Request)
+ask = ControllerT $ \rd -> return (Right rd, rd)
+
+-- | Extract the request
+request :: Monad m => ControllerT m r Request
+request = liftM snd ask
+
+local :: Monad m
+      => ((r, Request) -> (r, Request)) -> ControllerT m r a -> ControllerT m r a
+local f (ControllerT act) = ControllerT $ \st@(_, r) -> do
+  (eres, (req, _)) <- act (f st)
+  return (eres, (req, r))
+
+-- | Modify the request for the given computation
+localRequest :: Monad m
+             => (Request -> Request) -> ControllerT m r a -> ControllerT m r a
+localRequest f = local (\(r,req) -> (r, f req))
+
+-- | Extract the application-specific state
+controllerState :: Monad m => ControllerT m r r
+controllerState = liftM fst ask
+
+putState :: Monad m => r -> ControllerT m r ()
+putState r = ControllerT $ \(_, req) -> return (Right (), (r, req))
+
+-- | Convert the controller into an 'Application'
+controllerApp :: Monad m => r -> ControllerT m r a -> SimpleApplication m
+controllerApp r ctrl req =
+  runController ctrl (r, req) >>=
+    either return (const $ return notFound) . fst
+
+-- | Decline to handle the request
+--
+-- @pass >> c === c@
+-- @c >> pass === c@
+pass :: Monad m => ControllerT m r ()
+pass = ControllerT $ \st -> return (Right (), st)
+
+-- | Provide a response
+--
+-- @respond r >>= f === respond r@
+respond :: Monad m => Response -> ControllerT m r a
+respond resp = hoistEither $ Left resp
+
+
+-- | Lift an application to a controller
+fromApp :: Monad m => (Request -> m Response) -> ControllerT m r ()
+fromApp app = do
+  req <- request
+  resp <- liftC $ app req
+  respond resp
+
+-- | Matches on the hostname from the 'Request'. The route only succeeds on
+-- exact matches.
+routeHost :: Monad m => S.ByteString -> ControllerT m r a -> ControllerT m r ()
+routeHost host = guardReq $ \req ->
+  host == (fromMaybe "" $ requestHeaderHost req)
+
+-- | Matches if the path is empty.
+--
+-- Note that this route checks that 'pathInfo'
+-- is empty, so it works as expected in nested contexts that have
+-- popped components from the 'pathInfo' list.
+routeTop :: Monad m => ControllerT m r a -> ControllerT m r ()
+routeTop = guardReq $ \req -> null (pathInfo req) ||
+                              (T.length . head $ pathInfo req) == 0
+
+-- | Matches on the HTTP request method (e.g. 'GET', 'POST', 'PUT')
+routeMethod :: Monad m => StdMethod -> ControllerT m r a -> ControllerT m r ()
+routeMethod method = guardReq $ (renderStdMethod method ==) . requestMethod
+
+-- | Matches if the request's Content-Type exactly matches the given string
+routeAccept :: Monad m => S8.ByteString -> ControllerT m r a -> ControllerT m r ()
+routeAccept contentType = guardReq (isJust . find matching . requestHeaders)
+ where matching hdr = fst hdr == hAccept && snd hdr == contentType
+
+-- | Routes the given URL pattern. Patterns can include
+-- directories as well as variable patterns (prefixed with @:@) to be added
+-- to 'queryString' (see 'routeVar')
+--
+--  * \/posts\/:id
+--
+--  * \/posts\/:id\/new
+--
+--  * \/:date\/posts\/:category\/new
+--
+routePattern :: Monad m
+             => S.ByteString -> ControllerT m r a -> ControllerT m r ()
+routePattern pattern route =
+  let patternParts = map T.unpack $ decodePathSegments pattern
+  in foldr mkRoute (route >> return ()) patternParts
+  where mkRoute (':':varName) = routeVar (S8.pack varName)
+        mkRoute name = routeName (S8.pack name)
+
+-- | Matches if the first directory in the path matches the given 'ByteString'
+routeName :: Monad m => S.ByteString -> ControllerT m r a -> ControllerT m r ()
+routeName name next = do
+  req <- request
+  if (length $ pathInfo req) > 0 && S8.unpack name == (T.unpack . head . pathInfo) req
+    then localRequest popHdr next >> return ()
+    else pass
+  where popHdr req = req { pathInfo = (tail . pathInfo $ req) }
+
+-- | Always matches if there is at least one directory in 'pathInfo' but and
+-- adds a parameter to 'queryString' where the key is the first parameter and
+-- the value is the directory consumed from the path.
+routeVar :: Monad m => S.ByteString -> ControllerT m r a -> ControllerT m r ()
+routeVar varName next = do
+  req <- request
+  case pathInfo req of
+    [] -> pass
+    x:_ | T.null x -> pass
+        | otherwise -> localRequest popHdr next >> return ()
+  where popHdr req = req {
+              pathInfo = (tail . pathInfo $ req)
+            , queryString = (varName, Just (varVal req)):(queryString req)}
+        varVal req = S8.pack . T.unpack . head . pathInfo $ req
+
+--
+-- query parameters
+--
+
+-- | Looks up the parameter name in the request's query string and returns the
+-- @Parseable@ value or 'Nothing'.
+--
+-- For example, for a request with query string: \"?foo=bar&baz=7\",
+-- @queryParam \"foo\"@
+-- would return @Just "bar"@, but
+-- @queryParam \"zap\"@
+-- would return @Nothing@.
+queryParam :: (Monad m, Parseable a)
+           => S8.ByteString -- ^ Parameter name
+           -> ControllerT m r (Maybe a)
+queryParam varName = do
+  qr <- liftM queryString request
+  return $ case lookup varName qr of
+    Just p -> Just $ parse $ fromMaybe S.empty p
+    _ -> Nothing
+
+-- | Like 'queryParam', but throws an exception if the parameter is not present.
+queryParam' :: (Monad m, Parseable a)
+            => S.ByteString -> ControllerT m r a
+queryParam' varName =
+  queryParam varName >>= maybe (err $ "no parameter " ++ show varName) return
+
+-- | Selects all values with the given parameter name
+queryParams :: (Monad m, Parseable a)
+            => S.ByteString -> ControllerT m r [a]
+queryParams varName = request >>= return .
+                                  map (parse . fromMaybe S.empty . snd) .
+                                  filter ((== varName) . fst) .
+                                  queryString
+
+-- | The class of types into which query parameters may be converted
+class Parseable a where
+  parse :: S8.ByteString -> a
+
+instance Parseable S8.ByteString where
+  parse = id
+instance Parseable String where
+  parse = S8.unpack
+instance Parseable Text where
+  parse = T.decodeUtf8
+
+-- | Like 'queryParam', but further processes the parameter value with @read@.
+-- If that conversion fails, an exception is thrown.
+readQueryParam :: (Monad m, Read a)
+               => S8.ByteString -- ^ Parameter name
+               -> ControllerT m r (Maybe a)
+readQueryParam varName =
+  queryParam varName >>= maybe (return Nothing) (liftM Just . readParamValue varName)
+
+-- | Like 'readQueryParam', but throws an exception if the parameter is not present.
+readQueryParam' :: (Monad m, Read a)
+                => S8.ByteString -- ^ Parameter name
+                -> ControllerT m r a
+readQueryParam' varName =
+  queryParam' varName >>= readParamValue varName
+
+-- | Like 'queryParams', but further processes the parameter values with @read@.
+-- If any read-conversion fails, an exception is thrown.
+readQueryParams :: (Monad m, Read a)
+                => S8.ByteString -- ^ Parameter name
+                -> ControllerT m r [a]
+readQueryParams varName =
+  queryParams varName >>= mapM (readParamValue varName)
+
+readParamValue :: (Monad m, Read a)
+               => S8.ByteString -> Text -> ControllerT m r a
+readParamValue varName =
+  maybe (err $ "cannot read parameter: " ++ show varName) return .
+    readMay . T.unpack
+  where readMay s = case [x | (x,rst) <- reads s, ("", "") <- lex rst] of
+                      [x] -> Just x
+                      _ -> Nothing
+
+-- | Returns the value of the given request header or 'Nothing' if it is not
+-- present in the HTTP request.
+requestHeader :: Monad m => HeaderName -> ControllerT m r (Maybe S8.ByteString)
+requestHeader name = request >>= return . lookup name . requestHeaders
+
+-- | Redirect back to the referer. If the referer header is not present
+-- redirect to root (i.e., @\/@).
+redirectBack :: Monad m => ControllerT m r ()
+redirectBack = redirectBackOr (redirectTo "/")
+
+-- | Redirect back to the referer. If the referer header is not present
+-- fallback on the given 'Response'.
+redirectBackOr :: Monad m
+               => Response -- ^ Fallback response
+               -> ControllerT m r ()
+redirectBackOr def = do
+  mrefr <- requestHeader "referer"
+  case mrefr of
+    Just refr -> respond $ redirectTo refr
+    Nothing   -> respond def
+
+-- | Like 'Application', but with 'm' as the underlying monad
+type SimpleApplication m = Request -> m Response
+
+-- | Like 'Application', but with 'm' as the underlying monad
+type SimpleMiddleware m = SimpleApplication m -> SimpleApplication m
+
+-- guard
+
+guard :: Monad m => Bool -> ControllerT m r a -> ControllerT m r ()
+guard b c = if b then c >> return () else pass
+
+guardM :: Monad m
+       => ControllerT m r Bool -> ControllerT m r a -> ControllerT m r ()
+guardM b c = b >>= flip guard c
+
+guardReq :: Monad m
+         => (Request -> Bool) -> ControllerT m r a -> ControllerT m r ()
+guardReq f = guardM (liftM f request)
+
+data ControllerException = ControllerException String
+  deriving (Typeable)
+
+instance Show ControllerException where
+  show (ControllerException msg) = "ControllerT: " ++ msg
+
+instance Exception ControllerException
+
+err :: String -> ControllerT m r a
+err = throw . ControllerException
+
+{- $Example
+ #example#
+
+The most basic 'Routeable' types are 'Application' and 'Response'. Reaching
+either of these types marks a termination in the routing lookup. This module
+exposes a monadic type 'Route' which makes it easy to create routing logic
+in a DSL-like fashion.
+
+'Route's are concatenated using the '>>' operator (or using do-notation).
+In the end, any 'Routeable', including a 'Route' is converted to an
+'Application' and passed to the server using 'mkRoute':
+
+@
+
+  mainAction :: ControllerT () ()
+  mainAction = ...
+
+  signinForm :: ControllerT () ()
+  signinForm req = ...
+
+  login :: ControllerT () ()
+  login = ...
+
+  updateProfile :: ControllerT () ()
+  updateProfile = ...
+
+  main :: IO ()
+  main = run 3000 $ controllerApp () $ do
+    routeTop mainAction
+    routeName \"sessions\" $ do
+      routeMethod GET signinForm
+      routeMethod POST login
+    routeMethod PUT $ routePattern \"users/:id\" updateProfile
+    routeAll $ responseLBS status404 [] \"Are you in the right place?\"
+@
+
+-}
diff --git a/src/Web/Simple/Responses.hs b/src/Web/Simple/Responses.hs
--- a/src/Web/Simple/Responses.hs
+++ b/src/Web/Simple/Responses.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE Trustworthy #-}
 {-# LANGUAGE OverloadedStrings #-}
 
 -- | This module defines some convenience functions for creating responses.
diff --git a/src/Web/Simple/Templates.hs b/src/Web/Simple/Templates.hs
--- a/src/Web/Simple/Templates.hs
+++ b/src/Web/Simple/Templates.hs
@@ -1,4 +1,6 @@
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE OverloadedStrings, MultiParamTypeClasses #-}
+{-# LANGUAGE DefaultSignatures #-}
 module Web.Simple.Templates
   ( HasTemplates(..)
   , defaultGetTemplate, defaultRender, defaultFunctionMap
@@ -6,7 +8,6 @@
   , Function(..), ToFunction(..), FunctionMap
   ) where
 
-import Control.Applicative
 import Control.Monad.IO.Class
 import Data.Aeson
 import qualified Data.ByteString as S
@@ -17,16 +18,16 @@
 import qualified Data.Vector as V
 import Network.Mime
 import System.FilePath
-import Web.Simple.Controller (Controller, respond)
+import Web.Simple.Controller.Trans (ControllerT, respond)
 import Web.Simple.Responses (ok)
 import Web.Simple.Templates.Language
 
-class HasTemplates hs where
+class Monad m => HasTemplates m hs where
   -- | The layout to use by default. Layouts are just templates that embed
   -- views. They are rendered with the a global object containing the rendered
   -- view in the \"yield\" field, and the object the view was rendered with in
   -- the \"page\" field. By default, no template is used.
-  defaultLayout :: Controller hs (Maybe Template)
+  defaultLayout :: ControllerT m hs (Maybe Template)
   defaultLayout = return Nothing
 
   -- | The directory to look for views passed to 'render'. This defaults to
@@ -37,28 +38,29 @@
   -- @
   --
   -- will look for a view template in \"views/index.html.tmpl\".
-  viewDirectory :: Controller hs FilePath
+  viewDirectory :: ControllerT m hs FilePath
   viewDirectory = return "views"
 
   -- | A map of pure functions that can be called from within a template. See
   -- 'FunctionMap' and 'Function' for details.
-  functionMap :: Controller hs FunctionMap
+  functionMap :: ControllerT m hs FunctionMap
   functionMap = return defaultFunctionMap
 
   -- | Function to use to get a template. By default, it looks in the
   -- 'viewDirectory' for the given file name and compiles the file into a
   -- template. This can be overriden to, for example, cache compiled templates
   -- in memory.
-  getTemplate :: FilePath -> Controller hs Template
+  getTemplate :: FilePath -> ControllerT m hs Template
+  default getTemplate :: MonadIO m => FilePath -> ControllerT m hs Template
   getTemplate = defaultGetTemplate
 
   -- | Renders a view template with the default layout and a global used to
   -- evaluate variables in the template.
-  render :: ToJSON a => FilePath -> a -> Controller hs ()
+  render :: ToJSON a => FilePath -> a -> ControllerT m hs ()
   render = defaultRender
 
   -- | Same as 'render' but without a template.
-  renderPlain :: ToJSON a => FilePath -> a -> Controller hs ()
+  renderPlain :: ToJSON a => FilePath -> a -> ControllerT m hs ()
   renderPlain fp val = do
     fm <- functionMap
     dir <- viewDirectory
@@ -70,13 +72,13 @@
     respond $ ok mime pageContent
 
   -- | Render a view using the layout named by the first argument.
-  renderLayout :: ToJSON a => FilePath -> FilePath -> a -> Controller hs ()
+  renderLayout :: ToJSON a => FilePath -> FilePath -> a -> ControllerT m hs ()
   renderLayout lfp fp val = do
     layout <- getTemplate lfp
     renderLayout' layout fp val
 
   -- | Same as 'renderLayout' but uses an already compiled layout.
-  renderLayout' :: ToJSON a => Template -> FilePath -> a -> Controller hs ()
+  renderLayout' :: ToJSON a => Template -> FilePath -> a -> ControllerT m hs ()
   renderLayout' layout fp val = do
     fm <- functionMap
     dir <- viewDirectory
@@ -86,16 +88,16 @@
     respond $ ok mime $ L.fromChunks . (:[]) . encodeUtf8 $
       renderTemplate layout fm $ object ["yield" .= pageContent, "page" .= val]
 
-defaultGetTemplate :: HasTemplates hs => FilePath -> Controller hs Template
+defaultGetTemplate :: (HasTemplates m hs, MonadIO m)
+                   => FilePath -> ControllerT m hs Template
 defaultGetTemplate fp = do
-  eres <- compileTemplate . decodeUtf8 <$>
-    liftIO (S.readFile fp)
-  case eres of
+  contents <- liftIO $ S.readFile fp
+  case compileTemplate . decodeUtf8 $ contents of
     Left str -> fail str
     Right tmpl -> return tmpl
 
-defaultRender :: (HasTemplates hs , ToJSON a)
-              => FilePath -> a -> Controller hs ()
+defaultRender :: (HasTemplates m hs , Monad m, ToJSON a)
+              => FilePath -> a -> ControllerT m hs ()
 defaultRender fp val = do
   mlayout <- defaultLayout
   case mlayout of
diff --git a/template/Common_hs.tmpl b/template/Common_hs.tmpl
--- a/template/Common_hs.tmpl
+++ b/template/Common_hs.tmpl
@@ -1,3 +1,4 @@
+$if(include_templates)${-# LANGUAGE MultiParamTypeClasses #-}$endif$
 module $module$.Common where
 
 import Control.Applicative
@@ -23,6 +24,6 @@
     cs <- controllerState
     putState $$ cs { appSession = Just sess }
 $endif$$if(include_templates)$
-instance HasTemplates AppSettings where
+instance HasTemplates IO AppSettings where
   defaultLayout = Just <$$> getTemplate "layouts/main.html"
 $endif$
diff --git a/template/package_cabal.tmpl b/template/package_cabal.tmpl
--- a/template/package_cabal.tmpl
+++ b/template/package_cabal.tmpl
@@ -11,10 +11,10 @@
   ghc-options: -threaded -O2
   build-depends:
     base
-    , simple >= 0.7.0
+    , simple >= 0.8.0
     , wai
     , wai-extra
     , warp$if(include_sessions)$
-    , simple-session >= 0.7.0$endif$$if(include_postgresql)$
-    , simple-postgresql-orm >= 0.7.0$endif$
+    , simple-session >= 0.8.0$endif$$if(include_postgresql)$
+    , simple-postgresql-orm >= 0.8.0$endif$
 
