How do I pass the database connection to my HTTP handlers?
Here is a simplified version of my REST API. I am using Scotty and RethinkDB .
Now I have to pass the database connection to each route handler so they can run
query (see coursesAll
). With more than 10 routes, this will be very annoying. I know that I could define route handlers inside the main method and the connection handle h
would be in scope, but that doesn't scale either.
I would like to be able to define top-level functions so that I can put them in different files. How can I clear this code?
Part of my brain knows monads can do this, but how? Scotty uses a monad ActionM
and RethinkDB has a monad too, but I'm not sure how to combine them.
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
import Web.Scotty
import Courses.Course
import qualified Database.RethinkDB as R
import Control.Monad.IO.Class (liftIO)
import Courses.Connection
main :: IO ()
main = do
h <- connectDb
scotty 3000 $ do
get "/" info
get "/courses" (coursesAll h)
info :: ActionM ()
info = text "Courses v1"
coursesAll :: R.RethinkDBHandle -> ActionM ()
coursesAll h = do
courses <- liftIO $ R.run h coursesTable
json $ (courses :: [Course])
connectDb :: IO (R.RethinkDBHandle)
connectDb = do
connection <- R.connect "localhost" 28015 Nothing
let connectionDb = R.use connection (R.db "courses")
return connectionDb
UPDATE: RethinkDB turned out not to be appropriate. Actually, I want to know how to pass global config to my routes. For example:
{-# LANGUAGE OverloadedStrings #-}
import qualified Web.Scotty
import Web.Scotty.Trans
import Data.Text.Lazy
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Reader
import Control.Monad.Trans
data Config = Config Text
main :: IO ()
main = do
let config = Config "Hello World"
-- how to I make this line work?
scottyT 3000 id id routes
routes :: ScottyT Text (ReaderT Config IO) ()
routes = do
get "/" info
info :: ActionT Text (ReaderT Config IO) ()
info = do
-- this part seems like it works!
Config message <- lift ask
text $ "Info: " `append` message
source to share
You are almost gone with an updated question, the only thing you need is to provide two runners functionality scottyT
. So let's take a look at the signature
scottyT
:: (Monad m, MonadIO n)
=> Port
-> (forall a. m a -> n a)
-> (m Response -> IO Response)
-> ScottyT e m ()
-> n ()
m
is the monad you want to push onto the stack ActionT
, and n
is the output you want at startup scottyT
.
In your case, m
there is ReaderT Config IO
, but n
simply IO
.
A function (forall a. m a -> n a)
is a function that converts any computation ReaderT Config IO a
to IO a
, which we can easily do with runReaderT
, so let's define
let readerToIO ma = runReaderT ma config
Next, we need a function to turn m Response
into IO Response
, but since this is the n
same as IO
, we can simply reuse the above function readerToIO
. Thus,
main = do
let config = Config "Hello World"
readerToIO ma = runReaderT ma config
scottyT 3000 readerToIO readerToIO routes
source to share