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 $ 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

1 answer

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

  :: (Monad m, MonadIO n)    
  => Port    
  -> (forall a. m a -> n a) 
  -> (m Response -> IO Response)    
  -> ScottyT e m ()  
  -> n ()



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




All Articles