biff.graph: Structure Your Clojure Codebase as a Queryable Graph

If you've used Pathom in Clojure, you know the power of querying your data model as a unified graph. But Pathom's query planner adds complexity. Jacob O'Bryant created biff.graph, a lightweight alternative (~600 lines total, with query execution under 200 lines) that trades some query optimization for simplicity. It's now available as com.biffweb/graph {:mvn/version "2.0.0-rc7"}.

Core Concept: Resolvers

biff.graph uses "resolvers" — functions with declared input and output shapes. The query engine stitches them together to fulfill arbitrary queries. Here's a concrete example from the docs:

(require '[com.biffweb.graph :as biff.graph :refer [defresolver]])
(require '[clojure.string :as str])

(defresolver rss-feed
  {:input  [:rss-feed/id]
   :output [:rss-feed/title]}
  [_ctx {:rss-feed/keys [id]}]
  (get {1 {:rss-feed/title "My Blog"}}
       id))

(defresolver post
  {:input  [:post/id]
   :output [:post/title
            :post/url
            {:post/rss-feed [:rss-feed/id]}]}
  [_ctx {:post/keys [id]}]
  (get {2 {:post/url      "https://example.com/my-post"
           :post/title    "My Post 🎅"
           :post/rss-feed {:rss-feed/id 1}}
        3 {:post/title    "My Other Post 🎅"
           :post/rss-feed {:rss-feed/id 1}}}
       id))

(defn remove-emojis [s] (str/replace s #"🎅" ""))

(defresolver clean-post-title
  {:input  [:post/title]
   :output [:post/clean-title]}
  [_ctx {:post/keys [title]}]
  {:post/clean-title (-> title remove-emojis str/trim)})

Then query:

(def resolvers [rss-feed post clean-post-title])
(def ctx (biff.graph/new-ctx resolvers))

(biff.graph/query ctx
  [{:post/id 2} {:post/id 3}]
  [:post/id :post/clean-title
   [:? :post/url]
   {:post/rss-feed [:rss-feed/title]}])
;; =>
[{:post/id 2, :post/clean-title "My Post", :post/url "https://example.com/my-post", :post/rss-feed {:rss-feed/title "My Blog"}}
 {:post/id 3, :post/clean-title "My Other Post", :post/rss-feed {:rss-feed/title "My Blog"}}]

Notice how clean-post-title derives a new attribute from :post/title, and the query seamlessly includes it alongside database fields. Optional attributes (:? :post/url) and joins ({:post/rss-feed [:rss-feed/title]}) work as expected.

Design Philosophy

O'Bryant explains: "biff.graph is basically a lightweight version of Pathom. It implements only a subset of Pathom's functionality with the intention of being easier to understand." The key tradeoff: no query planning step. Pathom optimizes the order of resolver execution; biff.graph resolves eagerly. This can be less efficient for complex queries, but batch resolvers and caching mitigate that.

For small to medium projects, this tradeoff is worthwhile. The library ships with a :batch true option for resolvers, allowing database queries to fetch multiple entities at once. The upcoming biff.sqlite library will include autogenerated batch resolvers for SQLite.

Debugging and Testing

Exceptions thrown during query include a :biff.graph/trace key showing the exact graph traversal location. You can use this to build a minimal reproducer. Testing resolvers is straightforward:

(deftest test-my-resolver
  (is (= ((:biff.graph/resolve-fn my-resolver)
          {:biff.graph/input {:user/id 1}})
         ...)))

Integration with Biff

biff.graph is part of the Biff framework. Include (com.biffweb.graph/module) in your modules, then add resolvers under :biff.graph/resolvers. It also integrates with biff.fx for effectful resolvers (e.g., external API calls) using state machines.

Tips from the Author

  • Organize resolvers in a model/ directory, each namespace exposing a module with :biff.graph/resolvers.
  • Resolvers can return Hiccup or functions returning Hiccup for reusable UI components tied to your data model.
  • Write "global" resolvers without input queries for background jobs or session data (e.g., {:session/user [:user/id]}).
  • For authorization, use "params" resolvers that take entity IDs from request params, check permissions, and return the entity or throw an exception.

Bottom Line

biff.graph is a pragmatic choice for Clojure developers who want the graph query pattern without Pathom's learning curve. Version 2.0.0-rc7 is stable enough to try now, though breaking changes are possible until the full Biff 2 release. If you're building a new Clojure app or refactoring a tangled codebase, give it a shot.