vidéo peertube - vidéo youtube - dépôt git

Développement web en Haskell

Introduction

Haskell est bien adapté au développement web côté-serveur : nombreuses bibliothèques, avantages du typage fort…

Objectif de ce tutoriel : exemple d’application web pour gérer une “TODO list”. Fonctionnalités à mettre en oeuvre :

Application de base

Serveur web de routage d’url avec la bibliothèque scotty.

Bibliothèques Haskell de frameworks web : frameworks lourds à la “Ruby on Rails” (yesod, snap, hapstack…), frameworks légers à la “Ruby Sinatra” (scotty, spock…).

main :: IO ()
main = scotty 3000 $ do   -- crée un serveur web sur le port 3000

  -- paramétrage du serveur
  middleware logStdoutDev
  middleware simpleCors

  -- route racine "/"
  get "/" $ html "<html><body><a href='page2'>page2</a></body></html>"

  -- route "/page2"
  get "/page2" $ html "hello"

Base de données

Accès à une base sqlite avec la bibliothèque sqlite-simple.

Bibliothèques Haskell de base de données : bas-niveau (hdbc-odbc…), niveau moyen (sqlite-simple, postgresql-simple…), haut-niveau (persistent, acid-state..).

-- définit un type de données "Task"
data Task = Task
  { taskId :: Int
  , taskName :: L.Text
  } deriving (Show, Generic)
-- définit comment créer une valeur de type Task à partir de données de la base
instance FromRow Task where
  fromRow = Task <$> field <*> field

-- nom du fichier contenant la base sqlite
dbName :: String
dbName = "todolist.db"
selectTasks :: IO [Task]
selectTasks = 
  SQL.withConnection dbName (\c -> SQL.query_ c "SELECT * FROM task")

-- ...
main :: IO ()
main = scotty 3000 $ do
  -- ...
  get "/" $ do                      -- route racine
    myTasks <- liftIO selectTasks   -- accède à la base de données
    html $ renderHome myTasks       -- calcule la page résultat
  -- ...

Export JSON

Bibliothèque aeson.

instance ToJSON Task
main :: IO ()
main = scotty 3000 $ do
  -- ...
  get "/tasks" $ liftIO selectTasks >>= json

Génération de code HTML/CSS

Les bibliothèques lucid et clay fournissent des langages dédiés intégrés (EDSL) pour générer du code HTML et CSS.

bodyCss :: C.Css
bodyCss = C.body C.? do
  C.backgroundColor  C.beige

divCss :: C.Css
divCss = C.div C.? do
  C.border           C.solid (C.px 1) C.black
  C.backgroundColor  C.azure
renderHome :: [Task] -> L.Text
renderHome theTasks = renderText $ do
  doctype_
  html_ $ do
    header_ $ style_ $ L.toStrict $ C.render $ do
      bodyCss
      divCss
    body_ $ do
      h1_ "TODO list"
      div_ $ ul_ $ mapM_ formatTask theTasks
      form_ [action_ "/add", method_ "post"] $ do
        p_ $ do
          "New task: " 
          input_ [name_ "task_name"] 
          input_ [type_ "submit"]
      p_ $ a_ [href_ "tasks"] "Get JSON data"
    where formatTask :: Task -> Lucid.Html ()
          formatTask (Task tId tName) = 
            li_ $ do
              toHtml tName
              " - "
              a_ [href_ (T.concat ["del?task_id=", T.pack $ show tId])] "delete"

Conclusion et perspectives