diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2015, Athan Clark
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Athan Clark nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/nested-routes.cabal b/nested-routes.cabal
new file mode 100644
--- /dev/null
+++ b/nested-routes.cabal
@@ -0,0 +1,45 @@
+Name:                   nested-routes
+Version:                0.0.1
+Author:                 Athan Clark <athan.clark@gmail.com>
+Maintainer:             Athan Clark <athan.clark@gmail.com>
+License:                BSD3
+License-File:           LICENSE
+Synopsis:               Like scotty, but nested
+-- Description:
+Cabal-Version:          >= 1.10
+Build-Type:             Simple
+
+Library
+  Default-Language:     Haskell2010
+  HS-Source-Dirs:       src
+  GHC-Options:          -Wall
+  Exposed-Modules:      Web.Routes.Nested
+                        Web.Routes.Nested.VerbListener
+                        Web.Routes.Nested.FileExtListener
+  Build-Depends:        base >= 4 && < 5
+                      , wai
+                      , wai-extra
+                      , http-types
+                      , mtl
+                      , transformers
+                      , semigroups
+                      , containers
+                      , pseudo-trie
+                      , text
+                      , aeson
+
+Test-Suite spec
+  Type:                 exitcode-stdio-1.0
+  Default-Language:     Haskell2010
+  Hs-Source-Dirs:       src
+                      , test
+  Ghc-Options:          -Wall
+  Main-Is:              Spec.hs
+  Build-Depends:        base
+                      , hspec
+                      , QuickCheck
+                      , quickcheck-instances
+
+Source-Repository head
+  Type:                 http
+  Location:             https://github.com/athanclark/nested-routes
diff --git a/src/Web/Routes/Nested.hs b/src/Web/Routes/Nested.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Routes/Nested.hs
@@ -0,0 +1,88 @@
+{-# LANGUAGE DeriveFunctor              #-}
+{-# LANGUAGE GADTs                      #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ScopedTypeVariables        #-}
+{-# LANGUAGE StandaloneDeriving         #-}
+{-# LANGUAGE TypeOperators              #-}
+{-# LANGUAGE OverloadedStrings          #-}
+
+module Web.Routes.Nested where
+
+import           Web.Routes.Nested.FileExtListener
+import           Web.Routes.Nested.VerbListener
+
+import           Network.HTTP.Types
+import           Network.Wai
+import           Network.Wai.Middleware.AddHeaders
+
+import           Control.Applicative
+import           Control.Arrow                     (second, first, (***))
+import           Control.Monad.IO.Class
+import           Control.Monad.Trans
+import           Control.Monad.Writer
+import qualified Data.List.NonEmpty                as NE
+import           Data.Monoid
+import           Data.Trie.Pseudo
+import           Data.Trie.Rooted
+import qualified Data.Trie.Rooted                  as R
+import           Data.Traversable
+import qualified Data.Text                         as T
+import qualified Data.Map.Lazy                     as M
+
+newtype HandlerT m a = HandlerT
+  { runHandler :: WriterT (MergeRooted T.Text (Verbs Response)) m a }
+  deriving (Functor)
+
+deriving instance Applicative m => Applicative (HandlerT m)
+deriving instance Monad m =>       Monad       (HandlerT m)
+deriving instance MonadIO m =>     MonadIO     (HandlerT m)
+deriving instance                  MonadTrans   HandlerT
+
+
+handle :: Monad m =>
+          [T.Text]
+       -> VerbListenerT Response m ()
+       -> HandlerT m ()
+handle ts vl = do
+  vfrs <- lift $ execWriterT $ runVerbListenerT vl
+
+  HandlerT $ tell $
+    case ts of
+      [] -> MergeRooted $ Rooted (Just vfrs) []
+      _  -> MergeRooted $ Rooted Nothing [Rest (NE.fromList ts) vfrs]
+
+route :: (Functor m, Monad m) =>
+         HandlerT m a
+      -> Request -> (Response -> m b) -> m b
+route h req respond = do
+  trie <- unMergeRooted <$> (execWriterT $ runHandler h)
+  let mMethod = httpMethodToMSym $ requestMethod req
+      mFileext = possibleExts $ T.pack $ getFileExt $
+                                T.unpack $ last $ pathInfo req
+
+  case (mFileext, mMethod) of
+    (Just f, Just m) -> case R.lookup (pathInfo req) trie of
+      Just map -> case M.lookup (f,m) $ unVerbs map of
+        Just r  -> respond r
+        Nothing -> respond notFound
+      Nothing  -> respond notFound
+    _ -> respond notFound
+
+  where
+    getFileExt :: String -> String
+    getFileExt s = case foldr go Nothing s of
+                     Nothing -> ""
+                     Just x  -> x
+      where
+        go '.' _         = Just "."
+        go x   (Just xs) = Just $ xs ++ [x]
+        go x   Nothing   = Nothing
+
+    httpMethodToMSym :: Method -> Maybe Verb
+    httpMethodToMSym x | x == methodGet    = Just Get
+                       | x == methodPost   = Just Post
+                       | x == methodPut    = Just Put
+                       | x == methodDelete = Just Delete
+                       | otherwise         = Nothing
+
+    notFound = responseLBS status404 [("Content-Type","text/plain")] "404 :("
diff --git a/src/Web/Routes/Nested/FileExtListener.hs b/src/Web/Routes/Nested/FileExtListener.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Routes/Nested/FileExtListener.hs
@@ -0,0 +1,105 @@
+{-# LANGUAGE DeriveFunctor              #-}
+{-# LANGUAGE DeriveTraversable          #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings          #-}
+{-# LANGUAGE StandaloneDeriving         #-}
+
+module Web.Routes.Nested.FileExtListener where
+
+import qualified Data.Aeson              as A
+import qualified Data.Text               as T
+import qualified Data.Text.Lazy          as LT
+import qualified Data.Text.Lazy.Encoding as LT
+import           Network.HTTP.Types      (status200)
+import           Network.Wai
+
+import           Control.Applicative
+import           Control.Monad.Trans
+import           Control.Monad.Writer
+import           Data.Monoid
+import           Data.Map.Lazy
+import           Data.Traversable
+import           Data.Foldable hiding (elem)
+
+
+data FileExt = Html
+             | Json
+             | Text
+  deriving (Show, Eq, Ord)
+
+possibleExts :: T.Text -> Maybe FileExt
+possibleExts x | x `elem` ["", ".htm", ".html"] = Just Html
+               | x `elem` [".json"]             = Just Json
+               | x `elem` [".txt"]              = Just Text
+               | otherwise                      = Nothing
+
+newtype FileExts a = FileExts { unFileExts :: Map FileExt a }
+  deriving (Show, Eq, Functor, Traversable)
+
+deriving instance Monoid      (FileExts a)
+deriving instance Foldable     FileExts
+
+
+newtype FileExtListenerT r m a =
+  FileExtListenerT { runFileExtListenerT :: WriterT (FileExts r) m a }
+    deriving (Functor)
+
+deriving instance Applicative m => Applicative (FileExtListenerT r m)
+deriving instance Monad m =>       Monad       (FileExtListenerT r m)
+deriving instance MonadIO m =>     MonadIO     (FileExtListenerT r m)
+deriving instance                  MonadTrans  (FileExtListenerT r)
+
+
+json :: (A.ToJSON j, Monad m) =>
+        j
+     -> FileExtListenerT Response m ()
+json i =
+  let r = responseLBS status200 [("Content-Type", "application/json")] $
+            A.encode i
+  in
+  FileExtListenerT $ tell $
+    FileExts $ singleton Json r
+
+jsonp :: (A.ToJSON j, Monad m) =>
+         j
+      -> FileExtListenerT Response m ()
+jsonp i =
+  let r = responseLBS status200 [("Content-Type", "application/javascript")] $
+            A.encode i
+  in
+  FileExtListenerT $ tell $
+    FileExts $ singleton Json r
+
+text :: (Monad m) =>
+        LT.Text
+     -> FileExtListenerT Response m ()
+text i =
+  let r = responseLBS status200 [("Content-Type", "text/plain")] $
+            LT.encodeUtf8 i
+  in
+  FileExtListenerT $ tell $
+    FileExts $ singleton Text r
+--
+-- blaze :: Html -> Response
+-- blaze = HR.renderHtml
+--
+-- lucid :: Monad m => HtmlT m () -> Response
+-- lucid = L.renderTextT
+
+-- json :: ToJSON a => a -> Response
+-- json = lazy-bytestring . encode
+
+-- text :: T.Text -> Response
+-- text = bytestring . TR.encodeUtf8
+
+-- lazy-text :: LT.Text -> Response
+-- lazy-text = lazy-bytestring . LTR.encodeUtf8
+--
+-- builder :: Builder -> Response
+-- builder = responseBuilder
+--
+-- bytestring :: B.ByteString -> Response
+-- bytestring = lazy-bytestring . LB.fromStrict
+--
+-- lazy-bytestring :: LB.ByteString -> Response
+-- lazy-bytestring = responseLBS
diff --git a/src/Web/Routes/Nested/VerbListener.hs b/src/Web/Routes/Nested/VerbListener.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Routes/Nested/VerbListener.hs
@@ -0,0 +1,76 @@
+{-# LANGUAGE DeriveFunctor              #-}
+{-# LANGUAGE DeriveTraversable          #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ScopedTypeVariables        #-}
+{-# LANGUAGE StandaloneDeriving         #-}
+
+module Web.Routes.Nested.VerbListener where
+
+import           Web.Routes.Nested.FileExtListener
+
+import           Network.Wai
+
+import           Control.Applicative hiding (empty)
+import           Control.Monad.Trans
+import           Control.Monad.Writer
+import           Data.Monoid
+import           Data.Foldable
+import           Data.Traversable
+import           Data.Map.Lazy
+
+
+data Verb = Get
+          | Post
+          | Put
+          | Delete
+  deriving (Show, Eq, Ord)
+
+newtype Verbs a = Verbs { unVerbs :: Map (FileExt, Verb) a }
+  deriving (Show, Eq, Functor, Traversable)
+
+deriving instance Monoid      (Verbs a)
+deriving instance Foldable     Verbs
+
+
+newtype VerbListenerT r m a =
+  VerbListenerT { runVerbListenerT :: WriterT (Verbs r) m a }
+    deriving (Functor)
+
+deriving instance Applicative m => Applicative (VerbListenerT r m)
+deriving instance Monad m =>       Monad       (VerbListenerT r m)
+deriving instance MonadIO m =>     MonadIO     (VerbListenerT r m)
+deriving instance                  MonadTrans  (VerbListenerT r)
+
+
+get :: (Monad m) =>
+       FileExtListenerT Response m a
+    -> VerbListenerT Response m ()
+get flistener = do
+  (fileexts :: FileExts Response) <-
+      lift $ execWriterT $ runFileExtListenerT flistener
+  let new = foldrWithKey (\k -> insert (k, Get)) empty $ unFileExts fileexts
+
+  VerbListenerT $ tell $ Verbs $ new
+
+-- post :: MonadIO m =>
+--         (ByteString -> m ())
+--      -> FileExtListener ()
+--      -> VerbListener ()
+-- post fl =
+--   VerbListener $ tell $
+--     Verbs [(Post, fl)]
+--
+-- put :: MonadIO m =>
+--        (ByteString -> m ())
+--     -> FileExtListener ()
+--     -> VerbListener ()
+-- put fl =
+--   VerbListener $ tell $
+--     Verbs [(Put, fl)]
+
+-- delete :: (Monad m) =>
+--           FileExtListenerT m ()
+--        -> VerbListenerT m ()
+-- delete fl =
+--   VerbListenerT $ tell $
+--     Verbs [(Post, fl)]
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
