Show HN: Snowbin – A Mindmap-Based Social Platform Built with Lisp
2 points
1 hour ago
| 5 comments
| snowbin.net
| HN
I built a mindmap-based discussion platform, but what made the project truly interesting was not the UI — it was building the backend in Common Lisp.

Inspired by Paul Graham and his book The Hacker and the Painter, I wanted to experience what it feels like to design a system in a language built for abstraction rather than convention.

Instead of relying on a large framework, I combined small libraries and built a minimal internal structure myself.

rrepo
1 hour ago
[-]
Why Common Lisp?

In many ecosystems, frameworks define architecture for you. In Lisp, you define your own abstractions. The biggest difference was macros. Rather than repeating patterns such as JSON parsing,Error normalization, API response formatting and Exception mapping. I abstracted them into macros that standardized the flow across the entire backend. This allowed controllers to focus purely on business logic. For example, API responses are wrapped automatically (defmacro with-api-response (result) `(let ((res ,result)) (cond ((null res) `(200 (:content-type "application/json") (,(jonathan:to-json '(:status "success" :data ())))))

       ((eq res :invalid)
        `(400 (:content-type "application/json")
              (,(jonathan:to-json '(:status "error")))))

       (t
        `(200 (:content-type "application/json")
              (,(jonathan:to-json
                 (list :status "success" :data res))))))))
Instead of writing error handling and serialization logic repeatedly, I could define the contract once.
reply
rrepo
1 hour ago
[-]
Hot Reload Without Restarting

I also implemented a lightweight file reload mechanism. Instead of restarting the server on every change, the system checks file timestamps on access and reloads only what changed. This gave me a workflow similar to frontend hot reloading, but implemented manually.

(defun reload-dev () (dolist (file '("controllers/controllers-package")) (let* ((pathname (asdf:system-relative-pathname "mindmap" (format nil "~A.lisp" file))) (new-time (file-write-date pathname)) (old-time (gethash file file-mod-times 0))) (when (> new-time old-time) (load pathname) (setf (gethash file file-mod-times) new-time))))) Because everything is just code and macros, the system remains transparent and hackable.

reply
rrepo
1 hour ago
[-]
What Lisp Changed What surprised me most wasn’t performance. It was cohesion.

The combination of: Macros for abstraction, Symbolic error flow, Minimal external, framework constraints and Explicit layering. resulted in a backend that feels small, consistent, and expressive. It reminded me somewhat of Go’s explicit design philosophy, but with a far more powerful metaprogramming layer. Lisp did not give me more libraries. It gave me more control over structure.

reply
rrepo
1 hour ago
[-]
Standardizing Error Flow Another macro normalizes all exceptions into a single symbolic state:

(defmacro with-invalid (&body body) `(handler-case (progn ,@body) (error (e) (format error-output "ERROR: ~A~%" e) :invalid)))

Every layer speaks the same language: either valid data or :invalid. This made the layering extremely clear and reduced cognitive load.

reply
rrepo
1 hour ago
[-]
Closing Thoughts Building the server, reload mechanism, and API abstractions from scratch was far more educational than using a prebuilt framework. The codebase is currently closed, but I’m considering open-sourcing it. If you’re interested in using Common Lisp in a modern web backend, I’d love to hear your thoughts.
reply