There isn't an abundance online of writing about successful, reliable, and reproducible CRUD stacks for Haskell developers. We wanted to add to the amount of explanation available, using by example the practical context of our work with PowerZonePack, or PZP.
PZP is a group of Peloton users who like to exercise together. The core purpose of the PZP website is to sync users' exercise experiences, either in real time or after the fact. It offers challenges, each of which is a dedicated period of time where subscribed users can join teams and participate in pre-determined rides, and it offers admin the ability to create these. Finally, all users are given access to reporting to monitor their progress and communicate results with their peers.
The website was originally built using our stack by non-Obsidian developers. We were hired to maintain the site after their work together ceased. Since then, we've designed a cleaner UX for PZP, and implemented more advanced functionality, especially to do with the aforementioned reporting and challenges. We continue, every release, to widen the scope of challenges and deepen the technology that supports them.
We'll start with Obelisk, which is a full-stack framework for building web applications using Haskell. It was developed by Obsidian Systems. As written in Obelisk's repository:
Obelisk is targeted primarily at Haskell developers who want to build high-quality web and/or mobile applications in Haskell, without the distractions of manually choosing and integrating technology for every piece of the system.
By way of analogy, Obelisk fills a similar role for Haskell as Django does for Python.
Obelisk is designed to aid the implementation of reflex-frp, an engine for using the functional programming paradigm to build applications across a range of devices.
At the heart of every Obelisk web app are the "reflex" and "reflex-dom" libraries that leverage FRP to enable DOM manipulation of HTML elements using Haskell.
For database management, we primarily use Postgres, a database architecture that has been in standard use for decades, in conjunction with Beam. We prefer Beam for its balance of type safety (a Haskell principle) and usability when communicating with Postgres. There's a lot of extremes and options when it comes to database libraries; Beam, simply, suits us.
Tailwind is a CSS framework, and a recent addition to our stack. Using Tailwind lets one style elements directly within reflex-dom source code using classes, rather than having a separate CSS file from which style data has to travel back and forth. It's a trade-off, because Tailwind does come with limitations, but it works well for our purposes. Moreover, this usage gave rise to the ability to write nix derivations that handle static assets, which has been a beneficial addition to Obelisk.
Within the PZP stack, we've also integrated Stripe, for subscription payments, and Google Maps, to create the challenge routes that PZP members particularly enjoy. We use another subset of the Reflex platform to manage these APIs: reflex-gadt-api, an add-on to reflex-dom that gives us more type safety when using APIs for moving data between the frontend and backend.
Our stack neatly frames these technologies, easing the adoption of FRP for web app development. With Obelisk & our quickly iterative workflow, it's possible for us to build a ready-to-use web app in as few as ten weeks.
PowerZonePack, a dedicated community now counting over 150k members, are able to enjoy their shared interest without worrying that their platform will break or stagnate. Not only that, but new features are regularly added, improving & expanding the experience that makes users love PZP in the first place. The stable present structure and future feature roadmap are both continually developed.
Web apps offer real value to a wide range of users, and work like this helps us refine the tooling we've worked with for years. We'd love to do more.