This library was born out of a desire to have a basic understanding of my web projects’ visitors and a lack of desire to set up anything.
clj-simple-stats, as the name suggests, is trivial to set up:
- Add
io.github.tonsky/clj-simple-stats {:mvn/version "1.2.0"}todeps.edn. - Add
clj-simple-stats.core/wrap-statsanywhere in your middleware stack.
That’s it! There’s no step 3.
- All the visits that go through that middleware will be counted automatically.
- Data will be stored to
clj_simple_stats.duckdbin the current dir. - You’ll get a dashboard at
/statsthat looks like this:
Couple of highlights:
- Statistic is fully server-side, no JS is used.
- You can narrow down stats per URL, query, referrer, user agent, etc. (click on the looking glass icon).
- We try our best to distinguish between human visits, RSS readers, and scrapers.
- The goal is to understand how many people see your site, not how many requests.
- Only responses with status 200 and content-type:
text/html,application/atom+xmlorapplication/rss+xmlare counted - Only two external dependencies: DuckDB and Ring (which you probably have anyway).
These default values are used; feel free to override:
(wrap-stats handler
{:db-path "clj_simple_stats.duckdb"
:uri "/stats"
:dash-perms-fn (fn [req] true)})wrap-stats is a composition of wrap-collect-stats and wrap-render-stats, which you can use separately as well.
Finally, wrap-render-stats middleware checks if (= (:uri opts) (:uri req)) and then calls render-stats handler. If you prefer to use your own router, feel free to use render-stats handler directly.
Q: Will this work with static websites? E.g. served fully by Nginx? A: Unfortunately, no. This is designed to sit in your Ring middleware stack.
Q: How can I select time intervals? A: Select whole years at the top of the page. Select whole months by clicking their labels in the graph. Everything else only by modifying URL params. Might change later.
Copyright © 2025 Nikita Prokopov
Licensed under MIT.
