[ANN] Hyperbole - Interactive HTML applications with type-safe serverside Haskell. Like typed HTMX

When I released web-view 6 months ago, I said I was “weeks” away from releasing a framework for interactive web apps built on top of it. Well it’s been 26 weeks, and it’s finally ready!

Hyperbole makes it easy to create fully interactive HTML applications with type-safe serverside Haskell. It’s inspired by HTMX, Elm, and Phoenix LiveView

Motivation

I’ve been a web developer since before “Ajax”. I rode the wave of Single Page Applications (SPAs) and loved how interactive we could make things. I’ve written fancy apps in React and Elm. But ultimately SPAs mean writing two applications, a Javascript client and a server, plus an API between them. They’re a huge pain to write and maintain. I missed serverside web apps.

Instead of an SPA, Hyperbole allows us instead to write a single Haskell program which runs exclusively on the server. All user interactions are sent to the server for processing, and a sub-section of the page is updated with the resulting HTML.

There are frameworks that support this in different ways, including HTMX, Phoenix LiveView, and others. Hyperbole has the following advantages

  1. 100% Haskell
  2. Type safe views, actions, routes, and forms
  3. Elegant interface with little boilerplate
  4. VirtualDOM updates over sockets, fallback to HTTP
  5. Easy to use

Like HTMX, Hyperbole extends the capability of UI elements, but it uses Haskell’s type-system to prevent common errors and provide default functionality. Specifically, a page has multiple update targets called HyperViews. These are automatically targeted by any UI element that triggers an action inside them. The compiler makes sure that actions and targets match.

Like Phoenix LiveView, it upgrades the page to a WebSocket connection and uses VirtualDOM for live updates

Like Elm, it relies on an update function to handle actions, but greatly simplifies the Elm Architecture by handling state with extensible effects. forms are easy to use with minimal boilerplate

Depends heavily on the following frameworks

Simple Example

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}

import Web.Hyperbole

main = do
  run 3000 $ do
    liveApp (basicDocument "Example") (page mainPage)


mainPage = do
  handle message
  load $ do
    pure $ do
      el bold "My Page"
      hyper (Message 1) $ messageView "Hello"
      hyper (Message 2) $ messageView "World!"


data Message = Message Int
  deriving (Generic, Param)

data MessageAction = Louder Text
  deriving (Generic, Param)

instance HyperView Message where
  type Action Message = MessageAction


message :: Message -> MessageAction -> Eff es (View Message ())
message _ (Louder m) = do
  let new = m <> "!"
  pure $ messageView new


messageView m = do
  el_ $ text m
  button (Louder m) id "Louder"

Learn More

Hackage has a better intro and good docs

Examples demonstrating different features

Feedback

Any questions and comments appreciated! Please let me know if anything isn’t clear from the docs.

(Cross Posted to Reddit)

42 Likes

That looks great, thanks for sharing! What is the story around user’s authentication, could you show how to integrate oauth for example?

2 Likes

That looks great, thanks for sharing! What is the story around user’s authentication, could you show how to integrate oauth for example?

Good question! I don’t have direct library support for it (yet), but you’d implement it like you would in any non-interactive web framework, like Scotty. Handlers can use the Hyperbole effect to read the request (query params) and redirect the browser, so you could start the process in response to a button press. You can store tokens in a session

I just opened an issue for myself to create an OAuth example: OAuth example · Issue #2 · seanhess/hyperbole · GitHub. Anyone know of a simple demo OAuth provider for this sort of thing?

I did implement federated auth in the L2 admin app at the NSO, but that repo is a work in progress and not documented. See App.Globus and App.View.Layout

3 Likes

Hello Sean. I just skim the doc - really great. I love the way you have lots of examples. Thanks! I recently wrote a interactive app in Haskell using HTMX. It needs some updating. I just might switch to Hyperbole.

4 Likes

For anyone that wanna give this hello world example a try quickly, here’s a simple cabal script to make this works.

#!/usr/bin/env cabal
{- cabal:
default-language: GHC2021
build-depends: base,
               text,
               hyperbole,

ghc-options:   -threaded
               -rtsopts
               -with-rtsopts=-N
-}

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}


import Web.Hyperbole
import Data.Text (Text)

main = do
  run 3000 $ do
    liveApp (basicDocument "Example") (page mainPage)


mainPage = do
  handle message
  load $ do
    pure $ do
      el bold "My Page"
      hyper (Message 1) $ messageView "Hello"
      hyper (Message 2) $ messageView "World!"


data Message = Message Int
  deriving (Generic, Param)

data MessageAction = Louder Text
  deriving (Generic, Param)

instance HyperView Message where
  type Action Message = MessageAction


message :: Message -> MessageAction -> Eff es (View Message ())
message _ (Louder m) = do
  let new = m <> "!"
  pure $ messageView new


messageView m = do
  el_ $ text m
  button (Louder m) id "Louder"
8 Likes

You know it just so happens that yesterday I had the idea to write a webapp in Haskell and started looking a bit for frameworks (and failed at installing the javascript backend). I will give this a try!

4 Likes