Building web applications with Haskell: A beginner's guide

Are you looking for a new programming language to learn that is powerful, functional, and well-suited for web development? Look no further than Haskell! Haskell is a functional programming language that is great for building expressive, high-performance, and maintainable web applications. In this article, we’ll guide you through building a web application in Haskell from scratch.

Why Haskell for web development?

Before diving into Haskell web development, let's discuss why it's an excellent choice for building web applications. Haskell's strong type system makes catching errors early in the development process a breeze. Haskell's syntax is concise and expressive, making complex functionality easy to write and maintain. Haskell is also a great choice for high-performance applications due to its strong typing and lazy evaluation.

Setting up your Haskell development environment

Before we jump into building web applications, we need to set up our development environment. To enable a seamless development process with Haskell, we recommend installing Haskell Stack. Stack is a build tool and a package manager that makes managing dependencies and building Haskell packages and libraries a breeze.

Once we have Haskell Stack installed, we create a new project by running the following command in our terminal:

stack new my-web-app

This command creates a new directory called my-web-app that contains a simple project template.

Getting familiar with Haskell web frameworks

Now that we have our project set up, we need a web framework to build our application. There are many web frameworks available in Haskell, but for this tutorial, we’ll be working with the Yesod framework. Yesod is a high-level web framework that provides powerful tools for building scalable web applications. It emphasizes type safety and enforces a strict separation of concerns between the web application and the database.

We can add Yesod to our project by adding it to our dependency list in the stack.yaml file:

resolver: lts-17.14

- .
- yesod

extra-deps: []

After running stack build to install the dependencies, we can use the Yesod executable to generate a new project skeleton:

stack exec -- yesod init

This command generates a skeleton application with boilerplate code already implemented, ready for us to start customizing.

Defining the data model of your web application

The key to building a great web application is a well-designed data model. Our web application will store and display a list of articles, so our data model will include an Article type comprising a title, content, and date.

{-# LANGUAGE DeriveGeneric #-}

module Model where

import Data.Text (Text)
import Data.Time (UTCTime)
import GHC.Generics

data Article = Article
  { articleTitle :: Text
  , articleContent :: Text
  , articleDate :: UTCTime
  deriving (Show, Eq, Generic)

In the code above, we've defined the Article type using the DeriveGeneric language pragma. This pragma allows us to use a generic programming approach to automatically derive serialization and deserialization functions for our data model.

Defining the routes of your web application

Routes are how users interact with our web application. We define them in a file called config/routes

# config/routes
/ HomeR GET
/articles ArticlesR GET POST
/article/#Text ArticleR GET

In the code above, we’ve defined three routes: HomeR for our homepage, ArticlesR to display all articles or accept a new article creation form, and ArticleR to display a specific article.

Defining the handlers of your web application

Handlers are the functions that control the behavior of your web application for each route. We define them in a file called Handler.hs.

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}

module Handler where

import Data.Aeson (decode, encode)
import Data.ByteString.Lazy (toStrict)
import Data.Text (Text)
import Data.Time (getCurrentTime)
import Model (Article(..))
import Yesod

data WebApp = WebApp

mkYesod "WebApp" [parseRoutes|
/ HomeR GET
/articles ArticlesR GET POST
/article/#Text ArticleR GET

instance Yesod WebApp

instance YesodPersist WebApp where
  type YesodPersistBackend WebApp = SqlBackend
  runDB action = do
    pool <- getPool
    runSqlPool action pool

getHomeR :: Handler Html
getHomeR = defaultLayout $ do
    <h1>Welcome to my web app!</h1>
    <a href=@{ArticlesR}>Go to the articles page.

getArticlesR :: Handler TypedContent
getArticlesR = do
  articles <- runDB $ selectList [] [Desc ArticleDate]
  selectRep $ do
    provideRep $ defaultLayout $ do
        <h1>List of articles</h1>
          $forall Entity _ article <- articles
              <a href=@{ArticleR (articleTitle article)}>#{articleTitle article}
              #{articleDate article}
        <a href=@{ArticlesR}>Create a new article.
    provideJson articles

postArticlesR :: Handler Value
postArticlesR = do
  articleJson <- requireJsonBody :: Handler Value
  let decodeArticle = decode $ encode articleJson :: Maybe Article
  case decodeArticle of
    Just article -> do
      date <- liftIO getCurrentTime
      _ <- runDB $ insert $ article { articleDate = date }
      returnJson article
    Nothing -> invalidArgs ["Invalid parameters."]

getArticleR :: Text -> Handler TypedContent
getArticleR title = do
  maybeArticle <- runDB $ selectFirst [ArticleTitle ==. title] []
  case maybeArticle of
    Just (Entity _ article) -> selectRep $ do
      provideRep $ defaultLayout $ do
          <h1>#{articleTitle article}</h1>
          <p>#{articleContent article}</p>
      provideJson article
    Nothing -> notFound

getPool :: Handler (Pool SqlBackend)
getPool = do
  app <- getYesod
  return $ appConnPool app

In the code above, we define our WebApp type and its Yesod instance. We also define the handlers for each of our routes, including getHomeR, which renders the homepage, getArticlesR, which queries the database and returns a list of all articles, postArticlesR, which inserts new articles into the database, and getArticleR, which queries the database and displays a specific article.

Creating the database schema

To interact with our PostgreSQL database, we'll use the persistent-postgresql package to define a Schema.hs file:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeFamilies #-}

module Schema where

import Data.Text (Text)
import Data.Time (UTCTime)
import Database.Persist
import Database.Persist.Postgresql
import GHC.Generics

import Model

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
    title Text
    content Text
    date UTCTime default=now()

    deriving Generic Show Eq

fromModel :: Article -> ArticleDb
fromModel (Article title content date) =
  ArticleDb title content date

toModel :: ArticleDb -> Article
toModel (ArticleDb title content date) =
  Article title content date

Our Schema.hs file defines a single entity named ArticleDb, with fields corresponding to the fields in our Article type. Additionally, we define two functions for converting between our Haskell types and our database types.

Running the web application

With our data model, routes, handlers, and database schema defined, we can start our web application using stack exec -- yesod devel. With our application running, we can visit http://localhost:3000/ to see our homepage.

Homepage of my web app

We can also visit http://localhost:3000/articles to see a list of articles.

List of articles

When we click on the title of an article, we navigate to the page for that article.

Single article page


In this article, we’ve shown you how to build a web application with Haskell and Yesod from scratch. We’ve covered defining the data model, routes, handlers, and database schema, and run the application using the Yesod executable.

With Haskell’s powerful type system and high-level web frameworks like Yesod, building expressive, scalable, and maintainable web applications has never been easier. We hope this beginner’s guide will help you get started building your own web applications with Haskell.

Editor Recommended Sites

AI and Tech News
Best Online AI Courses
Classic Writing Analysis
Tears of the Kingdom Roleplay
GSLM: Generative spoken language model, Generative Spoken Language Model getting started guides
Ocaml Tips: Ocaml Programming Tips and tricks
Developer Key Takeaways: Dev lessons learned and best practice from todays top conference videos, courses and books
Haskell Programming: Learn haskell programming language. Best practice and getting started guides
Video Game Speedrun: Youtube videos of the most popular games being speed run