Webgear off-label experimentation

Some quick 'off-label' experimentation with WebGear (a high-performance framework to build composable, type-safe HTTP APIs. It is designed to make common API development tasks easy. It is also easily extensible to add components needed by your project. – haskell-webgear/webgear). Also see earlier Webgear post.

Introduction

Experimenting with Lucid templating on the server and automatic recompilation for changes to the Haskell source code.

The Hello World example from Webgear is used as a starting point and renamed to webgear-example-hello-lucid.

flake.nix

The program name has been changed and the Haskell package hsPkgs.ghcid is added to buildInputs.

 1  {
 2  description = "WebGear example project + Lucid - Hello World";
 3
 4  inputs = {
 5    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
 6    flake-utils.url = "github:numtide/flake-utils";
 7    gitignore = {
 8      url = "github:hercules-ci/gitignore.nix";
 9      # Use the same nixpkgs
10      inputs.nixpkgs.follows = "nixpkgs";
11    };
12  };
13
14  outputs = { self, nixpkgs, flake-utils, gitignore }:
15    flake-utils.lib.eachSystem [ "x86_64-linux" "x86_64-darwin" ] (system:
16      let
17        pkgs = import nixpkgs {
18          inherit system;
19          overlays = [ haskellOverlay ];
20        };
21        ghcVersion = "ghc924";
22        hsPkgs = pkgs.haskell.packages.${ghcVersion};
23
24        pkgName = "webgear-example-hello-lucid";
25
26        haskellOverlay = final: prev: {
27          haskell = prev.haskell // {
28            packages = prev.haskell.packages // {
29              ${ghcVersion} = prev.haskell.packages.${ghcVersion}.override {
30                overrides = hfinal: hprev: {
31                  webgear-core = hfinal.callPackage
32                    ./nix/haskell-packages/webgear-core-1.0.4.nix { };
33                  webgear-server = hfinal.callPackage
34                    ./nix/haskell-packages/webgear-server-1.0.4.nix { };
35                  ${pkgName} = hfinal.callCabal2nix pkgName
36                    (gitignore.lib.gitignoreSource ./.) { };
37                };
38              };
39            };
40          };
41        };
42      in {
43        packages.default = hsPkgs.${pkgName};
44        devShells.default = hsPkgs.shellFor {
45          name = pkgName;
46          packages = pkgs: [ pkgs.${pkgName} ];
47          buildInputs = [
48            pkgs.cabal-install
49            pkgs.cabal2nix
50            hsPkgs.fourmolu
51            hsPkgs.ghc
52            hsPkgs.ghcid
53            pkgs.hlint
54            pkgs.haskell-language-server
55          ];
56          src = null;
57        };
58      });
59}

webgear-example-hello-lucid.cabal

The program name is changed and the ghc-option to error out on unused packages -Wunused-packages is commented out because the automatic recompilation doesn't like it. Furthermore lucid is added to build-depends.

 1cabal-version:      2.4
 2name:               webgear-example-hello-lucid
 3version:            1.0.4
 4description:
 5  Please see the README at <https://github.com/haskell-webgear/webgear-example-hello#readme>
 6
 7homepage:
 8  https://github.com/haskell-webgear/webgear-example-hello#readme
 9
10bug-reports:
11  https://github.com/haskell-webgear/webgear-example-hello/issues
12
13author:             Raghu Kaippully [changes and additions by Mari Donkers]
14maintainer:         rkaippully@gmail.com
15copyright:          2021-2022 Raghu Kaippully
16license:            MPL-2.0
17license-file:       LICENSE
18build-type:         Simple
19extra-source-files: README.md
20
21source-repository head
22  type:     git
23  location: https://github.com/haskell-webgear/webgear-example-hello
24
25executable hello
26  default-language: Haskell2010
27  build-depends:
28    , base            >=4.12.0.0 && <5
29    , http-types      ^>=0.12
30    , lucid
31    , warp            ^>=3.3
32    , webgear-server  ==1.0.4
33
34  ghc-options:
35    -threaded -rtsopts -with-rtsopts=-N -Wall
36    -Wno-unticked-promoted-constructors -Wcompat -Widentities
37    -Wincomplete-record-updates -Wincomplete-uni-patterns
38    -Wmissing-fields -Wmissing-home-modules
39    -Wmissing-deriving-strategies -Wpartial-fields
40    -Wredundant-constraints -fshow-warning-groups -Werror
41
42  -- -Wunused-packages
43  main-is:          Main.hs
44  hs-source-dirs:   src

src/Main.hs

The file is changed because of the 'off-label' experimentation (some arbitrary code changes and additions to demonstrate Lucid).

  1{-# LANGUAGE Arrows               #-}
  2{-# LANGUAGE DataKinds            #-}
  3{-# LANGUAGE ExtendedDefaultRules #-}
  4{-# LANGUAGE FlexibleContexts     #-}
  5{-# LANGUAGE OverloadedStrings    #-}
  6{-# LANGUAGE QuasiQuotes          #-}
  7{-# LANGUAGE TypeApplications     #-}
  8
  9import           Control.Category         ((.))
 10import           Lucid
 11import           Network.HTTP.Types       (StdMethod (GET))
 12import qualified Network.HTTP.Types       as HTTP
 13import           Network.Wai.Handler.Warp (run)
 14import           Prelude                  hiding ((.))
 15import           WebGear.Server
 16
 17title :: Html ()
 18title = "Webgear hello example with Lucid templating server generated HTML"
 19
 20stylesheet :: Text
 21stylesheet = "https://cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.2.2/gh-fork-ribbon.min.css"
 22
 23description :: Text
 24description = "WebGear is a high-performance framework to build composable, type-safe HTTP APIs. It is designed to make common API development tasks easy. It is also easily extensible to add components needed by your project."
 25
 26viewport :: Text
 27viewport = "width=device-width, initial-scale=1"
 28
 29listItemCount :: Int
 30listItemCount = 7
 31
 32pageHead :: Html ()
 33pageHead = do
 34  title_ title
 35  link_ [ rel_ "stylesheet"
 36          , href_ stylesheet
 37          ]
 38  meta_ [ charset_ "utf-8" ]
 39  meta_ [ name_ "theme-color", content_ "#00d1b2" ]
 40  meta_ [ httpEquiv_ "X-UA-Compatible"
 41          , content_ "IE=edge"
 42          ]
 43  meta_ [ name_ "viewport"
 44          , content_ viewport
 45          ]
 46  meta_ [ name_ "description"
 47          , content_ description
 48          ]
 49  style_ ".github-fork-ribbon:before { background-color: \"#e59751\" !important; }"
 50
 51pageBody :: Html ()
 52pageBody = do
 53  p_ "This is the first (1st) section in an HTML body."
 54  p_ [style_ "color:red"] "The second (2nd) section."
 55  p_ [class_ "third"] "And the third (3rd) section."
 56  div_ [class_ "table"] "A table:" <> table
 57  p_ "THIS IS GENERATED BY HASKELL CODE:"
 58  hr_ []
 59  div_ [id_ "genlist"] $ do
 60    p_ "This is an example of Lucid syntax (a list generated by code)."
 61    ul_ $ gl
 62    where
 63      table = table_ [rows_ "2", style_ "border: 1px solid black; padding:10px;"] $ do
 64        tr_ $ do
 65          td_ [class_ "top",
 66               style_ "color:blue; border: 1px dashed black;"]
 67            $ p_ "1a"
 68          td_ [class_ "top",
 69               style_ "color:blue; border: 1px dashed black;"] "1a"
 70        tr_ $ do
 71          td_ [class_ "top",
 72               style_ "color:blue; border: 1px dashed black;"]
 73            $ p_ "1b"
 74          td_ [class_ "top",
 75               style_ "color:blue; border: 1px dashed black;"] "2b"
 76      gl = mapM_ (\i -> li_ $
 77                   toHtml $ "Item index [" ++ show i ++ "]") [1..listItemCount]
 78
 79page :: Html ()
 80page = doctypehtml_ $ do
 81  head_ pageHead
 82  body_ pageBody
 83
 84lucid :: ServerHandler IO t Response
 85lucid = proc _ -> do
 86  let h = page
 87  unlinkA <<< respondA HTTP.ok200 "text/html"
 88    -< renderBS h
 89
 90hello :: ServerHandler
 91  IO
 92  (Linked '[PathEnd, PathVar "name" String, Path, Method] Request)
 93  Response
 94hello = proc request -> do
 95  let name = pick @(PathVar "name" String) $ from request
 96  unlinkA <<< respondA HTTP.ok200 "text/plain" -< "Hello, " <> name
 97
 98routes :: RequestHandler (ServerHandler IO) '[]
 99routes = [route|       GET /lucid              |] lucid
100           <+> [route| GET /hello/name:String/ |] hello
101
102main :: IO ()
103main = run 3000 (toApplication routes)

Development environment

Start the development environment as follows.

1nix develop

Automatic recompilation

To start automatic recompilation use the following command:

1ghcid -c 'cabal repl' -T Main.main --restart=./webgear-example-hello-lucid.cabal

Loading the Lucid generated page

Navigate to http://localhost:3000/lucid and reload the page in the browser when you have changed something and the automatic recompilation has finished. Note that you will need to terminate the automatic reload command and exit out of the development environment and restart both, after changing the flake.nix or webgear-example-hello-lucid.cabal file.

Posts in this Series