Building a Web Application with Haskell

Are you tired of using the same programming languages to build your web applications? Are you looking for a language that can help you build high-performance applications without sacrificing readability or maintainability? Look no further than Haskell!

Haskell is a functional programming language that is gaining popularity in the web development community due to its ability to create efficient, expressive and type-safe code. In this article, we will walk you through the process of building a web application with Haskell, from development environment setup to deployment.

Set Up a Development Environment

Before we start building our web application, we need to set up our development environment. We will be using Stack, a build tool for Haskell, to manage dependencies and sandbox our projects.

$ curl -sSL https://get.haskellstack.org/ | sh

Once we have Stack installed, we can use it to create a new project.

$ stack new my-app

This command will create a new directory called my-app with a basic project structure.

Create a Simple Web Server

Let's start by creating a simple web server. We will be using the wai and warp packages to create our server.

Add the following packages to your my-app.cabal file.

...
executable my-app
  main-is:             Main.hs
  other-modules:       Lib
  hs-source-dirs:      src
  build-depends:       base >= 4.7 && < 5
                     , wai
                     , warp
  default-language:    Haskell2010
...

In src/Main.hs, add the following code.

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Network.Wai
import Network.HTTP.Types
import Network.Wai.Handler.Warp (run)

app :: Application
app _ respond = respond $ responseLBS
  status200
  [("Content-Type", "text/plain")]
  "Hello, world!"

main :: IO ()
main = run 8080 app

We have defined a simple Application that responds to every request with a "Hello, world!" message. We then start our server on port 8080 using run.

Add Database Support

Now that we have our server up and running, let's add database support to our application. We will be using the postgresql-simple package to interact with our PostgreSQL database.

Add the following package to your my-app.cabal file.

...
executable my-app
  main-is:             Main.hs
  other-modules:       Lib
  hs-source-dirs:      src
  build-depends:       base >= 4.7 && < 5
                     , wai
                     , warp
                     , postgresql-simple
  default-language:    Haskell2010
...

In src/Lib.hs, add the following code.

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}

module Lib where

import GHC.Generics
import Data.Aeson
import Network.Wai
import Network.HTTP.Types
import Network.Wai.Handler.Warp (run)
import Database.PostgreSQL.Simple

data User = User { userId :: Int, userName :: String }
  deriving (Show, Generic)

instance ToJSON User

app :: Connection -> Application
app conn req respond = do
  users <- liftIO $ query_ conn "SELECT id, name FROM users"
  respond $ responseLBS
    status200
    [("Content-Type", "application/json")]
    $ encode users

createTable :: Connection -> IO ()
createTable conn =
  execute_ conn "CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL);"

addUser :: Connection -> String -> IO Integer
addUser conn name =
  execute conn "INSERT INTO users (name) VALUES (?)"
    (Only name)

main :: IO ()
main = do
  conn <- connectPostgreSQL "postgresql://user:password@localhost/mydb"
  createTable conn
  run 8080 $ app conn

We have defined a User data type and made it an instance of the ToJSON typeclass to allow us to serialize it to JSON. We have modified our app function to query our users table and return the results as a JSON response. Finally, we have defined two helper functions createTable and addUser to assist with database manipulation.

Our main function now establishes a connection to our database, creates the users table if it does not exist, and passes the connection to our app function.

Deploy to Production

Now that we have a functioning web application with database support, let's deploy it to production. We will be using Docker to containerize our application and PostgreSQL.

Create a new Dockerfile in the root of our project with the following contents.

FROM fpco/stack-build:lts-16.31 AS builder

RUN mkdir /opt/build
WORKDIR /opt/build

COPY stack.yaml /opt/build/
COPY my-app.cabal /opt/build/
RUN stack setup
RUN stack build --only-dependencies

COPY . /opt/build/
RUN stack install --system-ghc

FROM ubuntu:20.04

RUN apt-get update && apt-get install -y libpq-dev
COPY --from=builder /usr/local/bin/my-app /usr/local/bin/my-app

EXPOSE 8080

CMD ["my-app"]

This Dockerfile uses two stages to build our application. The first stage uses fpco/stack-build as a base image and installs our dependencies. The second stage uses ubuntu:20.04 as a base image and copies our binary from the previous stage.

We will be using Docker Compose to manage our production deployment. Create a new docker-compose.yml file with the following contents.

version: '3'
services:
  db:
    image: postgres:11-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - db-data:/var/lib/postgresql/data
  app:
    build: .
    ports:
      - "8080:8080"
    depends_on:
      - db
    environment:
      PGUSER: "user"
      PGPASSWORD: "password"
      PGHOST: "db"
      PGDATABASE: "mydb"
    command: ["my-app"]
volumes:
  db-data:

This docker-compose.yml file defines two services: a PostgreSQL database and our web application. The database service uses the postgres:11-alpine image and defines environment variables for the POSTGRES_USER, POSTGRES_PASSWORD and POSTGRES_DB. It also mounts a volume to persist the database data.

Our application service builds the image using the Dockerfile defined earlier and exposes port 8080. It depends on the database service and defines environment variables for database connection information. Finally, it sets the command to start our web application.

We can now deploy our application to production by running the following command.

$ docker-compose up -d

Our web application is now running in a containerized environment and is ready to handle production traffic.

Conclusion

In this article, we have walked through the process of building a web application with Haskell, from development environment setup to deployment. We have shown how to create a simple web server using the wai and warp packages, and how to add database support using postgresql-simple. Finally, we have demonstrated how to deploy our application to production using Docker and Docker Compose.

Haskell's expressive and type-safe nature makes it an ideal language for building web applications that are both efficient and maintainable. By following the steps outlined in this article, you too can start building high-performance web applications with Haskell.

Editor Recommended Sites

AI and Tech News
Best Online AI Courses
Classic Writing Analysis
Tears of the Kingdom Roleplay
Learn Cloud SQL: Learn to use cloud SQL tools by AWS and GCP
Best Datawarehouse: Data warehouse best practice across the biggest players, redshift, bigquery, presto, clickhouse
Developer Wish I had known: What I wished I known before I started working on programming / ml tool or framework
Machine learning Classifiers: Machine learning Classifiers - Identify Objects, people, gender, age, animals, plant types
Flutter consulting - DFW flutter development & Southlake / Westlake Flutter Engineering: Flutter development agency for dallas Fort worth