DEV Community

webbureaucrat
webbureaucrat

Posted on • Originally published at webbureaucrat.gitlab.io on

Writing a (Nix-Friendly) 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 needcabal-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

nix-shell --packages bash cabal-install ghc git zlib
Enter fullscreen mode Exit fullscreen mode

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.

mkdir webcd webcabal init
Enter fullscreen mode Exit fullscreen mode

For completeness, here's my sample .cabal file:

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
Enter fullscreen mode Exit fullscreen mode

To ensure your project is created properly, run:

cabal update && cabal run
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You can test that your .cabal file is correct by going ahead and importingWeb.Scotty into your app/Main.hs file and running again with the command above.

app/Main.hs

module Main where
import qualified Web.Scotty
main :: IO ()
main = putStrLn "Hello, Haskell!"
Enter fullscreen mode Exit fullscreen mode

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.

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

homeAction :: Web.Scotty.ActionM ()
homeAction = Web.Scotty.html "<h1>Hello, world</h1>"
Enter fullscreen mode Exit fullscreen mode

ActionMs are monads, which means technically we can write our action as ado block like this:

app/Main.hs

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

homeAction :: Web.Scotty.ActionM ()
homeAction = do Web.Scotty.html "<h1>Hello, world</h1>"
Enter fullscreen mode Exit fullscreen mode

...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.

{-# 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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)