Notes on trying haskell's selda

posted on 24.05.2026

Over a couple evenings I’ve whipped up a full storage implementation for my reasonably complicated web-server using selda. The server is still deep in the pre-release and missing a lot of features and stability, so I have the opportunity to experiment a bit. I was choosing between selda and beam (and maybe sqlite-simple), and decided to try the underdog in this race. Here are my overall thoughts after the storage implementation is complete:

  1. The tutorial is nicely written, but very much incomplete. I like how the main example for each chapter is placed in the right column so you can easily reference it. But it’s very incomplete! Not even a mention of foreign keys, no left joins.

  2. The DSL for writing the sql tables and expressions is actually very nice; even writing a left join is pleasant. I like how it doesn’t abuse type parameters, neither it relies on template haskell. So yes, it’s Generics with its accompanying compile-time slowness. But the code that I write is compact and clean, easy to read and to produce, and the error messages are really good too.

  3. Inserting a record with auto-incrementing value requires setting it to Database.Selda.def. This def is defined as throw DefaultValueException - yes, throwing in pure code. This is even more fun because you usually would define a record with strict fields, so this will blow up when creating a record and you won’t know from where. That you need to define lazy records is not mentioned in the docs.

  4. This library loves pure exceptions, another instance is class SqlType’s method fromSql :: SqlValue -> a. So what if your type has a smaller range compared to the sql type, what if you’re encoding a NonEmpty Text? For some types in this library, they, as you have guessed, throw an exception. I would think that this exception is than handled in conversion like def is, but nope, when evaluating the thunk for the record field, you get the explosion.

  5. A small papercut: you can define a migration for changing a table to a table, but you can’t define a migration for creating a table, leading to this annoying edge case on database creation.

  6. You also can’t define a table in any other way than with defining a haskell record and deriving an SqlRow instance for it. So if you want to support migrating from older versions automatically, get ready for a lot of compilation of Generics.

  7. Oh maybe you want a complex row, or a record withing a record? Also not possible; you literally can not write the custom instance, because its backing definitions are not exported.

  8. The impure code can only throw SeldaError which is nice and easy to handle. But this error is no better than a String: you can’t distinguish transient connection failures from fatal failures from SQL constraint violations. Constraint violations are the most important! I just want to see if the post already exists or not when inserting it!

  9. The transactions interact weirdly with tryInsert.

  10. The docs assume you’re going to enter the SeldaMonad at toplevel, but you can also runSeldaT on any usage. From looking at the source, it seems the first one might give you problems with concurrent sqlite access. I’m not sure it will, so let’s leave this point as a plus for flexibility.

  11. The problems I mention above are all present on the issue tracker, and the library maintainers don’t do anything with them, not even a response. They merge patches, except the last several years of merged patches were just compat fixes.

I won’t be continuing to use selda, but I am tempted to fork it and fix the problems. No, have to hold the yaks from being shaved. Maybe in the future. Ciao.