Liking cljdoc? Tell your friends :D

Wagoe Framework

CI Clojars Project cljdoc License: EPL-2.0

Wagoe is a batteries-included Clojure web framework that enforces the Functional Core / Imperative Shell (FC/IS) pattern: pure business logic in core/, side effects in shell/, and clean interfaces through ports.clj protocols.


Why Wagoe?

For developers: 31 independently-publishable libraries on Clojars — use just wagoe-core for validation utilities, or go full-stack with JWT + MFA auth, auto-generated CRUD UIs, background jobs, multi-tenancy, real-time WebSockets, and more. Every library follows the same FC/IS structure, making any Wagoe codebase instantly familiar.

Ship faster: The scaffolder generates fully structured modules (entity + routes + tests) in seconds. The admin UI auto-generates CRUD interfaces from your schema — no manual forms. Built-in observability, RFC 5988 pagination, and declarative interceptors mean you write business logic, not plumbing. AI tooling (bb scaffold ai, bb ai gen-tests, bb ai sql) handles the repetitive parts.

Ship with confidence: Reference deployment configs (systemd, nginx, Fly.io, Render), an OWASP-aligned security checklist, scaling guides, health check endpoints, and zero-downtime migration patterns.

Zero lock-in: Each library is a standard deps.edn dependency. Swap what doesn't fit.


Install

Install the Wagoe CLI — it handles all prerequisites (JVM, Clojure CLI, Babashka, bbin) automatically:

curl -fsSL https://get.wagoe.org | bash

Fallback if get.wagoe.org is unavailable:

curl -fsSL https://raw.githubusercontent.com/wagoebv/wagoe/main/scripts/install.sh | bash

Supports macOS, Debian/Ubuntu, Arch Linux, and WSL2.

Quick Start

# 1. Create a new project
wagoe new my-app
cd my-app

# 2. Add optional modules (e.g. payments, cache, search)
wagoe add payments
wagoe list modules    # see all 19 optional modules

# 3. Run database migrations
clojure -M:migrate up

# 4. Start the REPL (headless nREPL server on port 7888)
export JWT_SECRET="change-me-dev-secret-min-32-chars"
clojure -M:repl-clj

Connect your editor (or the Wagoe MCP server) to the nREPL port, then eval:

(go)    ; start the system — http://localhost:3000
(reset) ; reload changed namespaces and restart
(halt)  ; stop the system

You get: SQLite database (zero-config, and your data survives a restart), HTTP server on port 3000, a complete Integrant system, and REPL-driven development.

When something is wrong

wagoe doctor      # checks the project and prints the one thing to fix
bb guide          # topic guides: scaffold, testing, database, fcis, config
bb guide error BND-201   # what an error code means and how to fix it

wagoe doctor runs the environment, config, command and project-setup checks in one pass and ends with a single next action. The bb tasks behind it still exist and CI calls them directly; you do not need to know which is which.


AI-Native Development (Claude Code & Agentic CLIs)

Projects created with wagoe new are agent-ready out of the box: they include a CLAUDE.md, an AGENTS.md, and a Claude Code skill at .claude/skills/wagoe/SKILL.md that teaches the agent to use Wagoe's scaffolder and AI tooling instead of hand-writing boilerplate. Open Claude Code in a fresh project and ask:

add a product module with name, price, and stock

The agent will reach for bb scaffold and generate a complete FC/IS module with tests and migrations.

For existing projects (or to get updates without regenerating), install the plugin from this repo's marketplace:

/plugin marketplace add wagoebv/wagoe
/plugin install wagoe@wagoe

See claude-plugin/README.md for details.


Documentation

ResourceDescription
DocumentationArchitecture guides, tutorials, library reference (Antora)
AGENTS.mdCommands, conventions, common pitfalls, debugging
dev-docs/adr/Architecture Decision Records
Deployment Patternssystemd, nginx, Fly.io, Render reference configs
Migrations GuideZero-downtime schema change patterns
Security ChecklistOWASP Top 10 aligned production checklist
Scaling GuideJVM, HikariCP, Redis, and HTTP tuning

Each library also has its own AGENTS.md with library-specific documentation.


Libraries

Wagoe is a monorepo of 31 independently publishable libraries, application and development tooling alike:

LibraryDescription
configAero-based configuration loading and typed accessors
coreFoundation: validation, utilities, interceptor pipeline, feature flags
observabilityLogging, metrics, error reporting (Datadog, Sentry)
platformHTTP, database, CLI infrastructure
userAuthentication, authorization, MFA, session management
adminAuto-generated CRUD admin UI (Hiccup + HTMX)
storageFile storage: local filesystem and S3
scaffolderInteractive module code generator
cacheDistributed caching: Redis and in-memory
jobsBackground job processing with retry logic
emailEmail delivery: SMTP, async, jobs integration
tenantMulti-tenancy with PostgreSQL schema-per-tenant isolation
realtimeWebSocket / SSE for real-time features
externalExternal service adapters: Twilio, IMAP
paymentsPayment provider abstraction: Stripe, Mollie, Mock
reportsPDF, Excel, and Word (DOCX) generation via defreport
calendarRecurring events, iCal export/import, conflict detection
workflowDeclarative state machine workflows with audit trail
searchFull-text search: PostgreSQL FTS with LIKE fallback for H2/SQLite
geoGeocoding (OSM/Google/Mapbox), DB cache, Haversine distance
aiFramework-aware AI tooling: NL scaffolding, error explainer, test generator, SQL copilot, docs wizard
i18nMarker-based internationalisation with translation catalogues
pushMulti-platform push notifications: FCM (Firebase) + APNs (Apple)
audienceRule-based audience segmentation with SQL + predicate pipeline
ui-styleShared UI style bundles, design tokens, CSS/JS assets
shared-uiShared Hiccup primitives: forms, tables, layouts, modals, icons
devtoolsDev-only: error pipeline, dev dashboard, REPL power tools, guidance engine
toolsDev-only: deploy, doctor, setup, scaffolder integration, quality checks
wagoe-cliThe wagoe command: new, add, list modules
wagoe-mcpMCP server over stdio for editor agents

Architecture

Wagoe enforces the Functional Core / Imperative Shell pattern throughout:

libs/{library}/src/wagoe/{library}/
├── core/       # Pure functions only — no I/O, no logging, no exceptions
├── shell/      # All side effects: persistence, services, HTTP handlers
├── ports.clj   # Protocol definitions (interfaces for dependency injection)
└── schema.clj  # Malli validation schemas

Dependency rules (strictly enforced):

  • Shell → Core (allowed)
  • Core → Ports (allowed)
  • Core → Shell (never — this violates FC/IS)

This keeps business logic fast to test (no mocks needed), easy to reason about, and safe to refactor.

Case conventions — a frequent source of bugs:

WagoeConvention
Clojure codekebab-case (:password-hash, :created-at)
Databasesnake_case
API (JSON)camelCase

Use wagoe.core.utils.case-conversion for conversions. Never convert manually.


Essential Commands

# Testing (Kaocha, default test profile uses H2 in-memory DB)
clojure -M:test                                          # All tests
clojure -M:test :core                                    # Single library
clojure -M:test --focus-meta :unit                       # Unit tests only
clojure -M:test --focus-meta :integration                # Integration tests only
clojure -M:test --watch :core                            # Watch mode
JWT_SECRET="dev-secret-at-least-32-characters-long" WAG_ENV=test clojure -M:test

# Linting
clojure -M:clj-kondo --lint src test libs/*/src libs/*/test

# REPL (nREPL on port 7888)
clojure -M:repl-clj
# In REPL: (go) | (reset) | (halt)

# Build
clojure -T:build clean && clojure -T:build uber

# Database migrations
clojure -M:migrate up

# Scaffolding
bb scaffold   # Interactive module wizard
bb scaffold ai "product module with name, price, stock"  # NL scaffolding via AI (interactive confirm)
bb scaffold ai "product module with name, price, stock" --yes  # Non-interactive generation

# AI tooling
bb ai explain --file stacktrace.txt  # Explain error
bb ai gen-tests libs/user/src/wagoe/user/core/validation.clj  # Generate tests
bb ai sql "find active users with orders in last 7 days"          # HoneySQL from NL
bb ai docs --module libs/user --type agents                       # Generate AGENTS.md

# Operations
wagoe doctor                       # One diagnostic pass, one next action (start here)
bb doctor                          # Just the config checks
bb doctor --env all --ci           # Check all envs, exit non-zero (CI)
bb guide next                      # What to do next in this project
bb setup                           # Interactive config setup wizard
bb setup ai "PostgreSQL with Stripe payments"  # AI-powered config setup
bb deploy --all                    # Deploy all libraries to Clojars
bb deploy --missing                # Deploy only unpublished libraries

See AGENTS.md for the complete command reference, common pitfalls, and debugging strategies.

Running The Full Suite Against PostgreSQL

The default test profile runs against in-memory H2. To run against PostgreSQL:

  1. Start a PostgreSQL instance matching the credentials in resources/conf/test/config.edn.
  2. In resources/conf/test/config.edn, move :wagoe/postgresql from :inactive to :active and move :wagoe/h2 out of :active.
  3. Run:
WAG_ENV=test JWT_SECRET="dev-secret-at-least-32-characters-long" clojure -M:migrate up
WAG_ENV=test JWT_SECRET="dev-secret-at-least-32-characters-long" clojure -M:test
  1. Revert resources/conf/test/config.edn after the run.

Quality Gates

Six automated safeguards run in CI to catch regressions early. The FC/IS check also runs as a pre-commit hook.

bb check:fcis                    # Core namespaces must not import shell, I/O, logging, or DB
bb check:placeholder-tests       # No (is true) placeholders masking missing coverage
bb check:deps                    # Library dependency direction + cycle detection
clojure -M:test --focus-meta :security  # Error mapping, CSRF, XSS, SQL parameterization

See ADR-021 (FC/IS rules) and ADR-022 (error handling conventions) for rationale.


Releasing a New Version

The version appears in 104 locations — 59 in source, 45 in documentation. bb check:versions is the list, and bb bump is the way to change it.

1. Bump:

bb bump 1.0.0-beta-6 --dry-run   # list what would change
bb bump 1.0.0-beta-6

It rewrites exactly the locations check:versions discovers and nothing else, prints a git diff --stat, and finishes by verifying the result against the version it just wrote. Re-running it is a no-op.

Give the plain version, not the tag: 1.0.0-beta-6, not v1.0.0-beta-6. It refuses a leading v rather than writing it into 104 places, where every location would then agree and the check would pass on it.

2. Verify and commit:

bb check
git add -A && git commit -m "bump library suite version to 1.0.0-beta-6"

The full bb check — not --quick, which skips check:versions. That gate is the one that knows every location and fails when they disagree.

3. Run the pre-release gate:

The nightly first-run matrix doubles as the pre-release check — that is what its workflow_dispatch is for. Run it against the commit you are about to tag rather than trusting last night's run to describe today's tree.

gh workflow run first-run-matrix.yml -f reason="pre-release gate for 1.0.0-beta-6"

4. Tag. The tag is the release:

git push
git tag -a "1.0.0-beta-6" -m "Release 1.0.0-beta-6"
git push --tags

Pushing an unprefixed semver tag fires .github/workflows/publish.yml, which waits for a maintainer to approve the release environment and then does the rest: builds every library from the tagged commit in publish order, refuses if the tag disagrees with the source version or if CI did not pass on that exact commit, deploys the artifacts not already on Clojars, and creates the GitHub release.

So there is nothing to run by hand afterwards. In particular:

  • Do not gh release create — the workflow creates the release, and doing both collides.
  • Do not bb deploy --all — the workflow has already deployed. Re-deploying a published artifact 409s and aborts the run, which is why the workflow itself uses --missing.

bb deploy by hand is the fallback for when the workflow cannot run, not a step of the normal release. patch-catalogue-version! keeps modules-catalogue.edn in sync after each successful deploy either way.

What bb bump deliberately leaves alone: CHANGELOG.md and the ADRs (historical — they record what was true when written), docs/superpowers/ (dated design records), and docs/modules/ROOT/pages/stability.adoc, whose subject is the old version numbers. Also draft/pre-releases on GitHub — install.sh uses /releases/latest, which only returns published releases.

Until BOU-316 this step was a global find | xargs sed. It set OLD and NEW to the same string, so a copy-paste run rewrote nothing and reported success — and the verification was grep -r "$OLD", which then found nothing and agreed. It was also macOS-only (sed -i '') and rewrote every occurrence of the version string, including third-party pins that happened to match.


Using Individual Libraries

;; Validation utilities only
{:deps {com.wagoe/wagoe-core {:mvn/version "1.0.0-beta-6"}}}

;; Full web application stack
{:deps {com.wagoe/wagoe-platform {:mvn/version "1.0.0-beta-6"}
        com.wagoe/wagoe-user     {:mvn/version "1.0.0-beta-6"}
        com.wagoe/wagoe-admin    {:mvn/version "1.0.0-beta-6"}}}

Deployment

Build the uberjar and deploy to any platform:

clojure -T:build clean && clojure -T:build uber
WAG_ENV=prod java -jar target/wagoe-*-standalone.jar

Reference configurations are provided under resources/deploy/:

TemplateDescription
systemdService unit + environment file for bare-metal/VM
nginxReverse proxy with TLS, WebSocket support, static caching
Fly.ioAuto-scaling, health checks, Amsterdam region
RenderBlueprint with managed PostgreSQL

Health endpoints: /health (liveness), /health/ready (readiness with DB/cache checks), /health/live (container orchestrator).

See the Deployment Patterns guide for full instructions.


Website

https://wagoe.org


License

Copyright 2024–2026 Thijs Creemers.

Distributed under the Eclipse Public License 2.0.

Can you improve this documentation? These fine people already did:
Thijs Creemers & thijscreemers
Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close