DEV Community

loading...

RealWorld example with Haskell Yesod

Tomáš Zemanovič
Core developer and researcher at Cryptium Labs
Originally published at tzemanovic.gitlab.io on ・3 min read

This is an overview of an example implementation of the RealWorld backend built using the powerful Yesod framework written in Haskell.

This project is essentially a RESTful API to a database and even though Yesod can do much more than that, you still get a lot of nice things that help along the way. For example, Yesod comes with some helper commands for common tasks (e.g. you can add a new handler using yesod add-handler) and you can start a new project using one of the stack templates. I’ve started this project with stack new yesod-sqlite which scaffolded application with SQLite database connection, whose schema is created from persistent entity file. The scaffolded project is setup with:

  • development server setup using ghcid with automatic rebuild and fast reloads
  • hpack for a nicer alternative to the Cabal package format
  • behaviour specification using hspec
  • fast and scalable logging using fast-logger for requests and database queries
  • log functions from monad-logger that includes source code location in the log entries
  • the fast Warp web server
  • classy-prelude as an alternative prelude that removes all partial functions
  • persistent for type-safe database-agnostic data modelling and querying with automatic migrations in development
  • aeson for fast JSON parsing and encoding
  • keter deployment system

If you want to learn more about Yesod, you can read the excellent Developing Web Applications with Haskell and Yesod for free. Some other dependencies used in this project are:

  • esqueleto - type-safe EDSL for SQL queries on persistent backends
  • forma - parse and validate form input
  • jwt - JSON Web Token (JWT) decoding and encoding
  • weeder - to detect dead code

Project structure

app
├── app
├── package.yaml
│   ├── main.hs                     "The main entry point for production build."
│   ├── devel.hs                    "The main for development server."
│   └── DevelMain.hs                "The main for running inside GHCi."
├── config
│   ├── models                      "Persistent entity file with the database schema declaration."
│   ├── routes                      "Routes declaration for our RESTful API."
│   ├── settings.yml                "App runtime settings."
│   └── test-settings.yml           "Settings overrides for tests."
├── src
│   ├── Application.hs              "Loads the settings to initalize the app with a HTTP manager,"
│   │                               "logger and database connection pool."
│   │
│   ├── Foundation.hs               "Declares the foundation 'App' datatype which is accessible"
│   │                               "from every handler, type for routes loaded from `config/routes`"
│   │                               "and authentication requirements for each route."
│   │
│   ├── Settings.hs                 "Declares and loads the app settings, which can be loaded from"
│   │                               "various sources."
│   │
│   ├── Import.hs                   "Commonly used module imports grouped for convenience."
│   ├── Model.hs                    "Generates types for models loaded from `config/models`."
│   ├── Database.hs                 "Database queries."
│   ├── Pagination.hs
│   ├── Handler                     "Handlers for the RESTful API."
│   │   ├── Articles.hs
│   │   ├── Profiles.hs
│   │   └── User.hs
│   ├── Database                    "Helpers for dealing with database."
│   │   └── Persist
│   │       ├── Extended.hs
│   │       └── Types
│   │           ├── Email           "Persistent field type for email, which is"
│   │           │   └── Internal.hs "stored in a case-insensitive string."
│   │           ├── Email.hs
│   │           ├── Password        "Persistent field type for password,"
│   │           │   └── Internal.hs "stored as a hash."
│   │           └── Password.hs
│   ├── Auth                        "Authentication using JWT token."
│   │   └── JWT.hs
│   └── Web                         "User input validation."
│       └── Forma
│           └── Extended.hs
├── test                            "The specs for user and profiles handlers."
│   ├── Handler
│   │   ├── ProfilesSpec.hs
│   │   └── UserSpec.hs
│   ├── Spec.hs
│   └── TestImport.hs
Enter fullscreen mode Exit fullscreen mode

Some notes

The handlers are implemented according to the API spec:

  • The Profiles module is the most simple one with handlers using persistent queries
  • The User handlers deal with user input that is parsed and validated using the forma library
  • The Articles handlers contain more complex queries and pagination for the article feed. Because their queries deal with multiple tables, I reached out to the esqueleto library, which provides a nice eDSL (a SQL language embedded in Haskell) for querying the database.

Discussion (0)