morgenthum.dev

Write a blog in Haskell - Part 1: Libraries

2019-08-26 21:57

In this article I want to show you common libraries and how you can put it all together to achieve a web application with blog functionality.

So what do we need for a simple blog? A way to

We will discuss each of them one by one.

warp - web server

A web server opens a port, listens for incoming connections, reads requests, calls handlers and writes responses. There is a very mature Haskell library we can use - warp.

The core function of the warp web server is straight forward. You can run an Application on a Port:

run :: Port -> Application -> IO ()

So, what means Application? The Application type is defined in the web application interface - as we will see next.

By the way, there is also a function that accepts a Settings type to configure things like response headers. You can also register hook functions for opened or closed connections and so on. But we don't need these advanced features for this article, so I won't explain them.

wai - web application interface

The wai defines the Application type and all common http types like Request and Response. It's the foundation of web development in Haskell. Finally, the definition of Application is:

type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived

An Application is a function that gets a Request and a function that runs in the IO monad that turns a Response into a ResponseReceived - and the whole function also returns a ResponseReceived.

Okay, this may sound a bit overwhelming, so let's start step by step.

The Request type gives us information about, well, the http request. There are accessor functions for the http version, http method, requested path, query parameters, request headers and so on.

We need a ResponseReceived as return value for the function. How can we get it? Let's take a look at the definition again. We have a function that can turn a Response into a ResponseReceived. Okay, but right now we have no Response. There are response builder functions which can build a Response:

-- | Build a simple response.
responseLBS :: Status -> ResponseHeaders -> ByteString -> Response

-- | Build a response from a file.
responseFile :: Status -> ResponseHeaders -> FilePath -> Maybe FilePart -> Response

The responseLBS function is the simplest form of all response builders. All parameters should be self explanatory. The ByteString is the content of the html response. So we could build a simple Response with this line:

responseLBS status200 [(hContentType, "text/html")] "Hello there!"

Now we can put it together to build our first Application function:

hello :: Application
hello request builder = do 
  let responseHeaders = [(hContentType, "text/plain")]
  builder $ case pathInfo request of
    [] -> responseLBS status200 responseHeaders "Hello"
    _  -> responseLBS status404 responseHeaders "Not found"

That's all we need. The pathInfo function returns the requested url as list of pathes. For example:

As we know - pattern matching is easy in Haskell, especially with lists, so we can easily check for patterns we want to handle. We take the result of responseLBS and pass it to the builder function which turns the Response into the needed ResponseReceived type.

A simple / request will result in a friendly greeting, anything else will return a 404 page.

We need to start the warp web server with our new shining Application to bring it to life:

run 80 hello

Puh, we made it! So what have we done so far? We can

in effectively 4 lines of code!

There are more advanced techniques for routing like wai-routes or even whole monsters comparable to Java's Spring framework like yesod.

Here is the complete listing of our running application:

{-# LANGUAGE OverloadedStrings #-}

module Example where

import Network.HTTP.Types
import Network.Wai
import Network.Wai.Handler.Warp

main :: IO ()
main = run 80 hello

hello :: Application
hello request builder = do 
  let responseHeaders = [(hContentType, "text/plain")]
  builder $ case pathInfo request of
    [] -> responseLBS status200 responseHeaders "Hello"
    _  -> responseLBS status404 responseHeaders "Not found"

cmark - Markdown parser

The easiest way to format blog posts, static pages and so on is to use the Markdown format:

# Hello I am h1 #

`This line is inline code`

We can use this function

commonmarkToHtml :: [CMarkOption] -> Text -> Text

of the CMark module to transform the Markdown code into this HTML code:

<h1>Hello I am h1</h1>
<code>This line is inline code</code>

lucid - EDSL for HTML

Until now we send plain text in our responses. The next step is to deliver valid HTML code. We can use Lucid, an embedded domain specific language, to write HTML code inside Haskell.

Here is an example:

{-# LANGUAGE ExtendedDefaultRules #-}
{-# LANGUAGE OverloadedStrings    #-}

module HtmlSnippets

import qualified Data.Text as T
import           Lucid

links :: [(String, String)]
links = [("Home", "/"), ("About" , "/about")]

page :: T.Text -> Html ()
page content = do
  doctype_
  html_ $ do
    head_ $ do
      meta_ [charset_ "utf-8"]
      link_ [href_ "/style.css", rel_ "stylesheet"]
      title_ "Page title"
    body_ $ do
      header_ $ do
        h1_ $ toHtml "Headline"
        nav_ $ ul_ $ mapM_ toLi links
      main_ $ toHtml content
      footer_ $ p_ $ toHtmlRaw "Copyright &copy; year, author"

toLi :: (String, String) -> Html ()
toLi (name, path) = li_ $ a_ [href_ (T.pack path)] $ toHtml name

I won't go into details how it works, because you need to know how monads work, and it's also a bit out of scope. Instead I will scratch the surface a bit.

This code is recognizable as HTML similar code, but a bit more dynamically. Each function returns Html (), which can later be rendered as real HTML code as text. You can pass HTML snippets as Html () around to another functions and build up a whole page out of small snippets - and you can use any Haskell function you want, because it's just Haskell code. For example, we can use the mapM_ function to transform a list of tuples (names and links) into a list of a-Tags enclosed in li-Tags.

We are now able to deliver HTML code. Just refactor our hello function to:

hello :: Application
hello request builder = do 
  let responseHeaders = [(hContentType, "text/html")]
  let renderPage = renderBS . page
  builder $ case pathInfo request of
    [] -> responseLBS status200 responseHeaders $ renderPage "Hello"
    _  -> responseLBS status404 responseHeaders $ renderPage "Not found"

The new renderPage function is a composition of the page function we defined to build our whole Html () page, and then gets rendered with renderBS as ByteString, which is compatible with the responseLBS function we already discussed.

Now we are able to build HTML in a statically typed way, without the need of a template engine or inline code hacks like JavaServer Pages.

clay - EDSL for CSS

There is also a way to build CSS code in a statically typed manner - with Clay.

{-# LANGUAGE OverloadedStrings #-}

module CssSnippets where

import           Clay
import qualified Data.ByteString.Lazy.Char8 as C8
import qualified Data.Text.Lazy             as T

css :: C8.ByteString
css = C8.pack $ T.unpack $ renderWith compact [] $ do
  body ? do
    fontFamily ["Montserrat"] [sansSerif]
    fontSize $ px 16
    maxWidth $ px 800
  h1 ? do
    display inline
    fontSize $ px 34
    fontWeight normal
  pre ? code ? do
    backgroundColor (other "#fefefe")
    fontSize $ px 14

It works the same way as Lucid works. There is a monad type Css = StyleM () which does all the glueing for us and there are render functions like renderWith to turn the result into a Text.

Now we can easily extend our Application to serve CSS code:

hello :: Application
hello request builder = do 
  let responseHeaders = [(hContentType, "text/html")]
  let renderPage = renderBS . page
  builder $ case pathInfo request of
    []            -> responseLBS status200 responseHeaders $ renderPage "Hello"
    ["style.css"] -> responseLBS status200 [(hContentType, "text/css")] css
    _             -> responseLBS status404 responseHeaders $ renderPage "Not found"

sqlite-simple - SQLite binding

Last but not least, we need a data storage to store blog posts, comments, static page content and so on. For a small blog should a simple SQLite database sufficient.

{-# LANGUAGE OverloadedStrings #-}

module Database

import qualified Data.Text              as T
import           Database.SQLite.Simple

data Page = Page {
  pageTitle :: T.Text,
  pageText  :: T.Text
} deriving (Show)

instance FromRow Page where
  fromRow = Page <$> field <*> field

readPages :: Connection -> IO [Page]
readPages db = query_ db "select title, text from pages" :: IO [Page]

updatePage :: Connection -> Page -> IO ()
updatePage db page = execute db "update pages set text = ? where title = ?" (pageText page, pageTitle page)

This binding library is quite straight forward.

You can

If you want to use these functions, you need to serve a Connection to the database:

fooBar :: IO ()
fooBar = do
  db <- open "storage.db"
  pages <- readPages db
  -- do something with these pages
  close db

Conclusion

These are the core libraries we will use to build our new simple blog in the next part of this tutorial.

Comments

no comments yet

Write a comment