Building web apps in 2024 - learning resources, libraries etc

Hi all! I was wondering if I wanted to start a web app (backend) from scratch using Haskell, what are the “best” approaches/libraries/frameworks/tools these days? I did some online search and can see there are a few options out there, but to be honest I’m not sure which one to chose and what are the pros and cons (although I guess most depends on the kind of app you want to build!).

As a relative newcomer to Haskell (I learned the basics a few years ago but never had the chance to work on a big-size project apart from a few months spent on a compiler/static analysis tool) and a complete newbie in web development (I know the basics but never started a project myself) I’d be particularly interested in choosing a library/framework that is well documented and perhaps has some nice learning resource (tutorial, books etc.).

Any thoughts or experiences to share?
Thank you :slight_smile:

4 Likes

As you know, the answer is “it depends”.

I should open by saying that I’m not a real web developer, I only develop web apps when needed.

Currently, for what I do (e.g. data labeling, interactive visualizations), I enjoy adding frontend interactivity with HTMX. The cost/opportunity of this approach is that the application state is whatever is in the server memory + the browser DOM, and you end up needing a bunch of html templates.

A somewhat heavier alternative is miso (it requires the Nix build tool), but it offers a lot of stuff for the frontend.

I hope others will suggest their favorite framework combination!

You might have already noticed that grepping packages by category | Hackage for “web framework” yields many packages that are essentially DSLs for writing REST API servers, but don’t have “batteries included” support for the frontend part.

2 Likes

I very much enjoy creating web apps using Servant to serve HTML generated by HTMX and Lucid. JavaScript is used very sparingly.

These libraries work super well together, and results in a very lightweight application. But most importantly, it’s a lot of fun.

Here’s a look of how parts of a larger hobby project of mine looks like, that highlights each library:

Subset of API:

data ShoppingApi as = ShoppingApi
  { shoppingPageEP :: as :- Get '[HTML] ShoppingPage,
    productListEP :: as :- "produkter" :> ReqBody '[FormUrlEncoded] Search :> Post '[HTML] ProductSearchList,
    addProductEP :: as :- "lagg-till" :> ReqBody '[JSON] Willys.Product :> Post '[HTML] [ShoppingItem],
    toggleProductEP :: as :- "toggla" :> ReqBody '[JSON] Willys.Product :> Post '[HTML] NoContent,
    removeCheckedEP :: as :- "ta-bort" :> Delete '[HTML] [ShoppingItem],
    removeAllEP :: as :- "ta-bort-alla" :> Delete '[HTML] [ShoppingItem],
    sseEP :: as :- "sse" :> StreamGet NoFraming EventStream EventSource
  }
  deriving (Generic)

Since I’m trained in React, a “component” (HTML content only needs an instance if it’s going to be the body of a response, otherwise it’s just a function):

instance ToHtml ProductSearchList where
  toHtmlRaw = toHtml
  toHtml (ProductSearchList attributes products rubric listId) =
    fieldset_ [class_ "products", id_ listId] $ do
      legend_ (toHtml rubric)
      mapM_
        ( \p -> div_ ([class_ "product-container", title_ p.name] <> (attributes p)) $ do
            img_ [class_ "product", src_ p.image.url]
            div_ [class_ "product-details"] $ do
              span_ [class_ "product-name"] $ toHtml p.name
              span_ [class_ "product-promo"] $ toHtml $ Willys.getPrice p
              span_ [class_ "product-save"] $ toHtml $ fromMaybe "" $ Willys.getSavePrice p
        )
        products

and here’s a typical endpoint:

removeExpenseH :: UUID -> Handler NoContent
removeExpenseH uuid = do
  res <- liftIO $ BS.readFile transactionsFile
  case eitherDecodeStrict res of
    Right ts -> do
      let newTs = filter (\t -> case t of ExpenseTransaction exp -> exp.id /= uuid; _ -> True) ts
      liftIO $ LBS.writeFile transactionsFile (encode newTs)
      hxRedirect "/split"
    Left err -> liftIO (print err) >> throwError err500

10 Likes

Yesod is the most general-purpose back end web framework, and just works. But I think many of us find it has a bit of a learning curve; it uses more types and template haskell than most first-time haskell web app builders will be comfortable with. (That’s true of servant as well.) But it does come with a nice introductory book.

4 Likes

In servant, I see you’re using the “data type” way to describe the API rather than the older “type alias” way – have there been any benefits to that?

Yes, there are benefits to using NamedRoutes! Here is a quote from an introductory blog post from Tweag about this way of constructing APIs with nested records:

Since matching between handlers and servant types is now done by name instead of position, order of handlers becomes irrelevant, which eliminates an entire source of frustration for servant users.

But it doesn’t stop here: GHC can now generate helpful, more focused error messages than it could before. For example, if we were to add an endpoint to the public routes of the anonymous API, like this: […]

So this significantly improved DX.

3 Likes

I find NamedRoutes invaluable in Servant. Trying to structure big APIs the old positional way would quickly become unmanageable. I have a short video about the basics of named routes here.

As for more general advice, I would recommend taking a look, however cursory, at the wai (web application interface), wai-extra (middlewares) and warp (web server) libraries. They are the base of almost all higher-level web frameworks in Haskell, so it’s a good idea to have some knowledge of basic types like Application.

Besides Yesod, another high-level web framework is IHP although I haven’t used it.

5 Likes

Thanks, I like the short and to-the-point format of the video! I’ve used servant and the “type alias way” quite elaborately, so the video makes it really clear on how to switch to NamedRoutes .

Servant itself doesn’t appear to document this feature much, the only example of it on their docs site I’ve found is here Using generics — Servant documentation.

Has anyone tried mig?
Looks really polished for a new project.

3 Likes

I’ve enjoyed using IHP. It has a strong focus on being newcomer-friendly; it’s less general and more opinionated than yesod/servant, but that can be a good thing if you don’t have a lot of experience yet, you get to skip spending time deciding on libraries/architectures etc. and go straight into implementing features.

3 Likes