<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Biff</title><id>https://biffweb.com/feed.xml</id><updated>2026-10-01T15:00:00.000Z</updated><link rel="self" href="https://biffweb.com/feed.xml" type="application/atom+xml" /><link href="https://biffweb.com" /><entry><title type="html">Understanding Datastar</title><id>https://biffweb.com/p/understanding-datastar/</id><updated>2026-10-01T15:00:00.000Z</updated><content type="html">&lt;p&gt;Previously I wrote &lt;a href="https://biffweb.com/p/understanding-htmx/"&gt;Understanding
htmx&lt;/a&gt; which was meant to explain htmx
conceptually, what the main tradeoffs are, and when you might want to use or not
use it. This post is a followup to that for &lt;a href="https://data-star.dev"&gt;Datastar&lt;/a&gt;.
Both of these posts describe how I personally think of these tools; if others
disagree with my framing, I make no apologies.&lt;/p&gt;
&lt;p&gt;Also note that the web apps I make do &lt;em&gt;not&lt;/em&gt; typically include collaborative or
real-time features, yet I still think Datastar is nice for this case. My
explanation here focuses on things that matter for people like me who make
boring apps.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;htmx and Datastar are both tools for doing server-side rendering instead of e.g.
using React; they both ask &amp;quot;what if we went back to how things were pre-jquery
and tried to extend that model of thin-client web development instead of moving
toward SPAs.&amp;quot; They differ in how they implement that vision.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;make an MPA&lt;br&gt;
each page is a resource&lt;br&gt;
keep a stream open on the current state of that resource&lt;/p&gt;
&lt;p&gt;-- a guy from the Datastar community&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Imagine: it's 2005. You have an MPA. Each page in your web app--a social network
for Tapirs--has a GET request handler (which returns a bunch of HTML for the
page) and an assortment of POST request handlers that do stuff and then redirect
back to that HTML endpoint. But then you run into the problems I brought up in
&lt;a href="https://biffweb.com/p/understanding-htmx/"&gt;Understanding htmx&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You need to implement the heart button so that people can heart their
favorite posts. However, if your heart button is a plain-old-form that causes
the entire page to reload, there are several potentially undesirable
consequences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All the posts in the feed will have to be fetched again.&lt;/li&gt;
&lt;li&gt;The posts that get fetched might be different.&lt;/li&gt;
&lt;li&gt;The user might lose their scroll position.&lt;/li&gt;
&lt;li&gt;The user might lose their draft if they were in the middle of typing a post.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Datastar approach looks something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The initial page load opens a long-lived SSE connection. Whenever backend
state changes (e.g. a database transaction is committed), the entire page is
re-rendered and pushed up to the client over the SSE connection—a bit
like telling the client to refresh the page but without doing an actual
browser refresh.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When the user takes an action, like hearting a post, Datastar makes an ajax
request to a POST request handler which updates the database. Since that
triggers a re-render by the SSE connection, the POST handler doesn't
redirect or return any HTML; it simply returns an empty 204 response.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Any state on the page that you don't want to recompute (e.g. a list of
recommended posts) can be stored in server-side &amp;quot;tab state,&amp;quot; keyed by some ID
that's unique to a single browser tab. For example, on page load, you could
trigger an action/POST request that computes the recommended posts and stores
them in tab state. The GET request handler &lt;em&gt;does not&lt;/em&gt; compute recommended
posts; it just reads tab state. If tab state hasn't been populated yet, it
shows a loading indicator.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you have an interaction that needs to be fast and you don't want to wait
for a server round-trip before re-rendering happens, you can have the
interaction update some &amp;quot;signals,&amp;quot; which are Datastar's mechanism for
handling frontend-only state. A common use case for this is storing form
field values: when you're typing into a text field, you don't want to wait
for the network before the characters show up.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Your application code has the same structure as the MPA-in-2005-thing; the main
differences are that your POST handlers return &lt;code&gt;204&lt;/code&gt; instead of &lt;code&gt;303&lt;/code&gt;, and you
instrument all your form fields so they're bound to signals. You get to keep the
single page rendering endpoint/function which gives you the whole &amp;quot;view is a
function of your state&amp;quot; thing, and then you can get rich interactivity on top
thanks to the SSE stuff.&lt;/p&gt;
&lt;p&gt;The cost: more moving parts on the backend, which means more work to set up your
codebase and more opportunities to screw something up. And your page rendering
function needs to be &lt;em&gt;fast&lt;/em&gt; since it's going to get called a bunch of times;
that could require some restructuring (such as the example above of using an
action on page load to compute recommended posts).&lt;/p&gt;
&lt;p&gt;htmx on the other hand doesn't try to overhaul your backend architecture. You're
typically doing standard request/response rather than this SSE thing. The
downside is that your application code has to do more stuff. You click a button,
htmx triggers a POST request, then the backend request handler has to know what
chunk(s) of html needs to be re-rendered and where those chunks should be
inserted in the DOM. Which is kind of imperative! You no longer have this dead
simple &amp;quot;the page is rendered by a single function&amp;quot; thing.&lt;/p&gt;
&lt;p&gt;So when should you use Datastar? My take: I don't think the downsides of
Datastar I've mentioned above are &lt;em&gt;that&lt;/em&gt; big of a deal, especially if you're
using a library that sets up the plumbing for you
(&lt;a href="https://github.com/jacobobryant/biff/tree/master/libs/datastar"&gt;ahem&lt;/a&gt;).
The main situation in which I'd be tempted to not use Datastar is if I'm
building something very small where plain-old form-posts-with-redirects is fine.&lt;/p&gt;
&lt;p&gt;I'm not sure I see any cases where I would use htmx again: I figure if I'm
making something complex enough to warrant htmx instead of plain form posts etc,
then the overhead of setting up Datastar is probably negligible.&lt;/p&gt;
</content><link href="https://biffweb.com/p/understanding-datastar/" /><author><name>Jacob O'Bryant</name><uri>https://obryant.dev</uri></author></entry><entry><title type="html">Biff 2.0 is released</title><id>https://biffweb.com/p/biff2-released/</id><updated>2026-09-10T16:00:00.000Z</updated><content type="html">&lt;p&gt;Last April I outlined &lt;a href="https://biffweb.com/p/biff2/"&gt;some changes&lt;/a&gt; I had planned
for Biff, which included making SQLite the default database, splitting the
monolithic &lt;code&gt;com.biffweb&lt;/code&gt; namespace into a bunch of independent libraries, using
Datastar by default, introducing some new approaches for keeping large codebases
maintainable, blah blah blah, and bumping the version to &lt;code&gt;2.0.0&lt;/code&gt;. I'm pleased
and slightly exhausted to announce that those changes are SHIPPED and you can
try them out like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;git clone https://github.com/jacobobryant/biff-starter my-project
cd my-project
clj -M:run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have &lt;a href="https://biffweb.com"&gt;tweaked the landing page&lt;/a&gt;, &lt;a href="https://github.com/jacobobryant/biff"&gt;written the
documentation&lt;/a&gt;, and even started, just
barely, to actually use Biff 2 to make a new app.&lt;/p&gt;
&lt;p&gt;If you've used Biff 1, then please be advised that there are technically no
breaking changes (since everything is in new namespaces) and that I've written
up &lt;a href="https://github.com/jacobobryant/biff/blob/master/docs/migrating-from-biff1.md"&gt;some
guidance&lt;/a&gt;
on gradually introducing Biff 2 into a Biff 1 codebase, if you so desire. There
&lt;em&gt;are&lt;/em&gt; &lt;a href="https://github.com/jacobobryant/biff/blob/master/CHANGELOG.md"&gt;some breaking
changes&lt;/a&gt; if
you've already been trying out the Biff 2 prereleases.&lt;/p&gt;
&lt;p&gt;A few things from Biff 2 that I find particularly interesting, some of which are
covered in more detail by the aforementioned &lt;a href="https://biffweb.com/p/biff2"&gt;blog
post&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;This
&lt;a href="https://github.com/jacobobryant/biff-starter/blob/main/src/com/example/app/demo.clj"&gt;demo.clj&lt;/a&gt;
file from the starter project gives a short yet representative taste of what
application code in a Biff project actually feels like. Note the parameters
injected into &lt;code&gt;demo-page&lt;/code&gt; by biff.graph; the POST request handlers that return
their side effects as data via biff.fx; the fact that only a single handler
needs to return HTML and those POST request handlers don't need to concern
themselves with rendering at all, and yet the page is fully reactive--thanks
to Datastar.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href="https://github.com/jacobobryant/biff-starter"&gt;starter project&lt;/a&gt; is now a
standalone repo and can be easily forked and modified if you want to create an
alternative starter project (with, say, a different database).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Speaking of using different databases, there is a guide on &lt;a href="https://github.com/jacobobryant/biff/blob/master/docs/db-adapters.md"&gt;writing a database
adapter&lt;/a&gt;.
This makes switching out the default database much easier than it was in Biff
1 (no need to rewrite the authentication module, for example).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;biff.core's &lt;a href="https://github.com/jacobobryant/biff/tree/master/libs/core#concepts"&gt;new module
system&lt;/a&gt;.
The flip side of making Biff more modular is that there's an increased need to
have well-defined interfaces for the modular pieces to plug into. I decided to
extract Biff's &amp;quot;framework&amp;quot; logic into a library not just to reduce
boilerplate but also to ensure things are being done the way that Biff 2
libraries expect.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/jacobobryant/biff/tree/master/libs/fx#pipelines"&gt;&lt;code&gt;defpipeline&lt;/code&gt;&lt;/a&gt;,
a very recent addition to biff.fx that cuts down the boilerplate and IMO makes
using biff.fx feel pretty ergonomic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/jacobobryant/biff/tree/master/libs/graph"&gt;biff.graph&lt;/a&gt; of
course. The whole thing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/jacobobryant/biff/tree/master/libs/run"&gt;biff.run&lt;/a&gt; and
&lt;a href="https://github.com/jacobobryant/biff/tree/master/libs/tasks"&gt;biff.tasks&lt;/a&gt;, the
latter of which has a video demo of using the &lt;code&gt;prod-setup&lt;/code&gt; and &lt;code&gt;deploy&lt;/code&gt; tasks
to deploy a vanilla (non-Biff) Clojure app to a fresh VPS.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My overall thoughts on where Biff has ended up: I'm definitely taking some bets
here. biff.fx and biff.graph are a bit weird. Awesome, but weird. Will the
benefits really matter for the projects people use Biff for? biff.core's module
system, despite being fairly lightweight, is still not as lightweight as the
5-line &lt;code&gt;reduce&lt;/code&gt; call that Biff 1 used. Does that similarly push Biff further out
of good-for-a-weekend-project territory? At the same time as I'm adding these
features to benefit large codebases, does switching to SQLite--an embedded
database--make Biff less attractive for projects that are likely to end up with
large codebases?&lt;/p&gt;
&lt;p&gt;My north star is still &amp;quot;what do I want for myself,&amp;quot; so despite the hypotheticals
above, I'm not actually &lt;em&gt;that&lt;/em&gt; concerned: I think all this new stuff is
ridiculously sweet. And I do think the modularity and the related ease of making
alternative starter projects is somewhat huge. Biff is much more evolvable now.
If I've gotten anything wrong, it really shouldn't be that difficult for anyone
(including my future self) to fork my starter project and fix whatever it is
that needs fixing.&lt;/p&gt;
&lt;p&gt;Except for biff.core; we're stuck with that part now.&lt;/p&gt;
</content><link href="https://biffweb.com/p/biff2-released/" /><author><name>Jacob O'Bryant</name><uri>https://obryant.dev</uri></author></entry><entry><title type="html">A bundle of CLI tasks for tools.deps projects</title><id>https://biffweb.com/p/tasks/</id><updated>2026-08-18T17:00:00.000Z</updated><content type="html">&lt;p&gt;I've just released
&lt;a href="https://github.com/jacobobryant/biff/tree/v2.x/libs/run"&gt;biff.run&lt;/a&gt;, a very
light &lt;code&gt;clj&lt;/code&gt;-based task runner; and
&lt;a href="https://github.com/jacobobryant/biff/tree/v2.x/libs/tasks"&gt;biff.tasks&lt;/a&gt;, a bunch
of default CLI tasks that can be used in tools.deps projects.&lt;/p&gt;
&lt;p&gt;The tasks are mostly the same as what I've already been including with Biff
projects for the past several years. I've reworked them a bit, added a few new
tasks, and have structured them to be hopefully useful for tools.deps projects
in general rather than being coupled to &amp;quot;Biff projects.&amp;quot;&lt;/p&gt;
&lt;p&gt;The biff.tasks docs linked above gives a good overview of what tasks it
includes. Some unorganized thoughts/random stuff I think is interesting/design
opinions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;biff.run is an answer to the question &amp;quot;what's an ergonomic way to publish a
curated collection of tasks rather than a single task.&amp;quot; Instead of one
deps.edn alias per task, there's a single &lt;code&gt;:run&lt;/code&gt; alias for all your tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A criticism of tools.deps is that it's lacked various functionality that lein
has had built in (simple but not necessarily easy). biff.tasks is my take on
addressing that, and I'm interested in if it could help people get going with
Clojure more smoothly. However Biff users specifically are still the main
audience I'm designing stuff for; time's limited.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Startup time has been fine. I've settled on a few approaches for managing it:
each task goes in a separate namespace so biff.run can load only the code for
the task you're running; tasks use &lt;code&gt;requiring-resolve&lt;/code&gt; when they have heavy
dependencies that are used conditionally; some tasks shell out to pre-compiled
binaries (like &lt;code&gt;cljfmt&lt;/code&gt; and &lt;code&gt;clj-kondo&lt;/code&gt;). In return, being able to do
everything in plain Clojure and use the regular dependency tooling is nice.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I did tinker with passing the classpath to Babashka like &lt;code&gt;bb -cp $(clj -A:run -Spath) -m com.biffweb.tasks.lib -h&lt;/code&gt;. Seems like it'd be kinda cool to default
to plain clj but then say &amp;quot;push this button to get instant startup times with
Babashka instead.&amp;quot; It hasn't been a huge need for me though so I haven't
explored it too much.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Speaking of pre-compiled binaries, biff.tasks uses some &lt;a href="https://github.com/jacobobryant/biff/blob/v2.x/libs/stuff/src/com/biffweb/stuff/bin.clj"&gt;internal
machinery&lt;/a&gt;
that makes working with these binaries pretty seamless. biff.tasks specifies a
default version for each binary, users can set a different version in their
project config if desired, and whenever a task needs to use that binary,
biff.tasks ensures the right version is installed. If not, biff.tasks
downloads the correct version.
&lt;a href="https://github.com/jacobobryant/biff/blob/e93a094ba5127a55875af661f435312bedcd6bb0/libs/tasks/src/com/biffweb/tasks/impl/format.clj#L42"&gt;Example&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;biff.tasks doesn't include a task for creating a new project because IMO
project templates should provide a way to use them without having to install
another tool first. That's perhaps the one bit of functionality that might be
nice to have baked into &lt;code&gt;clj&lt;/code&gt;; only then could I as a project template author
safely assume that all my users will already have the task installed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;biff.tasks &lt;em&gt;does&lt;/em&gt; come with an &lt;code&gt;init&lt;/code&gt; task that renames the current project's
main namespace from &lt;code&gt;com.example&lt;/code&gt; to a namespace that the user provides. The
idea is that you use project templates by doing &lt;code&gt;git clone &amp;lt;some template&amp;gt;; rm -rf .git; git init; clj -M:run init&lt;/code&gt;. (The project template would have
biff.tasks already installed in its deps.edn). Probably with that all wrapped
up into a little script so you get a nice one-liner.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For library projects, I settled on a &lt;code&gt;docs&lt;/code&gt; task that turns your docstrings
into markdown files &lt;a href="https://github.com/jacobobryant/biff/blob/97ed32fe82ee6de46bdc27d3cfa2f8f26034484a/libs/tasks/docs/api/com.biffweb.tasks.md"&gt;like this
one&lt;/a&gt;.
I'm quite fond of it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content><link href="https://biffweb.com/p/tasks/" /><author><name>Jacob O'Bryant</name><uri>https://obryant.dev</uri></author></entry><entry><title type="html">Deploy an app with biff.tasks</title><id>https://biffweb.com/p/deploy-biff-tasks/</id><updated>2026-08-18T16:00:00.000Z</updated><content type="html">&lt;div style="padding:56.25% 0 0 0;position:relative;"&gt;&lt;iframe src="https://player.vimeo.com/video/1219253956?badge=0&amp;amp;autopause=0&amp;amp;player_id=0&amp;amp;app_id=58479" frameborder="0" allow="autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share" referrerpolicy="strict-origin-when-cross-origin" style="position:absolute;top:0;left:0;width:100%;height:100%;" title="Deploy an app with biff.tasks"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;script src="https://player.vimeo.com/api/player.js"&gt;&lt;/script&gt;
&lt;br&gt;
&lt;p&gt;I demonstrate how to provision a new Ubuntu server on DigitalOcean and deploy a hello-world Clojure web app to it with biff.tasks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jacobobryant/biff/blob/v2.x/libs/tasks"&gt;biff.tasks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><link href="https://biffweb.com/p/deploy-biff-tasks/" /><author><name>Jacob O'Bryant</name><uri>https://obryant.dev</uri></author></entry><entry><title type="html">biff.datastar, biff.ring</title><id>https://biffweb.com/p/datastar/</id><updated>2026-08-10T15:15:00.000Z</updated><content type="html">&lt;p&gt;A couple more Biff 2 libraries are out the door:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/jacobobryant/biff/tree/v2.x/libs/datastar"&gt;biff.datastar&lt;/a&gt;:
the dumbest/awesomest possible way to make a reactive, server-side-rendered
web app. Over the past several years I haven't put much priority on making it
easy to make fancy reactive/real-time/collaborative UIs since my UI needs are
typically pretty simple. But this architecture is actually really nice even
when you don't need the fancy stuff.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/jacobobryant/biff/tree/v2.x/libs/ring"&gt;biff.ring&lt;/a&gt;: mostly
some Biff-related plumbing code. I think the &lt;code&gt;defroute&lt;/code&gt; macro is pretty nice.
There's a cool &lt;code&gt;wrap-csrf-protection&lt;/code&gt; middleware that &lt;a href="https://words.filippo.io/csrf"&gt;doesn't use
tokens&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last &amp;quot;big&amp;quot; Biff 2 library I need to fix up and release will be biff.tasks,
the one for all the CLI tasks. Other than that there's biff.authentication, the
email-powered authentication module thing (now with a default sign-in form
included) and a few other doodads. And then a starter app. And some
documentation that ties all the libraries together, not just documentation for
the individual libraries (which I've been writing as I go).&lt;/p&gt;
&lt;p&gt;I'm still shooting to have all that released before the conj, which is... coming
up. On the bright side, in the window of time between writing the first draft of
this post and sending it out, I've already finished editing the biff.tasks code
and only need to write the documentation. So I'd say we're well on our way.&lt;/p&gt;
</content><link href="https://biffweb.com/p/datastar/" /><author><name>Jacob O'Bryant</name><uri>https://obryant.dev</uri></author></entry><entry><title type="html">Database adapters in Biff 2</title><id>https://biffweb.com/p/db-adapters/</id><updated>2026-07-23T14:30:00.000Z</updated><content type="html">&lt;p&gt;I've released two new Biff libraries, both database adapters:
&lt;a href="https://github.com/jacobobryant/biff/tree/v2.x/libs/sqlite"&gt;biff.sqlite&lt;/a&gt;
and
&lt;a href="https://github.com/jacobobryant/biff/tree/v2.x/libs/xtdb"&gt;biff.xtdb&lt;/a&gt;.
Both of them implement some interfaces used by various other Biff libraries, and
they also both implement additional functionality that can be useful to Clojure
apps even if they aren't using Biff.&lt;/p&gt;
&lt;h1 id="the-interfaces"&gt;The interfaces&lt;/h1&gt;
&lt;p&gt;So far, modifying a Biff app to use a different database than the default has
been &lt;a href="https://biffweb.com/p/how-to-use-postgres-with-biff/"&gt;kind of
inconvenient&lt;/a&gt;, as a result
of my &lt;a href="https://biffweb.com/p/philosophy-of-biff/"&gt;philosophy&lt;/a&gt; about modularity for
Biff 1. The ability to swap out defaults was more of an escape hatch so that
starting out with Biff doesn't mean you're locked into all its choices forever. So
that meant if you wanted to swap out the database, you had to, for example, copy
and paste all of Biff's sign-in-via-email code and rewrite the queries.&lt;/p&gt;
&lt;p&gt;With Biff 2, modularity is becoming &lt;a href="https://biffweb.com/p/biff2/#alternate-starter-projects-will-get-easier"&gt;more of a first-class
citizen&lt;/a&gt;.
Hence these new &amp;quot;interfaces.&amp;quot; And the main interface here addresses the question
of, you know, how do you put things in a database. And get them back out. For
Biff 2, I wanted be able to package up shared application functionality that
needs persistence (such as the authentication module) in a database-agnostic
way.&lt;/p&gt;
&lt;p&gt;So Biff 2 now defines a key-value store interface which can be implemented by
database adapter libraries like the two I've just released:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jacobobryant/biff/blob/v2.x/libs/core/docs/reference/schema.md#biffcorekv-set"&gt;&lt;code&gt;:biff.core/kv-set&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jacobobryant/biff/blob/v2.x/libs/core/docs/reference/schema.md#biffcorekv-get"&gt;&lt;code&gt;:biff.core/kv-get&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jacobobryant/biff/blob/v2.x/libs/core/docs/reference/schema.md#biffcorekv-list"&gt;&lt;code&gt;:biff.core/kv-list&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And my two implementations:
&lt;a href="https://github.com/jacobobryant/biff/blob/b3abe5b13824af2f83f89ec31c63a430417ac457/libs/sqlite/src/com/biffweb/sqlite/impl/kv.clj"&gt;sqlite&lt;/a&gt;
and
&lt;a href="https://github.com/jacobobryant/biff/blob/b3abe5b13824af2f83f89ec31c63a430417ac457/libs/xtdb/src/com/biffweb/xtdb/impl/kv.clj"&gt;xtdb&lt;/a&gt;.
These KV-store functions are exposed via a biff.core module, &lt;a href="https://github.com/jacobobryant/biff/blob/b3abe5b13824af2f83f89ec31c63a430417ac457/libs/sqlite/src/com/biffweb/sqlite/impl/system.clj#L34"&gt;for
example&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In general I try to avoid adding layers to things unnecessarily, so it took a
little thinking to arrive at this solution. My main thought was that I'm
primarily interested in database-agnosticism for libraries, not applications.
Migrating a single application's database is a different use case than wanting
to provide functionality for multiple applications using different databases,
and it's the latter use case that I'm trying to address.&lt;/p&gt;
&lt;p&gt;So I didn't want to introduce some sort of &amp;quot;Biff query/transaction language&amp;quot;
that you'd use whenever writing a Biff app; that would be overkill. The
database-agnostic libraries I wanted to write have only basic persistence needs,
so a key-value interface is sufficient. And that's an easy interface for
database adapters to implement.&lt;/p&gt;
&lt;p&gt;The second biggest interface-type-thing is that both adapters come with
&lt;code&gt;make-resolvers&lt;/code&gt; functions
(&lt;a href="https://github.com/jacobobryant/biff/blob/b3abe5b13824af2f83f89ec31c63a430417ac457/libs/sqlite/src/com/biffweb/sqlite/impl/resolver.clj#L31"&gt;sqlite&lt;/a&gt; / &lt;a href="https://github.com/jacobobryant/biff/blob/b3abe5b13824af2f83f89ec31c63a430417ac457/libs/xtdb/src/com/biffweb/xtdb/impl/resolver.clj#L23"&gt;xtdb&lt;/a&gt;) which generate a set of
&lt;a href="https://github.com/jacobobryant/biff/tree/v2.x/libs/graph#defining-resolvers"&gt;biff.graph
resolvers&lt;/a&gt; for the tables in your application schema.&lt;/p&gt;
&lt;h1 id="the-features"&gt;The features&lt;/h1&gt;
&lt;p&gt;Besides the key-value functions and a couple other doodads, the remaining
functionality in these database libraries is just whatever stuff I thought would
be nice to have when writing a Clojure app using the respective databases. From
biff.sqlite's README:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Sane defaults like WAL mode, STRICT tables, etc.&lt;/li&gt;
&lt;li&gt;Backup/restore via &lt;a href="https://litestream.io"&gt;Litestream&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Migrations via &lt;a href="https://github.com/sqldef/sqldef"&gt;sqldef&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Rich schema types: define columns as e.g. booleans, instants, nested maps,
etc; and biff.sqlite converts them to/from SQLite's supported types
(ints, blobs, etc).&lt;/li&gt;
&lt;li&gt;Validate transactions based on centralized authorization rules you define
(helps to keep LLM code secure).&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;And for biff.xtdb:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Start up an in-process node with high-level config defaults.&lt;/li&gt;
&lt;li&gt;Custom &lt;code&gt;:biff/upsert&lt;/code&gt; and &lt;code&gt;:biff/assert-unique&lt;/code&gt; transaction operations.&lt;/li&gt;
&lt;li&gt;Optionally enforce Malli schemas on write.&lt;/li&gt;
&lt;li&gt;Define centralized authorization rules for validating transactions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;biff.sqlite is a chonkier library due to all the logic needed for supporting
rich schema types.&lt;/p&gt;
&lt;h1 id="write-your-own-database-adapter"&gt;Write your own database adapter&lt;/h1&gt;
&lt;p&gt;Finally, I have written a &lt;a href="https://github.com/jacobobryant/biff/blob/v2.x/docs/db-adapters.md"&gt;database adapter
guide&lt;/a&gt; which
lists all the interfaces that adapters should implement and suggests additional
features that may or may not be relevant for any given database. Between that
guide and the two sqlite/xtdb reference implementations, I'm hoping it should be
straightforward to implement an adapter for whatever database you want to use. I
will probably only maintain the SQLite and XTDB adapters, but I might publish
some as-is code for other databases which could be picked up and maintained
anyone who so chooses.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Plug: &lt;a href="https://jobs.ashbyhq.com/tyba/efd553f6-0e29-4827-af95-0fc41f063042?utm_source=5P6E9lPL4v"&gt;my team is
hiring&lt;/a&gt;
for a senior software engineer, writing ClojureScript and Python. We make
optimization software for clean energy projects.&lt;/em&gt;&lt;/p&gt;
</content><link href="https://biffweb.com/p/db-adapters/" /><author><name>Jacob O'Bryant</name><uri>https://obryant.dev</uri></author></entry><entry><title type="html">biff.fx: lightweight effects system</title><id>https://biffweb.com/p/fx/</id><updated>2026-06-16T17:20:00.000Z</updated><content type="html">&lt;p&gt;I'm releasing another Biff 2 library:
&lt;a href="https://github.com/jacobobryant/biff/tree/v2.x/libs/fx"&gt;biff.fx&lt;/a&gt;. It's a
lightweight approach to removing effects from your application logic, which
makes that logic easier to understand, test, and reuse.&lt;/p&gt;
&lt;p&gt;There are basically two ideas here. First is the common approach of having your
code return data describing effects (http requests, database
queries/transactions, etc) it wants to run instead of running those effects
directly. So for example, instead of calling &lt;code&gt;(http/request &amp;quot;https://example.com&amp;quot; {:query-params {:foo &amp;quot;bar&amp;quot;}})&lt;/code&gt;, you would return a vector
like &lt;code&gt;[:my-application.fx/http &amp;quot;https://example.com&amp;quot; {:query-params {:foo &amp;quot;bar&amp;quot;}}]&lt;/code&gt;, and then some sort of orchestrator would call &lt;code&gt;http/request&lt;/code&gt; for you.
Then it's easy to unit-test your code since it's pure, and if you wanted to swap
out certain effect implementations when running integration tests, that's easy
to do too.&lt;/p&gt;
&lt;p&gt;You could set something like that up with Ring middleware where effects run
before and after the handler. Handler functions could somehow declare what input
data they need/what database query they need to run, if any, and then they could
return some effect data for any database transactions etc they need to do
afterward.&lt;/p&gt;
&lt;p&gt;That works as long as you can structure your logic and effects like a sandwich,
with effects on the outside and gooey, pure logic on the inside. What if you
need logic on either side of an effect though, i.e. what if you need to
interleave logic and effects? For example, in one of my apps I have some code to
initialize a Stripe checkout session. It has to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Query our database to see if the current user has a Stripe customer ID&lt;/li&gt;
&lt;li&gt;If not: hit the Stripe API to create a new customer and save the returned ID
in our database&lt;/li&gt;
&lt;li&gt;Hit the Stripe API to create a new checkout session&lt;/li&gt;
&lt;li&gt;Save the session ID in our database and redirect to a URL returned by the
Stripe API&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The database bits can be pushed before and after our logic, however the HTTP
requests can't. So what do we do?&lt;/p&gt;
&lt;p&gt;One approach would be to just have a special case for situations like this under
the assumption that &lt;em&gt;most&lt;/em&gt; of your code can in fact be structured as a sandwich.
i.e. write a plain-old-impure-function and be on with your day.&lt;/p&gt;
&lt;p&gt;Another approach is to use data not just to describe effects but also to
describe control flow. One of the shapes that approach can take is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each &amp;quot;chunk&amp;quot; of logic from a previously impure function is extracted into a
separate, pure function.&lt;/li&gt;
&lt;li&gt;Those functions can return data that describes both (1) what effects they need
to run, and (2) which pure logic function should run next.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;i.e. you make a state machine where the states are pure logic and effects happen
in the transitions. And that's what
&lt;a href="https://github.com/jacobobryant/biff/tree/v2.x/libs/fx"&gt;biff.fx&lt;/a&gt; does.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Plug: &lt;a href="https://jobs.ashbyhq.com/tyba/efd553f6-0e29-4827-af95-0fc41f063042?utm_source=r2RjeVOa9N"&gt;my team is
hiring&lt;/a&gt;
for a senior software engineer, writing ClojureScript and Python mostly. We make modeling software
for renewable energy projects.&lt;/em&gt;&lt;/p&gt;
</content><link href="https://biffweb.com/p/fx/" /><author><name>Jacob O'Bryant</name><uri>https://obryant.dev</uri></author></entry><entry><title type="html">New library: biff.core</title><id>https://biffweb.com/p/core/</id><updated>2026-06-09T17:00:00.000Z</updated><content type="html">&lt;p&gt;As I wrote about &lt;a href="https://biffweb.com/p/biff2/"&gt;previously&lt;/a&gt;, I've been working
on splitting Biff up into a bunch of separate libraries and changing various
things along the way. I've completed a rough draft of &lt;a href="https://github.com/jacobobryant/biff/tree/v2.x#libraries"&gt;all twelve
libraries&lt;/a&gt; and am now
going through them one-by-one to polish and release them. The first library is
now ready.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/jacobobryant/biff/tree/v2.x/libs/core"&gt;biff.core&lt;/a&gt;: system
composition and other interfaces for Biff projects. This is the glue that holds
all the other libraries together, and that's why I'm releasing it first.&lt;/p&gt;
&lt;p&gt;For a long time Biff has had this &amp;quot;modules and components&amp;quot; structure where each
application namespace in your project exposes a &amp;quot;module&amp;quot; map, then you have a
bunch of boilerplate to combine stuff from those modules into a single &amp;quot;system&amp;quot;
map, and then we thread the system map through your &amp;quot;component&amp;quot; functions on
startup. Biff 2 retains that structure, and it has some additional stuff to deal
with that boilerplate.&lt;/p&gt;
&lt;p&gt;For an example of what I'm talking about, see &lt;a href="https://github.com/jacobobryant/biff/blob/e164e341fd51ca361f065a5ecd59873c39f247b9/starter/src/com/example.clj#L25-L31"&gt;this
code&lt;/a&gt;
which takes the &lt;code&gt;:routes&lt;/code&gt; (and &lt;code&gt;:api-routes&lt;/code&gt;) keys from your modules and turns
them into a &lt;code&gt;:biff/handler&lt;/code&gt; value for the system map. I wanted a first-class way
to be able to extract that kind of logic cleanly into a library so that the
library's instructions can just be &amp;quot;add this module to your project&amp;quot; without an
accompanying &amp;quot;and then paste all this stuff into your main namespace.&amp;quot;&lt;/p&gt;
&lt;p&gt;So this new biff.core library includes a concept of &amp;quot;init functions.&amp;quot; These are
functions that take a collection of modules and return a single map that can be
merged into your system map. Ta da. &lt;a href="https://github.com/jacobobryant/biff/blob/ece1cb9dcb27c1dc6bb033f76a0a370a8e1eacd7/libs/ring/src/com/biffweb/ring.clj#L407"&gt;Here's an
example&lt;/a&gt;.
Init functions are stored in the &lt;code&gt;:biff.core/init&lt;/code&gt; key in your module maps, so
we get that nice &amp;quot;all you need are modules (well, and components)&amp;quot; effect.&lt;/p&gt;
&lt;p&gt;The main complication here is that the boilerplate of defining a &lt;code&gt;(def handler ...)&lt;/code&gt; var in your application code actually has a nice side benefit: late
binding. If you change any of your modules, the handler var will get updated,
and if you set &lt;code&gt;:biff/handler&lt;/code&gt; in your system map to the var instead of the
value (&lt;code&gt;#’handler&lt;/code&gt;), incoming Ring requests get the latest handler without you
having to restart the web server. If we extract that boilerplate into library
code, we don't get the var.&lt;/p&gt;
&lt;p&gt;I ended up on this solution:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Init functions take a &lt;em&gt;var&lt;/em&gt; of your modules vector, not the vector value
itself.&lt;/li&gt;
&lt;li&gt;Anything in the system map that you want to get updated without a restart
needs to be a function. In some cases this means instead of setting a
&lt;code&gt;:com.example/my-thing&lt;/code&gt; key on the system map, you need to set a
&lt;code&gt;:com.example/get-my-thing&lt;/code&gt; function which returns &lt;code&gt;my-thing&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;That function on the system map should dereference the modules var and pass it
to a memoized function that builds whatever thing it is you need (like the
Ring handler).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Again, see &lt;a href="https://github.com/jacobobryant/biff/blob/ece1cb9dcb27c1dc6bb033f76a0a370a8e1eacd7/libs/ring/src/com/biffweb/ring.clj#L407"&gt;this
example&lt;/a&gt;.
The result is kind of aesthetically pleasing: you get a nice clean &lt;a href="https://github.com/jacobobryant/biff/blob/ece1cb9dcb27c1dc6bb033f76a0a370a8e1eacd7/demo/src/com/biffweb/demo.clj"&gt;main
namespace&lt;/a&gt;
that shouldn't need to change much, and all you do is add
&lt;a href="https://github.com/jacobobryant/biff/blob/ece1cb9dcb27c1dc6bb033f76a0a370a8e1eacd7/demo/src/com/biffweb/demo/modules.clj"&gt;modules&lt;/a&gt;
and
&lt;a href="https://github.com/jacobobryant/biff/blob/ece1cb9dcb27c1dc6bb033f76a0a370a8e1eacd7/demo/src/com/biffweb/demo/components.clj"&gt;components&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There's always the temptation to consolidate things further. Why even have a
separate components vector? Why not have modules support &lt;code&gt;:biff.core/on-start&lt;/code&gt;
and &lt;code&gt;:biff.core/on-stop&lt;/code&gt; keys and then have some way to express dependencies
between these lifecycle functions so we can call them in the right order?&lt;/p&gt;
&lt;p&gt;And the answer is so that we don't have to have some way to express dependencies
between these lifecycle functions so we can call them in the right order. It's
not that hard to put the components in the right order yourself (especially
since the Biff starter project does that for you), and then it's easier to
understand how components work. It's just a sequence of functions that you pass
a map through. If you work on a project with so many stateful resources that
it's hard to keep track of them all, you can always layer something on top that
figures out what your components vector should be before you pass it to
biff.core.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Plug: &lt;a href="https://jobs.ashbyhq.com/tyba/efd553f6-0e29-4827-af95-0fc41f063042?utm_source=r2RjeVOa9N"&gt;my team is
hiring&lt;/a&gt;
for a senior software engineer, writing ClojureScript and Python mostly. We make modeling software
for renewable energy projects.&lt;/em&gt;&lt;/p&gt;
</content><link href="https://biffweb.com/p/core/" /><author><name>Jacob O'Bryant</name><uri>https://obryant.dev</uri></author></entry><entry><title type="html">Biff 2.0 sneak peak</title><id>https://biffweb.com/p/biff2/</id><updated>2026-04-20T18:25:00.000Z</updated><content type="html">&lt;p&gt;I have for the past year or two been working on some large Biff changes, such as those discussed in
&lt;a href="https://biffweb.com/p/structuring-large-codebases/"&gt;Structuring large Clojure codebases with Biff&lt;/a&gt;
and &lt;a href="https://biffweb.com/p/xtdb2-prerelease/"&gt;Biff support for XTDB v2 is in pre-release&lt;/a&gt;. Now that
coding agents have gone mainstream (and in particular, now that I personally have started using them
heavily), I've had a few more ideas for changes I'd like to make to Biff. And also thanks to coding
agents, I've actually been able to make consistent progress instead of my Biff development time
being bottlenecked by how late I can stay awake on weekend nights after my kids are sleeping. So we,
fingers crossed, are getting close to some major Biff updates, and I figure I may as well slap a 2.0
label on it.&lt;/p&gt;
&lt;p&gt;Here's what I've got in the works.&lt;/p&gt;
&lt;h2 id="sqlite-will-be-the-default-database"&gt;SQLite will be the default database&lt;/h2&gt;
&lt;p&gt;This is the biggest change. Biff will retain first-class support for XTDB, but it'll also have
first-class support for SQLite, and I'll update the starter project to use SQLite by default. There
will still be a (non-default) starter project that uses XTDB.&lt;/p&gt;
&lt;p&gt;Biff has used XTDB since its (Biff's) initial release in 2020, back when the database was still
called Crux. About a year ago I started working on migrating Biff from XTDB v1 to XTDB v2, which
brings a whole new architecture, including column-oriented indexes that make analytical queries
faster. Besides writing some Biff-specific helper code for XTDB v2, I migrated
&lt;a href="https://yakread.com"&gt;Yakread&lt;/a&gt; (a 10k-LOC article recommender system) to v2 and did a bunch of
benchmarking for Yakread's queries. (A big thank you to the XTDB team who responded to lots of my
questions during this time and also made a bunch of query optimizations!)&lt;/p&gt;
&lt;p&gt;Long-story short: despite the optimizations, I had trouble getting Yakread's page load times to be
as quick as I wanted. For the particular queries Yakread runs—which are mostly row-oriented—I've
generally found v2's performance to be slower than v1. There is also a larger per-query latency
overhead, perhaps another design tradeoff of the new architecture (you can still run v2 as an
embedded node within your application process, but it’s designed primarily to be run on a separate
machine like more traditional networked databases).&lt;/p&gt;
&lt;p&gt;I also will admit that before this benchmarking exercise I had not actually used SQLite much, and I
was unaware of how &lt;em&gt;ridiculously&lt;/em&gt; fast it is. And one of the main downsides of SQLite when compared
to XTDB—that SQLite is a mutable database—is mitigated by Litestream, which streams
changes to object storage and lets you restore from (and even &lt;a href="https://fly.io/blog/litestream-vfs/"&gt;run ad-hoc
queries&lt;/a&gt; on) historical snapshots saved with 30-second
granularity.&lt;/p&gt;
&lt;p&gt;I could see myself switching back to XTDB at some point in the future. It's still the early days for
v2 and the XTDB team is doing lots of work, including on query performance. And SQLite's speed comes
with tradeoffs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Scaling beyond one machine is an unsolved problem. Litefs can let you put SQLite nodes in a
cluster where writes get forwarded to a single leader and changes are streamed to the other nodes.
However, to use it with Litestream, you have to &lt;a href="https://fly.io/docs/litefs/backup/#continuous-backup-via-litestream"&gt;disable automatic leader
failover&lt;/a&gt;. So you basically
have to choose between HA or PITR.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SQLite only supports a few basic datatypes: ints, floats, strings, and blobs (byte arrays). A
large part of my work in integrating SQLite into Biff has been to set up automatic data type
coercion so you can use richer types (UUID, boolean, instant, enum, map/set/vector) in your schema
without having to do manual coercion when reading and writing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Litestream's snapshots-at-30-second-granularity is fine for recovering from bad transactions like
a &lt;code&gt;DELETE FROM&lt;/code&gt; without the &lt;code&gt;WHERE&lt;/code&gt;, but it's less helpful than XTDB/Datomic for the
debugging-weird-production-issues use case: you can't include a transaction ID or similar in your
production logs and then re-run queries with 100% confidence that the results you're seeing are
what the application saw when it e.g. threw an unexpected exception.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was chatting with Jeremy from the XTDB team last week, and he mentioned they've been working on
having XTDB ingest changes directly from Postgres. It sounds like it shouldn't be much work to make
that work with SQLite too, which means that you could stick an XTDB node alongside your
SQLite-powered Biff app and then get more granular historical queries. Maybe XTDB could be a
replacement for Litestream?&lt;/p&gt;
&lt;p&gt;That could get even more interesting if eventually we can do the inverse as well, where data from
our immutable XTDB log could be sent both to a bitemporal index for historical queries and also to
SQLite &amp;quot;indexes&amp;quot;/databases for the application servers to use. That would solve the HA problem too.&lt;/p&gt;
&lt;p&gt;Anyway. However it happens, I'm looking forward to the glorious future when we finally have an
&lt;a href="https://martin.kleppmann.com/2015/03/04/turning-the-database-inside-out.html"&gt;inside-out database&lt;/a&gt;
that's fast for all query shapes, highly available, models time correctly, and can even do advanced
things like let you put a UUID in it. In the meantime, I think SQLite is a reasonable default given
Biff's focus on solo developers, and I would absolutely consider XTDB today for situations in which
modeling time correctly is a top concern.&lt;/p&gt;
&lt;h2 id="alternate-starter-projects-will-get-easier"&gt;Alternate starter projects will get easier&lt;/h2&gt;
&lt;p&gt;Biff consists of a starter project, a bunch of helper code exposed through a single &lt;code&gt;com.biffweb&lt;/code&gt;
namespace, tooling for CLI tasks and deployment, and a big pile of documentation. The
&lt;code&gt;com.biffweb&lt;/code&gt; namespace is on its way out: I'll be publishing Biff helper code as individual
libraries like &lt;code&gt;com.biffweb.sqlite&lt;/code&gt; (and &lt;code&gt;com.biffweb.xtdb&lt;/code&gt;), &lt;code&gt;com.biffweb.authentication&lt;/code&gt;,
&lt;code&gt;com.biffweb.middleware&lt;/code&gt;, &lt;code&gt;com.biffweb.config&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;Part of the motivation for this change is that Biff is more mature than it was five years ago and
it's become more clear what the different cohesive parts of Biff should actually be. I started out
with a single kitchen-sink library because splitting it up felt premature; I didn't think it would
realistically make sense to use one of them outside a standard Biff project that would already be
depending on all the Biff libraries anyway.&lt;/p&gt;
&lt;p&gt;But over the past few months, I've been developing a couple new side projects from scratch without
even using Biff. As I've done this, I've started extracting various things into standalone
libraries, and this time I &lt;em&gt;do&lt;/em&gt; see them as useful libraries in their own right. For example, the
new biff.authentication library will be an easy way to add email-based authentication to any Clojure
web app that uses Reitit—it even comes with a default sign-in page.&lt;/p&gt;
&lt;p&gt;The other factor behind this change is agent-driven development. The difficulty of
mixing-and-matching different libraries is dramatically easier now to the point where I wondered
briefly if Biff was even needed anymore. Developing those new side projects via agent has disabused
me of that notion: agents still need a lot of structure (e.g. in the form of these Biff libraries)
to guide them. Even for starting new projects, why have everyone generate a different starter
project via some prompt when you could have a single person generate the starter project, make sure
it actually works, and then publish that?&lt;/p&gt;
&lt;p&gt;That's still a meaningful change though: the effort required to create and maintain new project
templates has decreased significantly. So I think it makes more sense for Biff to be split up into
multiple libraries that can themselves be mixed-and-matched. I will myself provide Biff starter
projects for SQLite and XTDB, respectively. If anyone else wants to make a Biff starter project
variant with different library choices, they'll similarly be able to do that without much effort.&lt;/p&gt;
&lt;p&gt;For vanity reasons, I'll need to continue having a single &amp;quot;main&amp;quot; Biff repo of some sort (did I
mention Biff hit 1,000 github stars recently?). Maybe I'll have that repo be the default starter
project.&lt;/p&gt;
&lt;h2 id="new-approaches-for-structuring-application-logic"&gt;New approaches for structuring application logic&lt;/h2&gt;
&lt;p&gt;Two of these Biff libraries that happen to contain some new stuff—instead of being a
splitting-out of code that was already in Biff—are
&lt;a href="https://github.com/jacobobryant/biff.graph"&gt;biff.graph&lt;/a&gt;, which lets you structure your domain model
as a queryable graph, inspired by Pathom; and &lt;a href="https://github.com/jacobobryant/biff.fx"&gt;biff.fx&lt;/a&gt;,
which helps you remove effectful code from your application logic via state machines.&lt;/p&gt;
&lt;p&gt;Both libraries help you write purer code (and thus code that's easier to understand and test).
biff.graph is a higher-level abstraction that helps with code that reads data.
biff.fx is a lower-level thing that I mostly use when writing data. However
they're also useful together: e.g. my GET request handlers are typically biff.fx
machines that run a biff.graph query and pass the results to the (now pure) rendering code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-clojure"&gt;(def some-route
  [&amp;quot;/some-page/:id&amp;quot;
   {:get
    (fx/machine ::some-page

      :start
      (fn [{:keys [path-params] :as request}]
        {:stuff [:biff.fx/graph
                 {:stuff/id (parse-uuid (:id path-params))}
                 [:stuff/foo :stuff/bar]]
         :biff.fx/next :render-stuff})

      :render-stuff
      (fn [{:keys [stuff] :as request}]
        {:status 200
         :headers {&amp;quot;content/type&amp;quot; &amp;quot;text/html&amp;quot;}
         :body (render-html
                [:div &amp;quot;foo: &amp;quot; (:stuff/foo stuff)
                 &amp;quot;, bar: &amp;quot; (:stuff/bar stuff)])}))}])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;biff.fx provides a &lt;code&gt;defroute&lt;/code&gt; macro to make this kind of thing more concise, so the code I actually write looks more like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-clojure"&gt;(fx/defroute some-page &amp;quot;/some-page/:id&amp;quot;
  [:biff.fx/graph
   {:params/stuff [:stuff/foo :stuff/bar]}]

  :get
  (fn [request stuff]
    [:div
     &amp;quot;foo: &amp;quot; (:stuff/foo stuff)
     &amp;quot;, bar: &amp;quot; (:stuff/bar stuff)]))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'll save a fuller explanation for later; hopefully that gives you the flavor of what these libs do.&lt;/p&gt;
&lt;p&gt;I've been using Pathom heavily over the past few years, both for work and pleasure. I've started
referring to the code structure it enables as “data-oriented dependency injection.” It helps you
structure your application in small easy-to-understand chunks that declare exactly what data they
need as input and what data they provide as output. The main downside in my experience is that it
can be difficult to understand exactly what Pathom is doing and debug when things go wrong.&lt;/p&gt;
&lt;p&gt;For “serious” projects, that's a price worth paying. For the kinds of solo projects that Biff is
aimed at, I've felt apprehensive about foisting another layer of abstraction on people for code
structure benefits that they may or may not notice.&lt;/p&gt;
&lt;p&gt;However, my own experience is that even for small apps, the benefit is real. So biff.graph is an
attempt to provide the same graph computational model / “data-oriented dependency injection” with as
small of an implementation as possible: biff.graph is about 400 lines of code currently, whereas
Pathom is closer to 10k.&lt;/p&gt;
&lt;p&gt;The main tradeoff I've made in service of that goal is to omit the query planning step that Pathom
uses. biff.graph traverses directly over your input query, looking up which resolver(s) to call for
each attribute as it goes. For each resolver, biff.graph runs what is more-or-less a separate query
to get that resolver's inputs. This hopefully makes biff.graph easier to trace and understand what
it's doing, but it also means biff.graph isn't able to optimize the query plan the way Pathom does.
(biff.graph does support batch resolvers and caching at least).&lt;/p&gt;
&lt;p&gt;biff.fx is more of an original creation. Instead of a single function, you have a
map of functions, one for each state. Effects happen in the transitions. You define global “fx
handlers” that do things like HTTP requests, database queries/transactions, etc, represented by
keywords (e.g. &lt;code&gt;:biff.fx/graph&lt;/code&gt; in the example). I’ve changed up the format
for describing effects a few times; I think I've finally landed on something that feels ergonomic
(&lt;code&gt;[:do-something arg1 arg2]&lt;/code&gt; as a replacement for &lt;code&gt;(do-something! ctx arg1 arg2)&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="authorization-rules-are-so-back"&gt;Authorization rules are so back&lt;/h2&gt;
&lt;p&gt;Biff entered this world as a replacement for Firebase, which I had enjoyed using but left me with
the desire for a regular long-lived clojure backend. Firebase lets your frontend submit arbitrary
transactions from the frontend, and then they're checked against some centralized authorization
rules you define (e.g. “documents in the stuff table can only be edited if the current user's ID is
the same as stuff.user_id”). I implemented a similar thing where you would submit transactions in a
format similar to Firebase's, then I would translate them to XTDB's transaction format and pass a
diff of the database changes to your authorization functions.&lt;/p&gt;
&lt;p&gt;I ended up abandoning the SPA approach altogether for server-side rendering (with htmx), and that
made authorization rules unnecessary since transactions were originating from the backend: I no
longer needed to validate completely arbitrary transactions.&lt;/p&gt;
&lt;p&gt;Once again, coding agents have changed the game. When working on mature codebases, of course we all
read our generated code carefully before submitting a pull request. But when I've got a new app
idea, I want to mostly just vibe code it until I get to the MVP. I'd like to be able to do a light
review just to make sure the structure of the code is reasonable. With authorization rules, you can
carefully review those central rules in the same way you'd carefully review the database schema, and
then you can have confidence that the feature code isn't missing an authorization check. (Of course
you still have to make sure the agent didn't bypass the authorization rules...)&lt;/p&gt;
&lt;p&gt;This is only for writing data. For reading data, I typically have a few Pathom/biff.graph resolvers
that e.g. read an entity ID from the incoming request's path parameters and ensure the user has
access to that entity (like the &lt;code&gt;:param/stuff&lt;/code&gt; resolver alluded to in the example above). Other
related entities are queried as joins against that root entity, so if the authorization check fails,
the rest of the query will fail too. So once again you have a way to put authorization logic in a
central place that can be reused by your feature-specific code.&lt;/p&gt;
&lt;h2 id="oh-yeah-and-datastar"&gt;oh yeah and datastar&lt;/h2&gt;
&lt;p&gt;As mentioned above, Biff uses htmx. &lt;a href="https://biffweb.com/p/understanding-htmx/"&gt;I like server-side
rendering&lt;/a&gt; and I think it's a particularly good fit for
Biff's solo developer focus. htmx however has a critical flaw: it's too popular. It has 47k github
stars—that's &lt;em&gt;half&lt;/em&gt; of what Tailwind has.&lt;/p&gt;
&lt;p&gt;Datastar fixes this problem by being a much younger project—a niche of a niche. There is a
much smaller chance that your colleagues will have heard of it. Datastar also has some smaller but
still tangible benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It has some frontend reactivity built in. With htmx, you typically use another tool like
_hyperscript or Alpine.js to provide interactivity in cases where you really
don't want to wait for a server roundtrip (e.g. a dropdown menu). Datastar has a concept of
&amp;quot;signals&amp;quot; baked in so you don't need a second tool.&lt;/li&gt;
&lt;li&gt;It has a smaller API surface; much of what htmx offers is replaced by &amp;quot;just use out-of-band
swaps.&amp;quot; So it might be easier to learn?&lt;/li&gt;
&lt;li&gt;It works well for &lt;a href="https://andersmurphy.com/2025/04/07/clojure-realtime-collaborative-web-apps-without-clojurescript.html"&gt;fancy CQRS
stuff&lt;/a&gt;
(still on my list of things to try out).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of the changes I've mentioned, this one is the most experimental. I actually haven't even made an
official decision if I really will switch Biff from htmx to Datastar; at this point I'm just making
a prediction that I probably will.&lt;/p&gt;
&lt;p&gt;More broadly I would like to explore how far I can push the server-side rendering model before I
feel it breaking down. e.g. what approach would I use with it to handle forms with 50+ fields and
lots of conditional logic, complex validation logic etc? How about charts? (What I'm getting at:
would I regret asking an LLM to migrate our large codebase at work over to htmx/Datastar?).&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I’d like to give an honorable mention to inline snapshot testing which &lt;a href="https://biffweb.com/p/edn-tests/"&gt;I’ve been excited
about&lt;/a&gt; for a year and a half but now find
unnecessary—counterproductive, even—with coding agents. I had started working on some updates to
my test code so you could do inline snapshot tests in plain .clj files instead of in .edn files
(turns out that tooling support is best when you put your code in files meant for code). But with
coding agents, I’ve found that I don’t &lt;em&gt;want&lt;/em&gt; tests that auto-update when the actual result changes:
it’s too easy for agents to ignore new results that are obviously incorrect. And of course I don’t
care if my coding agent finds updating unit tests to be tedious. So the test-related stuff that Biff
does will be limited to making your application code more pure so you (your agent) can write dumb
&lt;code&gt;(is (= (f x) y))&lt;/code&gt; tests. I might add some structure/patterns for integration tests, though.&lt;/p&gt;
&lt;p&gt;Another change driven by coding agents, not a change to the code but a change to my philosophy: I'm
more interested in smaller projects. As mentioned, my time for working on personal projects has been
extremely limited until a few months ago. I've only ever had a single Biff project at a time that I
have attempted to work on regularly; new projects started after the old one failed. So the primary
use case I designed Biff for was “serious side projects,” applications that may be solo projects now
but will &lt;em&gt;definitely&lt;/em&gt; be bringing in a 6-figure income and fulfilling all your entrepreneurial
desires at... some point. That one project is the only thing I've ever had a chance of having time
for.&lt;/p&gt;
&lt;p&gt;Now I can code up an MVP for something over a weekend without ever sitting down at my desk. I built
an app that helps me find good &lt;a href="https://starwarsunlimited.com/"&gt;Star Wars: Unlimited&lt;/a&gt; decks to play.
I'm building a blogging platform next. After that maybe I'll build a music recommender system. Or a
state legislation tracker/summarizer.&lt;/p&gt;
&lt;p&gt;I'm having a blast. Maybe that will affect design decisions I make down the road? I certainly am
interested in the use case of doing agent-driven development from a mobile device, so maybe expect
something in that area.&lt;/p&gt;
</content><link href="https://biffweb.com/p/biff2/" /><author><name>Jacob O'Bryant</name><uri>https://obryant.dev</uri></author></entry><entry><title type="html">Biff support for XTDB v2 is in pre-release</title><id>https://biffweb.com/p/xtdb2-prerelease/</id><updated>2025-11-09T11:00:00.000Z</updated><content type="html">&lt;p&gt;I've been working on/preparing for migrating Biff to XTDB v2 since that &lt;a href="https://xtdb.com/blog/launching-xtdb-v2"&gt;became generally
available&lt;/a&gt; in June. After investigating the deployment
options and performance characteristics, I've added some XTDB v2 helper functions to the Biff
library (under a new &lt;code&gt;com.biffweb.experimental&lt;/code&gt; namespace) and I've made a version of the starter
project that uses XTDB v2.&lt;/p&gt;
&lt;p&gt;You can create a new XTDB v2 Biff project by running &lt;code&gt;clj -M -e '(load-string (slurp &amp;quot;https://biffweb.com/new.clj&amp;quot;))' -M xtdb2&lt;/code&gt;. See &lt;a href="https://gist.github.com/jacobobryant/7c2853f2fa391d8d30f19f363709ffc5"&gt;this
gist&lt;/a&gt; for a diff between the
old/main starter project and this new one.&lt;/p&gt;
&lt;p&gt;To give you a quick overview of what Biff provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There are &lt;code&gt;use-xtdb2&lt;/code&gt; and &lt;code&gt;use-xtdb2-listener&lt;/code&gt; components, roughly the same as we have already for
XTDB v1.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ctx&lt;/code&gt; map will have a &lt;code&gt;:biff/conn&lt;/code&gt; key in it (a Hikari connection pool object) which you can
pass to &lt;code&gt;xtdb.api/q&lt;/code&gt; to do queries.&lt;/li&gt;
&lt;li&gt;There is no longer a custom Biff transaction format. There is still a lightweight wrapper
function, &lt;code&gt;com.biffweb.experimental/submit-tx&lt;/code&gt;, which will apply Malli validation to any
&lt;code&gt;:put-docs&lt;/code&gt; / &lt;code&gt;:patch-docs&lt;/code&gt; operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There's still plenty of work to do before XTDB v2 support in Biff is officially released and becomes
the default:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Next up, I'm migrating &lt;a href="https://github.com/jacobobryant/yakread"&gt;Yakread&lt;/a&gt; to XTDB v2. This will
help me find any more issues that need to be addressed/make sure that Biff is indeed ready for
XTDB v2.&lt;/li&gt;
&lt;li&gt;After that I need to update a bunch of documentation, including the tutorial project.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since those next two steps will take a while, I wanted to do this &amp;quot;pre-release&amp;quot; for anyone who would
like to get a head start on trying out Biff with XTDB v2. If you do so, let me know whatever
questions/comments you have. Just note that the new functions in Biff's API are still experimental
and might have breaking changes before I do the official release.&lt;/p&gt;
&lt;p&gt;And for anyone who would rather not deal with migrating an existing app, Biff will still support
XTDB v1. It's totally fine to stay on that.&lt;/p&gt;
&lt;p&gt;Finally: I'll be at Clojure/Conj next week, at least if my flight doesn't get canceled. Come say hi.&lt;/p&gt;
</content><link href="https://biffweb.com/p/xtdb2-prerelease/" /><author><name>Jacob O'Bryant</name><uri>https://obryant.dev</uri></author></entry></feed>