biff

2025-12-17T17:19:40.853729Z

How do you handle errors in biff? There's three parts to this question: 1. How and where (if at all) do try-catches go ? I'd put them in the request handlers and try to avoid them elsewhere. 2. How do I actually handle them? I have the impression that the access to the exception stack is very limited, so the only thing I have managed to do is just catch everything and return the exception data, which brings me to the next question, which is probably the most concrete 3. It seems like malli doesn't tell me a lot when the schema is invalid - which is a problem that I encounter quite often. Is there some way to drill deeper into malli?

2025-12-18T15:00:44.181649Z

The only place malli is used is in calls to biff/submit-tx, i.e. when you're writing to the database. (That function gets access to your schema because the schema is in the ctx map.) The starter app includes a biff/wrap-internal-error middleware (see the middleware.clj in your project, and the https://github.com/jacobobryant/biff/blob/a075fae22993451aab1d3f83af7ac793f5dc67a5/src/com/biffweb/impl/middleware.clj#L75) that logs unhandled errors for you. That should be sufficient for logging errors from application bugs like invalid calls to submit-tx. You should only need to add try-catches if you have a particular way to handle a specific exception in mind. It's hard to say anything more specific since it depends on the exception. I just looked through my own app and I have very few try-catches in it. Most of them were for wrapping java libraries that throw exceptions when I'd rather it just return nil (e.g. "parse this string of text for something, and if you don't find it, return a nil", similar to clojure's functions like parse-uuid). For debugging submit-tx calls: the exception log in the terminal should have the info you need. e.g. if I edit in the starter app so that it tries to save a value to the :user/fooooo key (instead of :user/foo), I get this:

[qtp1065363787-65] ERROR com.biffweb.impl.middleware - Exception while handling request
clojure.lang.ExceptionInfo: Doc wouldn't be a valid :user after transaction. {:tx-doc {:db/op :update, :db/doc-type :user, :xt/id #uuid "aa8ddebd-5390-4ccd-8b83-1070072f8978", :user/fooooo "hey"}, :explain {:user/fooooo ["disallowed key"]}}
        at com.biffweb.impl.xtdb$biff_op__GT_xt.invokeStatic(xtdb.clj:320)
        at com.biffweb.impl.xtdb$biff_op__GT_xt.invoke(xtdb.clj:254)
        [long stack trace]
Notice the {:tx-doc {:db/op :update, :db/doc-type :user, :xt/id #uuid "aa8ddebd-5390-4ccd-8b83-1070072f8978", :user/fooooo "hey"}, :explain {:user/fooooo ["disallowed key"]}} bit, which is the ex-data from the exception. That should have everything you need to understand what went wrong. the :tx-doc value is what you were trying to save to the database, and the :explain value is the error info from malli. In this case it tells us that :user/fooooo is a disallowed key, i.e. it's not part of our :user schema. If I change set-foo to save a number to :user/foo (so the key is valid, but the value isn't), then I get {:tx-doc {:db/op :update, :db/doc-type :user, :xt/id #uuid "aa8ddebd-5390-4ccd-8b83-1070072f8978", :user/foo 123}, :explain {:user/foo ["should be a string"]}} which tells us that :user/foo was 123 but it should have been a string. If you're not getting that ex-data stuff in your logs, there might be some kind of misconfiguration we need to fix.

2025-12-20T21:45:41.424679Z

Wow, that would be convenient. The funny thing is, to track down this one I just used the tool at http://malli.io, I just found this when reading the docs looking for the error, and that is quite an amazing tool already

2025-12-17T18:08:13.587579Z

I guess the implicit question behind this might as well be: How exactly is malli wired into biff? It seems a little magic: I define a schema in a file and suddenly somewhere something says "you can't write this map to the database". It's probably a middleware, sure but I haven't been able to trace from the top level namespace back to where malli is actually used

2025-12-19T18:34:09.438279Z

Glad to hear that you don't have a ton of try catch blocks, very relieving^^

2025-12-19T18:35:13.725389Z

Ok , good to know that I have to look at submit-tx, In fact I haven't tried that yet

2025-12-19T18:36:18.410379Z

Yeah I saw those validation errors, but as soon as I had massaged my test data enough to make them go away, I got an invalid-schema exception

2025-12-19T18:36:57.780259Z

But now that I know that I just need to look at submit-tx I feel confident again that I might be able to figure out what's going on there

2025-12-19T20:04:39.441649Z

Aaand I've found it. Just a syntax error in my schema. I really shouldn't program when I'm tired 😅

2025-12-19T21:19:00.129719Z

nice! I'm planning to add a schema validation check somewhere, probably in schema.clj, so you'll get errors for that right away instead of later the next time you try to submit a transaction.