Writing a (Nix-Friendly) Hello World Web App in Haskell with Scotty
Tags: haskell, functional, nixos, scotty
Published: 2023-07-17 14:53:18

---
title: Writing a (Nix-Friendly) Hello World Web App in Haskell with Scotty
published: true
date: 2023-07-10 00:00:00 UTC
tags: ["haskell", "functional", "nixos", "scotty"]
canonical_url: https://webbureaucrat.gitlab.io/articles/writing-a-hello-world-web-app-in-haskell-with-scotty/
---

Okay, so this article has been written before, and I don't have a lot new to add, but some of these tutorials are getting pretty old, and the ones that aren't use `stack`, which I personally am avoiding as it doesn't play well with NixOS, so at least for me, for future reference, this tutorial will be useful.

### Prerequisites for Haskell and Scotty

The first thing we need is a working development environment. I recommend Nix for this because I've fallen in love with the reproducibility of Nix scripts. Using Nix, we can download and run packages on the fly without messing with the rest of our system. Whether we're using nix or not, we need`cabal-install`, `ghc`, `git`, and `zlib`.

(A couple of those are not strictly intuitive, which is one reason why I'm writing this tutorial. `git` is required for `cabal-install` to work properly, and `zlib` is a dependency of Scotty.)

In Nix, we can simply start a new environment with

```sh
nix-shell --packages bash cabal-install ghc git zlib
```

### Initializing a Hello World App in Haskell with `cabal`

We can initialize a new web app in Haskell with `cabal init`. I won't go through all the steps because the defaults are generally very sensible.

```sh
mkdir webcd webcabal init
```

For completeness, here's my sample _.cabal_ file:

```toml
cabal-version:      3.0
-- The cabal-version field refers to the version of the .cabal specification,
-- and can be different from the cabal-install (the tool) version and the
-- Cabal (the library) version you are using. As such, the Cabal (the library)
-- version used must be equal or greater than the version stated in this field.
-- Starting from the specification version 2.2, the cabal-version field must be
-- the first thing in the cabal file.

-- Initial package description 'web' generated by
-- 'cabal init'. For further documentation, see:
--   http://haskell.org/cabal/users-guide/
--
-- The name of the package.
name:               web

-- The package version.
-- See the Haskell package versioning policy (PVP) for standards
-- guiding when and how versions should be incremented.
-- https://pvp.haskell.org
-- PVP summary:     +-+------- breaking API changes
--                  | | +----- non-breaking API additions
--                  | | | +--- code changes with no API change
version:            0.1.0.0

-- A short (one-line) description of the package.
-- synopsis:

-- A longer description of the package.
-- description:

-- The license under which the package is released.
license:            GPL-3.0-or-later

-- The file containing the license text.
license-file:       LICENSE

-- The package author(s).
author:             eleanorofs

-- An email address to which users can send suggestions, bug reports, and patches.
maintainer:         lu80mmgmt5nm@blurmail.net

-- A copyright notice.
-- copyright:
category:           Web
build-type:         Simple

-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.
extra-doc-files:    CHANGELOG.md

-- Extra source files to be distributed with the package, such as examples, or a tutorial module.
-- extra-source-files:

common warnings
    ghc-options: -Wall

executable web
    -- Import common warning flags.
    import:           warnings

    -- .hs or .lhs file containing the Main module.
    main-is:          Main.hs

    -- Modules included in this executable, other than Main.
    -- other-modules:

    -- LANGUAGE extensions used by modules in this package.
    -- other-extensions:

    -- Other library packages from which modules are imported.
    build-depends:    base ^>=4.15.1.0

    -- Directories containing source files.
    hs-source-dirs:   app

    -- Base language which the package is written in.
    default-language: Haskell2010
```

To ensure your project is created properly, run:

```sh
cabal update && cabal run
```

You should see "Hello Haskell" printed in addition to some other output.

### Adding Scotty to your Haskell project

To make Scotty available for import, add it as a build dependency in your_.cabal_ file.

```
-- Other library packages from which modules are imported.
build-depends: base ^>=4.15.1.0, scotty
```

You can test that your _.cabal_ file is correct by going ahead and importing`Web.Scotty` into your _app/Main.hs_ file and running again with the command above.

_app/Main.hs_

```haskell
module Main where
import qualified Web.Scotty
main :: IO ()
main = putStrLn "Hello, Haskell!"
```

If your app compiles and runs successfully, you know you're ready to start writing code with Scotty.

### Writing a "Hello, world" action in Scotty

In order to use Scotty (with reasonable developer ergonomics), you'll want the `OverloadedStrings` language extension. This allows string literals to be used as other string-like objects when appropriate.

Once you have `OverloadedStrings`, you can write a Scotty `ActionM` action for greeting the world.

```haskell
{-# LANGUAGE OverloadedStrings #-}
module Main where
import qualified Web.Scotty

homeAction :: Web.Scotty.ActionM ()
homeAction = Web.Scotty.html "<h1>Hello, world</h1>"
```

`ActionM`s are monads, which means technically we can write our action as a`do` block like this:

_app/Main.hs_

```haskell
{-# LANGUAGE OverloadedStrings #-}
module Main where
import qualified Web.Scotty

homeAction :: Web.Scotty.ActionM ()
homeAction = do Web.Scotty.html "<h1>Hello, world</h1>"
```

...although this may look ugly for trivial examples like the one shown here.

### Specifying a port and running a server in Scotty

Finally, we're ready to create our server. This example uses port 3000.

```haskell
{-# LANGUAGE OverloadedStrings #-}

module Main where
import qualified Web.Scotty
homeAction :: Web.Scotty.ActionM ()
homeAction = do Web.Scotty.html "<h1>Hello, world</h1>"

main :: IO ()
main = do Web.Scotty.scotty 3000 $ 
    do Web.Scotty.get "/" $ 
        do homeAction
```

When you run this with `cabal run`, you should be able to see your server running at `http://localhost:3000/`.

### In conclusion

Whether you use Nix or not, I hope this has been helpful to people getting started with Scotty.