CRUD App in Haskell with Servant/Opaleye
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
0
down vote
favorite
I was trying to learn how to use libraries like Opaleye and Servant. I wrote this toy Create/Read/Update/Delete App.
-# OPTIONS_GHC -Wall #-
-# LANGUAGE DataKinds #-
-# LANGUAGE FunctionalDependencies #-
-# LANGUAGE DeriveGeneric #-
-# LANGUAGE FlexibleInstances #-
-# LANGUAGE GeneralizedNewtypeDeriving #-
-# LANGUAGE MultiParamTypeClasses #-
-# LANGUAGE OverloadedStrings #-
-# LANGUAGE RankNTypes #-
-# LANGUAGE ScopedTypeVariables #-
-# LANGUAGE TypeOperators #-
-# LANGUAGE Arrows #-
-# LANGUAGE TemplateHaskell #-
module Main where
import Servant
import Data.Aeson
import Data.Aeson.Types
import Data.Aeson.Casing
import GHC.Generics
import Opaleye
import Database.PostgreSQL.Simple
import Control.Lens.TH (makeLensesWith, abbreviatedFields)
import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
import Control.Arrow
import Control.Monad.IO.Class (liftIO)
import Network.Wai.Handler.Warp
import qualified GHC.Int
type CrudAPI = "read" :> Get '[JSON] [User]
:<|> "read" :> Capture "name" String :> Get '[JSON] [User]
:<|> "create" :> ReqBody '[JSON] User :> Post '[JSON] [GHC.Int.Int64]
:<|> "update" :> ReqBody '[JSON] User :> Post '[JSON] [GHC.Int.Int64]
:<|> "delete" :> ReqBody '[PlainText] String :> Post '[JSON] [GHC.Int.Int64]
data UserPoly name city age = User
userName :: name
, userCity :: city
, userAge :: age
deriving (Eq, Show, Generic)
type User = UserPoly String String Int
$(makeAdaptorAndInstance "pUser" ''UserPoly)
$(makeLensesWith abbreviatedFields ''UserPoly)
userTable :: Table
(UserPoly (Column PGText) (Column PGText) (Column PGInt4))
(UserPoly (Column PGText) (Column PGText) (Column PGInt4))
userTable = Table "users" (pUser User userName = required "name",
userCity = required "city",
userAge = required "age")
instance FromJSON User
where
parseJSON = genericParseJSON $ aesonPrefix camelCase
instance ToJSON User
where
toJSON = genericToJSON $ aesonPrefix camelCase
toEncoding = genericToEncoding $ aesonPrefix camelCase
crudAPI :: Server CrudAPI
crudAPI = readAPI1 :<|> readAPI2 :<|> createAPI :<|> updateAPI :<|> deleteAPI
where
readAPI1 = liftIO $ dbConnection >>= selectAllRows
readAPI2 = ns -> liftIO $ dbConnection >>= conn -> runQuery conn (nameQuery ns)
createAPI = u -> liftIO $ insertRow u
updateAPI = u -> liftIO $ updateRow u
deleteAPI = n -> liftIO $ deleteRow n
dbConnection :: IO Connection
dbConnection = connect ConnectInfoconnectHost="localhost"
,connectPort=5432
,connectDatabase="mydb"
,connectPassword="b2b"
,connectUser="b2b"
selectAllRows :: Connection -> IO [User]
selectAllRows conn = runQuery conn $ queryTable userTable
nameQuery :: String -> Opaleye.Query (UserPoly (Column PGText) (Column PGText) (Column PGInt4))
nameQuery ns = proc () -> do
row <- (queryTable userTable) -< ()
restrict -< (userName row .== pgString ns)
returnA -< row
insertRow :: User -> IO [GHC.Int.Int64]
insertRow u = do
conn <- dbConnection
success <- runInsert conn userTable (User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
return [success]
updateRow :: User -> IO [GHC.Int.Int64]
updateRow u = do
conn <- dbConnection
success <- runUpdate conn userTable (const $ User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
( entry -> userName entry .== pgString (userName u))
return [success]
deleteRow :: String -> IO [GHC.Int.Int64]
deleteRow n = do
conn <- dbConnection
success <- runDelete conn userTable (entry -> userName entry .== pgString n)
return [success]
main :: IO ()
main = run 8081 (serve (Proxy :: Proxy CrudAPI) crudAPI)
Suggestions on coding styles and other things are welcome
haskell crud
add a comment |Â
up vote
0
down vote
favorite
I was trying to learn how to use libraries like Opaleye and Servant. I wrote this toy Create/Read/Update/Delete App.
-# OPTIONS_GHC -Wall #-
-# LANGUAGE DataKinds #-
-# LANGUAGE FunctionalDependencies #-
-# LANGUAGE DeriveGeneric #-
-# LANGUAGE FlexibleInstances #-
-# LANGUAGE GeneralizedNewtypeDeriving #-
-# LANGUAGE MultiParamTypeClasses #-
-# LANGUAGE OverloadedStrings #-
-# LANGUAGE RankNTypes #-
-# LANGUAGE ScopedTypeVariables #-
-# LANGUAGE TypeOperators #-
-# LANGUAGE Arrows #-
-# LANGUAGE TemplateHaskell #-
module Main where
import Servant
import Data.Aeson
import Data.Aeson.Types
import Data.Aeson.Casing
import GHC.Generics
import Opaleye
import Database.PostgreSQL.Simple
import Control.Lens.TH (makeLensesWith, abbreviatedFields)
import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
import Control.Arrow
import Control.Monad.IO.Class (liftIO)
import Network.Wai.Handler.Warp
import qualified GHC.Int
type CrudAPI = "read" :> Get '[JSON] [User]
:<|> "read" :> Capture "name" String :> Get '[JSON] [User]
:<|> "create" :> ReqBody '[JSON] User :> Post '[JSON] [GHC.Int.Int64]
:<|> "update" :> ReqBody '[JSON] User :> Post '[JSON] [GHC.Int.Int64]
:<|> "delete" :> ReqBody '[PlainText] String :> Post '[JSON] [GHC.Int.Int64]
data UserPoly name city age = User
userName :: name
, userCity :: city
, userAge :: age
deriving (Eq, Show, Generic)
type User = UserPoly String String Int
$(makeAdaptorAndInstance "pUser" ''UserPoly)
$(makeLensesWith abbreviatedFields ''UserPoly)
userTable :: Table
(UserPoly (Column PGText) (Column PGText) (Column PGInt4))
(UserPoly (Column PGText) (Column PGText) (Column PGInt4))
userTable = Table "users" (pUser User userName = required "name",
userCity = required "city",
userAge = required "age")
instance FromJSON User
where
parseJSON = genericParseJSON $ aesonPrefix camelCase
instance ToJSON User
where
toJSON = genericToJSON $ aesonPrefix camelCase
toEncoding = genericToEncoding $ aesonPrefix camelCase
crudAPI :: Server CrudAPI
crudAPI = readAPI1 :<|> readAPI2 :<|> createAPI :<|> updateAPI :<|> deleteAPI
where
readAPI1 = liftIO $ dbConnection >>= selectAllRows
readAPI2 = ns -> liftIO $ dbConnection >>= conn -> runQuery conn (nameQuery ns)
createAPI = u -> liftIO $ insertRow u
updateAPI = u -> liftIO $ updateRow u
deleteAPI = n -> liftIO $ deleteRow n
dbConnection :: IO Connection
dbConnection = connect ConnectInfoconnectHost="localhost"
,connectPort=5432
,connectDatabase="mydb"
,connectPassword="b2b"
,connectUser="b2b"
selectAllRows :: Connection -> IO [User]
selectAllRows conn = runQuery conn $ queryTable userTable
nameQuery :: String -> Opaleye.Query (UserPoly (Column PGText) (Column PGText) (Column PGInt4))
nameQuery ns = proc () -> do
row <- (queryTable userTable) -< ()
restrict -< (userName row .== pgString ns)
returnA -< row
insertRow :: User -> IO [GHC.Int.Int64]
insertRow u = do
conn <- dbConnection
success <- runInsert conn userTable (User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
return [success]
updateRow :: User -> IO [GHC.Int.Int64]
updateRow u = do
conn <- dbConnection
success <- runUpdate conn userTable (const $ User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
( entry -> userName entry .== pgString (userName u))
return [success]
deleteRow :: String -> IO [GHC.Int.Int64]
deleteRow n = do
conn <- dbConnection
success <- runDelete conn userTable (entry -> userName entry .== pgString n)
return [success]
main :: IO ()
main = run 8081 (serve (Proxy :: Proxy CrudAPI) crudAPI)
Suggestions on coding styles and other things are welcome
haskell crud
add a comment |Â
up vote
0
down vote
favorite
up vote
0
down vote
favorite
I was trying to learn how to use libraries like Opaleye and Servant. I wrote this toy Create/Read/Update/Delete App.
-# OPTIONS_GHC -Wall #-
-# LANGUAGE DataKinds #-
-# LANGUAGE FunctionalDependencies #-
-# LANGUAGE DeriveGeneric #-
-# LANGUAGE FlexibleInstances #-
-# LANGUAGE GeneralizedNewtypeDeriving #-
-# LANGUAGE MultiParamTypeClasses #-
-# LANGUAGE OverloadedStrings #-
-# LANGUAGE RankNTypes #-
-# LANGUAGE ScopedTypeVariables #-
-# LANGUAGE TypeOperators #-
-# LANGUAGE Arrows #-
-# LANGUAGE TemplateHaskell #-
module Main where
import Servant
import Data.Aeson
import Data.Aeson.Types
import Data.Aeson.Casing
import GHC.Generics
import Opaleye
import Database.PostgreSQL.Simple
import Control.Lens.TH (makeLensesWith, abbreviatedFields)
import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
import Control.Arrow
import Control.Monad.IO.Class (liftIO)
import Network.Wai.Handler.Warp
import qualified GHC.Int
type CrudAPI = "read" :> Get '[JSON] [User]
:<|> "read" :> Capture "name" String :> Get '[JSON] [User]
:<|> "create" :> ReqBody '[JSON] User :> Post '[JSON] [GHC.Int.Int64]
:<|> "update" :> ReqBody '[JSON] User :> Post '[JSON] [GHC.Int.Int64]
:<|> "delete" :> ReqBody '[PlainText] String :> Post '[JSON] [GHC.Int.Int64]
data UserPoly name city age = User
userName :: name
, userCity :: city
, userAge :: age
deriving (Eq, Show, Generic)
type User = UserPoly String String Int
$(makeAdaptorAndInstance "pUser" ''UserPoly)
$(makeLensesWith abbreviatedFields ''UserPoly)
userTable :: Table
(UserPoly (Column PGText) (Column PGText) (Column PGInt4))
(UserPoly (Column PGText) (Column PGText) (Column PGInt4))
userTable = Table "users" (pUser User userName = required "name",
userCity = required "city",
userAge = required "age")
instance FromJSON User
where
parseJSON = genericParseJSON $ aesonPrefix camelCase
instance ToJSON User
where
toJSON = genericToJSON $ aesonPrefix camelCase
toEncoding = genericToEncoding $ aesonPrefix camelCase
crudAPI :: Server CrudAPI
crudAPI = readAPI1 :<|> readAPI2 :<|> createAPI :<|> updateAPI :<|> deleteAPI
where
readAPI1 = liftIO $ dbConnection >>= selectAllRows
readAPI2 = ns -> liftIO $ dbConnection >>= conn -> runQuery conn (nameQuery ns)
createAPI = u -> liftIO $ insertRow u
updateAPI = u -> liftIO $ updateRow u
deleteAPI = n -> liftIO $ deleteRow n
dbConnection :: IO Connection
dbConnection = connect ConnectInfoconnectHost="localhost"
,connectPort=5432
,connectDatabase="mydb"
,connectPassword="b2b"
,connectUser="b2b"
selectAllRows :: Connection -> IO [User]
selectAllRows conn = runQuery conn $ queryTable userTable
nameQuery :: String -> Opaleye.Query (UserPoly (Column PGText) (Column PGText) (Column PGInt4))
nameQuery ns = proc () -> do
row <- (queryTable userTable) -< ()
restrict -< (userName row .== pgString ns)
returnA -< row
insertRow :: User -> IO [GHC.Int.Int64]
insertRow u = do
conn <- dbConnection
success <- runInsert conn userTable (User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
return [success]
updateRow :: User -> IO [GHC.Int.Int64]
updateRow u = do
conn <- dbConnection
success <- runUpdate conn userTable (const $ User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
( entry -> userName entry .== pgString (userName u))
return [success]
deleteRow :: String -> IO [GHC.Int.Int64]
deleteRow n = do
conn <- dbConnection
success <- runDelete conn userTable (entry -> userName entry .== pgString n)
return [success]
main :: IO ()
main = run 8081 (serve (Proxy :: Proxy CrudAPI) crudAPI)
Suggestions on coding styles and other things are welcome
haskell crud
I was trying to learn how to use libraries like Opaleye and Servant. I wrote this toy Create/Read/Update/Delete App.
-# OPTIONS_GHC -Wall #-
-# LANGUAGE DataKinds #-
-# LANGUAGE FunctionalDependencies #-
-# LANGUAGE DeriveGeneric #-
-# LANGUAGE FlexibleInstances #-
-# LANGUAGE GeneralizedNewtypeDeriving #-
-# LANGUAGE MultiParamTypeClasses #-
-# LANGUAGE OverloadedStrings #-
-# LANGUAGE RankNTypes #-
-# LANGUAGE ScopedTypeVariables #-
-# LANGUAGE TypeOperators #-
-# LANGUAGE Arrows #-
-# LANGUAGE TemplateHaskell #-
module Main where
import Servant
import Data.Aeson
import Data.Aeson.Types
import Data.Aeson.Casing
import GHC.Generics
import Opaleye
import Database.PostgreSQL.Simple
import Control.Lens.TH (makeLensesWith, abbreviatedFields)
import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
import Control.Arrow
import Control.Monad.IO.Class (liftIO)
import Network.Wai.Handler.Warp
import qualified GHC.Int
type CrudAPI = "read" :> Get '[JSON] [User]
:<|> "read" :> Capture "name" String :> Get '[JSON] [User]
:<|> "create" :> ReqBody '[JSON] User :> Post '[JSON] [GHC.Int.Int64]
:<|> "update" :> ReqBody '[JSON] User :> Post '[JSON] [GHC.Int.Int64]
:<|> "delete" :> ReqBody '[PlainText] String :> Post '[JSON] [GHC.Int.Int64]
data UserPoly name city age = User
userName :: name
, userCity :: city
, userAge :: age
deriving (Eq, Show, Generic)
type User = UserPoly String String Int
$(makeAdaptorAndInstance "pUser" ''UserPoly)
$(makeLensesWith abbreviatedFields ''UserPoly)
userTable :: Table
(UserPoly (Column PGText) (Column PGText) (Column PGInt4))
(UserPoly (Column PGText) (Column PGText) (Column PGInt4))
userTable = Table "users" (pUser User userName = required "name",
userCity = required "city",
userAge = required "age")
instance FromJSON User
where
parseJSON = genericParseJSON $ aesonPrefix camelCase
instance ToJSON User
where
toJSON = genericToJSON $ aesonPrefix camelCase
toEncoding = genericToEncoding $ aesonPrefix camelCase
crudAPI :: Server CrudAPI
crudAPI = readAPI1 :<|> readAPI2 :<|> createAPI :<|> updateAPI :<|> deleteAPI
where
readAPI1 = liftIO $ dbConnection >>= selectAllRows
readAPI2 = ns -> liftIO $ dbConnection >>= conn -> runQuery conn (nameQuery ns)
createAPI = u -> liftIO $ insertRow u
updateAPI = u -> liftIO $ updateRow u
deleteAPI = n -> liftIO $ deleteRow n
dbConnection :: IO Connection
dbConnection = connect ConnectInfoconnectHost="localhost"
,connectPort=5432
,connectDatabase="mydb"
,connectPassword="b2b"
,connectUser="b2b"
selectAllRows :: Connection -> IO [User]
selectAllRows conn = runQuery conn $ queryTable userTable
nameQuery :: String -> Opaleye.Query (UserPoly (Column PGText) (Column PGText) (Column PGInt4))
nameQuery ns = proc () -> do
row <- (queryTable userTable) -< ()
restrict -< (userName row .== pgString ns)
returnA -< row
insertRow :: User -> IO [GHC.Int.Int64]
insertRow u = do
conn <- dbConnection
success <- runInsert conn userTable (User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
return [success]
updateRow :: User -> IO [GHC.Int.Int64]
updateRow u = do
conn <- dbConnection
success <- runUpdate conn userTable (const $ User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
( entry -> userName entry .== pgString (userName u))
return [success]
deleteRow :: String -> IO [GHC.Int.Int64]
deleteRow n = do
conn <- dbConnection
success <- runDelete conn userTable (entry -> userName entry .== pgString n)
return [success]
main :: IO ()
main = run 8081 (serve (Proxy :: Proxy CrudAPI) crudAPI)
Suggestions on coding styles and other things are welcome
haskell crud
asked May 10 at 1:02
Agnishom Chattopadhyay
21818
21818
add a comment |Â
add a comment |Â
2 Answers
2
active
oldest
votes
up vote
0
down vote
Three of your definitions have a lot in common.
crudAPI :: Server CrudAPI
crudAPI = readAPI1 :<|> readAPI2 :<|> createAPI :<|> updateAPI :<|> deleteAPI where
readAPI1 = liftIO $ dbConnection >>= selectAllRows
readAPI2 = ns -> liftIO $ dbConnection >>= conn -> runQuery conn (nameQuery ns)
createAPI = u -> wrap insertRow $ f -> f
(User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
updateAPI = u -> wrap runUpdate $ f -> f
(const $ User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
(entry -> userName entry .== pgString (userName u))
deleteAPI = n -> wrap runDelete $ f -> f
(entry -> userName entry .== pgString n)
wrap f g = liftIO $ do
conn <- dbConnection
(:) <$> g (f conn userTable)
add a comment |Â
up vote
0
down vote
I quickly looked at the code, and didn't see that we use the lazyness of the fields of Userpoly
, so we could make them strict:
data UserPoly name city age = User
userName :: !name
, userCity :: !city
, userAge :: !age
deriving (Eq, Show, Generic)
add a comment |Â
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
0
down vote
Three of your definitions have a lot in common.
crudAPI :: Server CrudAPI
crudAPI = readAPI1 :<|> readAPI2 :<|> createAPI :<|> updateAPI :<|> deleteAPI where
readAPI1 = liftIO $ dbConnection >>= selectAllRows
readAPI2 = ns -> liftIO $ dbConnection >>= conn -> runQuery conn (nameQuery ns)
createAPI = u -> wrap insertRow $ f -> f
(User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
updateAPI = u -> wrap runUpdate $ f -> f
(const $ User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
(entry -> userName entry .== pgString (userName u))
deleteAPI = n -> wrap runDelete $ f -> f
(entry -> userName entry .== pgString n)
wrap f g = liftIO $ do
conn <- dbConnection
(:) <$> g (f conn userTable)
add a comment |Â
up vote
0
down vote
Three of your definitions have a lot in common.
crudAPI :: Server CrudAPI
crudAPI = readAPI1 :<|> readAPI2 :<|> createAPI :<|> updateAPI :<|> deleteAPI where
readAPI1 = liftIO $ dbConnection >>= selectAllRows
readAPI2 = ns -> liftIO $ dbConnection >>= conn -> runQuery conn (nameQuery ns)
createAPI = u -> wrap insertRow $ f -> f
(User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
updateAPI = u -> wrap runUpdate $ f -> f
(const $ User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
(entry -> userName entry .== pgString (userName u))
deleteAPI = n -> wrap runDelete $ f -> f
(entry -> userName entry .== pgString n)
wrap f g = liftIO $ do
conn <- dbConnection
(:) <$> g (f conn userTable)
add a comment |Â
up vote
0
down vote
up vote
0
down vote
Three of your definitions have a lot in common.
crudAPI :: Server CrudAPI
crudAPI = readAPI1 :<|> readAPI2 :<|> createAPI :<|> updateAPI :<|> deleteAPI where
readAPI1 = liftIO $ dbConnection >>= selectAllRows
readAPI2 = ns -> liftIO $ dbConnection >>= conn -> runQuery conn (nameQuery ns)
createAPI = u -> wrap insertRow $ f -> f
(User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
updateAPI = u -> wrap runUpdate $ f -> f
(const $ User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
(entry -> userName entry .== pgString (userName u))
deleteAPI = n -> wrap runDelete $ f -> f
(entry -> userName entry .== pgString n)
wrap f g = liftIO $ do
conn <- dbConnection
(:) <$> g (f conn userTable)
Three of your definitions have a lot in common.
crudAPI :: Server CrudAPI
crudAPI = readAPI1 :<|> readAPI2 :<|> createAPI :<|> updateAPI :<|> deleteAPI where
readAPI1 = liftIO $ dbConnection >>= selectAllRows
readAPI2 = ns -> liftIO $ dbConnection >>= conn -> runQuery conn (nameQuery ns)
createAPI = u -> wrap insertRow $ f -> f
(User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
updateAPI = u -> wrap runUpdate $ f -> f
(const $ User (pgString (userName u)) (pgString (userCity u)) (pgInt4 (userAge u)))
(entry -> userName entry .== pgString (userName u))
deleteAPI = n -> wrap runDelete $ f -> f
(entry -> userName entry .== pgString n)
wrap f g = liftIO $ do
conn <- dbConnection
(:) <$> g (f conn userTable)
answered May 27 at 22:51
Gurkenglas
2,658411
2,658411
add a comment |Â
add a comment |Â
up vote
0
down vote
I quickly looked at the code, and didn't see that we use the lazyness of the fields of Userpoly
, so we could make them strict:
data UserPoly name city age = User
userName :: !name
, userCity :: !city
, userAge :: !age
deriving (Eq, Show, Generic)
add a comment |Â
up vote
0
down vote
I quickly looked at the code, and didn't see that we use the lazyness of the fields of Userpoly
, so we could make them strict:
data UserPoly name city age = User
userName :: !name
, userCity :: !city
, userAge :: !age
deriving (Eq, Show, Generic)
add a comment |Â
up vote
0
down vote
up vote
0
down vote
I quickly looked at the code, and didn't see that we use the lazyness of the fields of Userpoly
, so we could make them strict:
data UserPoly name city age = User
userName :: !name
, userCity :: !city
, userAge :: !age
deriving (Eq, Show, Generic)
I quickly looked at the code, and didn't see that we use the lazyness of the fields of Userpoly
, so we could make them strict:
data UserPoly name city age = User
userName :: !name
, userCity :: !city
, userAge :: !age
deriving (Eq, Show, Generic)
answered Jul 3 at 0:50
Olivier Sohn
101
101
add a comment |Â
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f194069%2fcrud-app-in-haskell-with-servant-opaleye%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password