Writing a (Nix-Friendly) Hello World Web App in Haskell with Scotty
haskell scotty nixosOkay, 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
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.
mkdir web
cd web
cabal init
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
To ensure your project is created properly, run:
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
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.
{-# 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
{-# 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.
{-# 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.
I write to learn, so I welcome your constructive criticism. Report issues on GitLab.