{"id":19708194,"url":"https://github.com/toptobes/realworld-haskell-scotty-example","last_synced_at":"2026-03-09T15:02:55.054Z","repository":{"id":205340542,"uuid":"714001863","full_name":"toptobes/realworld-haskell-scotty-example","owner":"toptobes","description":"Haskell + Scotty example RealWorld API implementation for https://realworld.io","archived":false,"fork":false,"pushed_at":"2025-01-01T11:24:13.000Z","size":464,"stargazers_count":27,"open_issues_count":2,"forks_count":6,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-05T18:50:10.802Z","etag":null,"topics":["api","example","haskell","postgres","realworld","scotty","tutorial"],"latest_commit_sha":null,"homepage":"https://toptobes.github.io/realworld-haskell-scotty-example/","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"gothinkster/realworld-starter-kit","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/toptobes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-03T17:39:34.000Z","updated_at":"2025-01-01T11:24:16.000Z","dependencies_parsed_at":null,"dependency_job_id":"5837ab1a-a467-4e4a-91a4-166e90d14e8c","html_url":"https://github.com/toptobes/realworld-haskell-scotty-example","commit_stats":null,"previous_names":["toptobes/realworld-starter-kit","toptobes/realworld-haskell-scotty-example"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toptobes%2Frealworld-haskell-scotty-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toptobes%2Frealworld-haskell-scotty-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toptobes%2Frealworld-haskell-scotty-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toptobes%2Frealworld-haskell-scotty-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/toptobes","download_url":"https://codeload.github.com/toptobes/realworld-haskell-scotty-example/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251549117,"owners_count":21607355,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","example","haskell","postgres","realworld","scotty","tutorial"],"created_at":"2024-11-11T21:42:40.601Z","updated_at":"2026-03-09T15:02:54.899Z","avatar_url":"https://github.com/toptobes.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ![RealWorld Example App](logo.png)\n\n\u003e ### Haskell codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the [RealWorld](https://github.com/gothinkster/realworld) spec and API.\n\n### [Demo](https://demo.realworld.io/)\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;[RealWorld](https://github.com/gothinkster/realworld)\n\nThis codebase was created to demonstrate a fully fledged fullstack application built with **Haskell/Scotty** including CRUD operations, authentication, routing, pagination, and more.\n\nWe've gone to great lengths to adhere to the **Haskell/Scotty** community styleguides \u0026 best practices.\n\nFor more information on how to this works with other frontends/backends, head over to the [RealWorld](https://github.com/gothinkster/realworld) repo.\n\n# How it works\n\n## Basic architectural overview\nSince this is small application (only 2k+ϵ actual LOC), I've opted for a very vertical-slice-esque architecture, with each endpoint\ngetting its own file, and with common logic simply going in its own files. Controllers, services, and data access are of course still\ndecoupled through MTL style classes (inspired by [three-layer-cake](https://www.parsonsmatt.org/2018/03/22/three_layer_haskell_cake.html)),\nmaking testing extremely simple. I believe the general architecture should be quite self explanitory and easy to refactor as the app scales.\nI will admit it *is* slightly influenced by Haskell's dissallowance of circular imports, and I couldn't get hls to work with an hs-boot file.\n\nFeel free to let me know your thoughts, or open an issue!\n\nView more fine-grained documentation [here](https://toptobes.github.io/realworld-scotty-hs/).\n\n## Noteworthy dependencies\n(Not including common dependencies such as `mtl` and `aeson`)\n - [`scotty`](https://github.com/scotty-web/scotty) — The minimal web \"framework\" which makes this all possible\n - [`relude`](https://github.com/kowainik/relude) — A nicer/safer Prelude alternative\n - [`esqueleto`](https://github.com/bitemyapp/esqueleto) — A type-safe SQL eDSL wrapping [`persistent`](https://www.yesodweb.com/book/persistent)\n - [`jwt`](https://hackage.haskell.org/package/jwt) — Library for working w/ JWTs\n - [`cryptonite`](https://hackage.haskell.org/package/cryptonite) — Low-level cryptography library\n - [`wai-middleware-static`](https://hackage.haskell.org/package/wai-middleware-static) — Used to easily serve static files\n - [`file-embed`](https://hackage.haskell.org/package/file-embed) — Used to embed the sqlbits directly into haskell code\n\n## Basic file-structure overview\n```ruby\napp/                      # The entrypoint into the application.\n  Main.hs                 # Very thin, just deals with configuration.\n\nsqlbits/                  # Some sql files w/ triggers/functions\n                          # which are embedded directly within\n                          # the haskell files via TemplateHaskell.\n\nsrc/                      # The actual source code for Conduit.\n\n  Conduit/App/            # This folder deals with the App Monad,\n                          # which holds the server's global state.\n\n  Conduit/DB/             # Holds some DB-related utilities and\n                          # code for decoupling/abstraction purposes.\n                          # Also holds some DB initializtion code.\n\n  Conduit/Features/       # Contains the bulk of the Conduit logic,\n                          # including the API endpoints/services/DB\n                          # access logic.\n                          # Also holds feature-related DB/error logic.\n\n  Conduit/Identity/       # Holds the code for the JWT-based auth.\n\n  Errors.hs               # Some code for handling and translating\n                          # feature-specific errors\n\n  Validation.hs           # Some utilities for basic validation\n                          # of incoming data.\n\nstatic/                   # Holds the static files where the avatar\n                          # images reside. The images are actually just\n                          # blank files for now but it's fiiine.\n\ntest/                     # Contains the unit tests made w/ hspec,\n                          # currently only have tests for auth and\n                          # slug-building since cypress covers the rest.\n\npackage.yaml              # Describes the project and its dependencies.\nrealworld-hs.cabal        # hpack generates the .cabal file from the\n                          # package.yaml which, IMO, is much nicer\n                          # to work with.\n\nconduit-schema.json       # The configuration files for conduit itself. \nconduit.json              # The schema file for your convenience.\n```\n\n## Misc\n**On ExceptT vs Exceptions:** yeah uh, besides the lack of structural typing, from my non-expert opinion, one of my biggest issues with Haskell\nis the lack of truly idiomatic error handling; ask 10 different people, get 11 different answers. I ended up going with ExceptT because it seemed like\nthe simplest method while maintaining sufficient cleanliness and typechecking.\n\n**On not just writing into the AppM monad:** I easily could've used `(AppM m)` as a constraint directly, everywhere. And some people would argue that I\nshould've, on an app on this scale—which is totally fair: it's much simpler and lightweight, compared to the (somewhat?) three-layer-style I opted for.\nHowever, I do find semantic meaning in seeing some context such as `(PasswordGen m, AuthTokenGen m, CreateUser m, ReadUsers m)` where it's immediately\nclear what the function needs access to. It's also arguably better for testing, but I didn't really test the services here (due to the tests provided\nby gothinkster), so I won't go into that here. It's arguable that such contexts are merely implementation details, but I'd say that it's more than\na detail; it's a purpose, the essence of that function. But that's just me, do whatever you want lol.\n\n# Getting started\n\nThis project requires Cabal \u0026 a running Postgres instance. Here're a couple links of that may help you out:\n - [Cabal via GHCup](https://cabal.readthedocs.io/en/3.4/getting-started.html) — Guide for installing + getting started w/ Cabal\n - [Postgres + Docker](https://www.youtube.com/watch?v=G3gnMSyX-XM) — A quick tutorial for getting a Postgres container running\n\nTo begin, of course, clone the repo and cd into it:\n```julia\ngit clone https://github.com/toptobes/realworld-scotty-hs.git \u0026\u0026 cd realworld-scotty-hs\n# or\ngh repo clone toptobes/realworld-scotty-hs \u0026\u0026 cd realworld-scotty-hs\n```\n\nFrom there, update the appropriate configs in `conduit.json`\n - It's already set with sensible defaults; be sure to double-check the postgres connection string\n - there is a `conduit-schema.json` for your convenience as well that provides extra info\n - If necessary, you can set the `CONDUIT_CONFIG` env var to to some custom config path\n\nThen, you can start the app via `cabal run app`, or run the unit tests w/ `cabal run spec`.\n\nIt may take a while the first time while it obtains/builds the relevant dependencies.\n\nIf you plan to make any modifications involving creating new files or adding new dependencies, you may need [hpack](https://github.com/sol/hpack).\n - You could update the `realworld-hs.cabal` file manually if you really wanted or needed to though\n - Otherwise, just run the `hpack` command whenever you modify `package.yaml`\n\n## Further documentation\n\n**Access the documentation site @ [https://toptobes.github.io/realworld-haskell-scotty-example].**\nNearly everything except for the features are decently documented, and I'll work on adding more soon.\n\nYou can also build the documentation site locally using `cabal haddock`, and then serve it w/ `npx serve` or whatever\nelse you fancy.\n\nOr just read through the code manually if you prefer, whatever you want lol.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoptobes%2Frealworld-haskell-scotty-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftoptobes%2Frealworld-haskell-scotty-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoptobes%2Frealworld-haskell-scotty-example/lists"}