# Biff: a Clojure web framework that is really a set of libraries

> Biff ships as a composition of libraries, from biff.core to biff.admin, each documented on its own and each usable on its own. The keyword namespaces are the documentation system, and version 2.0 arrived in September 2026 after a year of silence.

**jacobobryant/biff** — A Clojure web framework for solo developers.

- Repository: https://github.com/jacobobryant/biff
- Website: https://biffweb.com
- Stars: 1,120 · Forks: 56
- Language: Clojure
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/jacobobryant-biff

## A framework you take apart as the project grows

Biff is a full-stack Clojure web framework, and the way it describes itself explains its shape. It speeds up web development by providing as much functionality as possible out of the box while making it easy to swap out, modify or remove parts as your project grows. It is built with solo developers in mind and is quite opinionated in its approach to serving that use case, and it has also been influenced by the author's experience working in enterprise SaaS, so it is perfectly capable of keeping large codebases maintainable.

The design decision that follows from that is that Biff is a composition of libraries. Some of them are wrapper libraries that make other tools easy to integrate into a Biff project, and some are entirely new creations. The important property is that each library is documented individually and is meant to be usable on its own, which is the opposite of the usual framework bargain where the parts are only useful inside the whole.

The list is the framework. `biff.core` provides the interfaces and the code that connect all the other Biff libraries, and it is called the framework part of Biff. `biff.config` is a light wrapper around Aero. `biff.fx` is an approach to keeping your application logic pure, with the effects separated out. `biff.graph` is about structuring your data model as a queryable graph, and it is inspired by Pathom. `biff.sqlite` integrates SQLite with the other parts and adds support for rich data types. `biff.ring` integrates Ring, Reitit and Jetty and includes a default middleware stack. `biff.datastar` is lightweight server-side rendering with Datastar. `biff.background` is in-memory job queues and scheduled tasks. `biff.authenticate` is signin-via-email functionality including a default signin form. `biff.admin` is an admin dashboard with usage and performance metrics, a user directory and email alerting. `biff.run` is a lightweight clj-based task runner, and `biff.tasks` is the collection of default biff.run tasks, which is what you see when you run the help command.

Two more sit alongside. `biff.defaults` is a wrapper library that includes the others, except for biff.run and biff.tasks, which are development-only, and `biff.xtdb` is an alternative to biff.sqlite.

The project is MIT licensed, the primary language is Clojure, the homepage is `biffweb.com`, and the last push was on 2026-10-10.

## Namespaced keywords are the documentation system

Clojure has a convention of namespacing keywords, and Biff leans on it harder than most projects, with a consequence that is easy to miss and useful once you know it.

Each library's README links to a schema reference, and that document describes the keywords owned by that library, all of which are namespaced with the library name. The example given is the biff.core schema reference, which specifies keys such as `:biff.core/id`, `:biff.core/init` and `:biff.core/start`.

So whenever you see a keyword in a Biff codebase, you can tell immediately which library owns it, and which schema reference document to read to find out more. That is a documentation strategy built on the language rather than on a search box, and for a framework that is made of fourteen libraries it scales better than a manual would.

The other side of the convention is stated plainly: other documentation assumes you are familiar with the schema reference and therefore does not always describe what the keys are. That is a real trade-off, and it is the kind of thing that reads as a strength in the first week and as an obstacle in the first month for someone who has to learn the framework from the outside.

The repository structure supports the same idea. The `libs/` directory holds the libraries, each with its own README and its own schema document, and the root carries `deps.edn`, `tests.edn`, a `deps/` directory for the tooling dependencies, a `dev/` directory, a `docs/` directory and a `RELEASES/` directory that holds the release notes.

## Getting started is three commands and a sign-in code

The requirements are Java 21 or higher and Clojure, and the starter project is the way in.

```clojure
git clone https://github.com/jacobobryant/biff-starter my-project
cd my-project
clj -M:run dev
```

The first time the `dev` command runs, it prompts for a namespace for the new project. After the application starts, you go to `localhost:8080` and sign in, and the sign-in code is printed to the console.

That last detail is a small piece of design worth noticing. The application has authentication from the first run, there is no account to create in a database, and the credential is in the terminal that started the server. It is the signin-via-email flow of `biff.authenticate`, with its default signin form, running against a console-printed code, so the first session needs no setup at all.

The other property of the dev command is the evaluation loop. Changes are evaluated whenever you save a file, which for a Clojure project means the usual REPL-driven workflow is what you get, with the task runner deciding when to reload.

The task runner itself is `biff.run`, and the commands it offers are the `biff.tasks` collection. `clj -M:run -h` lists them, which is the first thing to run in a new project.

The documentation adds two resources for going further: a video tutorial on deploying an app with biff.tasks, and a background post on the design of Biff 2.0, which is the document to read before choosing the framework rather than after.

## biff.fx is the opinionated part

Of the fourteen libraries, `biff.fx` is the one that most shapes an application, and it is the one the README calls out in the guide.

It is an approach to keeping your application logic pure, with the effects separated out, and the starter app uses biff.fx to define its Ring handlers. That is a much stronger statement than it looks. In a typical Clojure web application, a handler function reaches for the database, sends an email and reads a request map, all in one body. With biff.fx, the handler is written as a pure function of its inputs, and the effects it wants to perform are declared rather than performed, so the same handler can be called in a test with no database and no network.

The reason to care is that the alternative is a codebase where every behaviour is only reachable by running the whole application, and where the tests are integration tests. Biff is opinionated about this, and the README says so directly, which is the point of the sentence about the framework being quite opinionated in its approach to serving the solo developer case.

The rest of the stack supports the same idea rather than fighting it. `biff.graph` gives a queryable data model, inspired by Pathom, so that the data a page needs can be described as a graph query rather than assembled by hand in every handler. `biff.sqlite` is the default store and it adds support for rich data types, which matters when a graph is the shape of the data. `biff.ring` supplies the HTTP layer with a default middleware stack, so an application does not have to assemble its own.

The combination of a pure handler, a graph query and a default middleware stack is what makes a Biff application small. A request arrives, a graph is queried, the result is handed to a template or a Datastar response, and the effects that need the outside world are declared and run by the framework.

## Rendering, queues, authentication and the admin dashboard

The remaining libraries cover the parts of a web application that every product ends up needing and nobody wants to write.

Rendering is `biff.datastar`, lightweight server-side rendering with Datastar. The choice of renderer is what separates Biff from most Clojure web frameworks, and it is worth being explicit about: this is server-rendered HTML with a small amount of interactivity, driven by a hypermedia-oriented approach, rather than a client-side component application. The documentation has a page on understanding Datastar for that reason, and a separate video tutorial and post on the 2.0 design.

Background work is `biff.background`, in-memory job queues and scheduled tasks. In-memory means the jobs are lost when the process restarts, which is the right default for a solo developer with a single process and the wrong one for a service that must not drop work.

Authentication is `biff.authenticate`, signin-via-email with a default form, and administration is `biff.admin`, a dashboard with usage and performance metrics, a user directory and email alerting. The admin dashboard is the feature that most often decides the framework question for a solo developer, because it is the screen they would otherwise write themselves, and it comes with the metrics that a small service needs.

On the data side, `biff.xtdb` is an alternative to `biff.sqlite`, so the storage decision is a library choice rather than a rewrite. The documentation also has a guide on writing a Biff database adapter for anything else, and the repository keeps a `dev/` directory for the development tooling, a `tests.edn` for the test aliases, and a `.cljfmt.edn` and a `.clj-kondo/` directory for formatting and linting the Clojure sources.

## Version 2.0, and a year between the releases

The release history is worth reading, because the gap between versions is the story.

Version 1.8.10 was published on 2024-04-16, 1.9.0 on 2025-09-14, and 2.0.0 on 2026-09-10. So there was a year and a half between 1.8.10 and 1.9.0, and then a year between 1.9.0 and 2.0.0.

That is the rhythm of a framework maintained by one person alongside a job, and the README is upfront about it, with a newsletter for announcements, a Clojurians Slack channel for questions and a GitHub sponsors page. The post called the Biff 2.0 sneak peak is linked from the content library as background information on the design of Biff, and there is a separate document on migrating from Biff 1.0, which the README recommends reading for the eponymous task.

The `RELEASES/` directory in the repository holds the release notes, so the history is versioned alongside the code rather than living only in a changelog.

One section of the README is about how the author uses large language models, and it is unusually specific. There are no hard rules, but typically the author uses LLMs to generate a rough draft of pretty much all the code, reading and often editing it thoroughly. Tests are the exception, glanced over but rarely edited. First drafts of documentation are written by hand, with LLMs used to help keep the docs up to date. The summary is moderately enthusiastic about using LLMs for code and pretty skeptical about using them for documentation, and the README links an essay on the subject, 2x, not 10x, about coding with LLMs in 2026.

The conclusion drawn in the README is that Biff is intended to be a good framework for both manual and LLM-assisted development, which is a claim about the shape of the code rather than about the tooling: small pure functions, an explicit library structure and a schema document per library are all things a code-generating model has a better chance of getting right than a large implicit framework.

## Where Biff is the wrong tool

The trade-offs follow from the design, and they are the trade-offs of an opinionated framework that a single person maintains.

The first is the renderer. Biff renders with Datastar, which is a deliberate choice for a small server-rendered application and a poor fit for a product whose frontend is a React application. The moment the frontend is a component app, the value of biff.datastar goes to zero and the rest of the framework is a set of Clojure libraries around a Ring handler, which is a perfectly good thing to have and not a reason to adopt a framework.

The second is the storage default. `biff.sqlite` with rich data types is the right default for a solo developer's application and the wrong one for a service that has to write from several processes or run on more than one machine. `biff.xtdb` is there as an alternative and the database adapter guide exists, so the escape route is documented, but the decision is much cheaper to make at the start than to unwind.

The third is the opinionatedness. The README says the framework is quite opinionated in its approach to serving the solo developer use case, and it is right to say so. Opinionated means the defaults are good for one shape of application and wrong for others, and a team that has already chosen its shape, its renderer and its state management is paying for a framework that will not be adjusted to fit.

The fourth is the maintenance shape. A framework maintained by one person, with a year and a half between the 1.8 and 1.9 lines and a 2.0 that arrived a year later, is a fine thing to use and a risky thing to depend on. The `biff.defaults` alias library and the fact that every library is usable on its own are the mitigation, and they are a real one: an application that uses biff.core, biff.ring and biff.sqlite directly has little of Biff to keep up with.

The alternative for a team that wants the libraries without the framework is exactly that, and the README says each library is meant to be usable on its own. A Clojure application built on plain Ring and Reitit, with Aero for configuration and an XTDB or SQLite store chosen deliberately, is less code to own and more code to write, and for a team that already knows Clojure it is often the cheaper option. The difference in approach is that Biff is a complete answer with strong opinions, and the alternative is a set of parts with none.

## Conclusion

Biff fits a solo developer or a small team that wants a working web application, with authentication, an admin dashboard, a database and a job queue included, and that expects to remove or replace the parts it does not need later, since the framework is built to be taken apart. It is the wrong choice for a team that wants a conventional Clojure stack with a framework it does not have to argue with, and it is the wrong choice for a product whose frontend is a React application rather than server-rendered Datastar pages. Before adopting it, read the Biff 2.0 background post and the migration guide from 1.0, because the major version changed the shape of the project, and look at `biff.sqlite` and `biff.xtdb` side by side, because the default storage is the decision that is hardest to reverse later.

## FAQ

### How do I start a new Biff project?

With Java 21 or newer and Clojure installed, clone the starter with `git clone https://github.com/jacobobryant/biff-starter my-project`, change into the directory and run `clj -M:run dev`. The first run asks for a namespace, the app is then on localhost:8080, and the sign-in code is printed to the console.

### What is biff.fx in the Biff framework?

It is an approach to keeping your application logic pure, with the effects separated out, and the starter app uses biff.fx to define its Ring handlers. It is the opinionated centre of the framework, since it changes how every handler is written.

### Can I use a database other than SQLite with Biff?

Yes. biff.xtdb is an alternative to biff.sqlite, and the documentation includes a guide on how to write a Biff database adapter if you want to use something else entirely.

### How is the Biff documentation organised?

Each library README links to a schema reference that lists the keywords that library owns, all namespaced with the library name, for example :biff.core/id, :biff.core/init and :biff.core/start. Seeing a keyword in the code therefore tells you which library owns it and which document describes it.

## Sources

- [jacobobryant/biff on GitHub](https://github.com/jacobobryant/biff)
- [License: MIT](https://github.com/jacobobryant/biff/blob/master/LICENSE)
- [Project website](https://biffweb.com)
- [README](https://github.com/jacobobryant/biff/blob/master/README.md)
- [Releases](https://github.com/jacobobryant/biff/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jacobobryant-biff
