Liking cljdoc? Tell your friends :D

Boundary Framework

Boundary 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 Boundary?

For developers: 24 independently-publishable libraries on Clojars — use just boundary-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 Boundary 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 Boundary CLI — it handles all prerequisites (JVM, Clojure CLI, Babashka, bbin) automatically:

curl -fsSL https://get.boundary-app.org | bash

Fallback if get.boundary-app.org is unavailable:

curl -fsSL https://raw.githubusercontent.com/thijs-creemers/boundary/main/scripts/install.sh | bash

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

Quick Start

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

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

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

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

In the REPL:

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

You get: H2 in-memory database (zero-config), HTTP server on port 3000, a complete Integrant system, and REPL-driven development.


AI-Native Development (Claude Code & Agentic CLIs)

Projects created with boundary new are agent-ready out of the box: they include a CLAUDE.md, an AGENTS.md, and a Claude Code skill at .claude/skills/boundary/SKILL.md that teaches the agent to use Boundary'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 thijs-creemers/boundary
/plugin install boundary@boundary

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

Boundary is a monorepo of 24 independently publishable libraries plus development tooling:

LibraryDescription
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
devtoolsDev-only: error pipeline, dev dashboard, REPL power tools, guidance engine
toolsDev-only: deploy, doctor, setup, scaffolder integration, quality checks

Architecture

Boundary enforces the Functional Core / Imperative Shell pattern throughout:

libs/{library}/src/boundary/{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:

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

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


Essential Commands

# Testing (Kaocha, default test profile uses H2 in-memory DB)
clojure -M:test:db/h2                                          # All tests
clojure -M:test:db/h2 :core                                    # Single library
clojure -M:test:db/h2 --focus-meta :unit                       # Unit tests only
clojure -M:test:db/h2 --focus-meta :integration                # Integration tests only
clojure -M:test:db/h2 --watch :core                            # Watch mode
JWT_SECRET="dev-secret-32-chars-minimum" BND_ENV=test clojure -M:test:db/h2

# 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/boundary/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
bb doctor                          # Validate config for common mistakes
bb doctor --env all --ci           # Check all envs, exit non-zero (CI)
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 :boundary/postgresql from :inactive to :active and move :boundary/h2 out of :active.
  3. Run:
BND_ENV=test JWT_SECRET="dev-secret-32-chars-minimum" clojure -M:migrate up
BND_ENV=test JWT_SECRET="dev-secret-32-chars-minimum" clojure -M:test:db/h2
  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:db/h2 --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

Version appears in 26+ files — use these steps to bump consistently.

1. Replace the version string everywhere (all .clj, .edn, and .md files):

OLD="1.0.1-alpha-31"
NEW="1.0.1-alpha-31"   # example

# Source and config files
find . \( -name "*.clj" -o -name "*.edn" \) \
  ! -path "*/docs/superpowers/*" ! -path "*/.git/*" \
  -exec grep -l "$OLD" {} \; | xargs sed -i '' "s/$OLD/$NEW/g"

# Documentation
find . -name "*.md" \
  ! -path "*/CHANGELOG.md" ! -path "*/docs/superpowers/*" ! -path "*/.git/*" \
  -exec grep -l "$OLD" {} \; | xargs sed -i '' "s/$OLD/$NEW/g"

On Linux, use sed -i instead of sed -i ''.

2. Verify, commit, tag, and release:

# Verify — must print nothing
grep -r "$OLD" --include="*.clj" --include="*.edn" . | grep -v ".git" | grep -v "docs/superpowers"

bb check --quick

git add -A && git commit -m "bump library suite version $OLD → $NEW"
git tag -a "$NEW" -m "Release $NEW"
git push && git push --tags
gh release create "$NEW" --title "$NEW" --notes "Library suite release $NEW"

3. Deploy to Clojars:

bb deploy --all

patch-catalogue-version! in the deploy script keeps modules-catalogue.edn in sync automatically after each successful deploy.

What to skip: CHANGELOG.md (maintain manually), docs/superpowers/ (historical planning docs), draft/pre-releases on GitHub (install.sh uses /releases/latest which only returns published releases).


Using Individual Libraries

;; Validation utilities only
{:deps {org.boundary-app/boundary-core {:mvn/version "1.0.1-alpha-31"}}}

;; Full web application stack
{:deps {org.boundary-app/boundary-platform {:mvn/version "1.0.1-alpha-31"}
        org.boundary-app/boundary-user     {:mvn/version "1.0.1-alpha-31"}
        org.boundary-app/boundary-admin    {:mvn/version "1.0.1-alpha-31"}}}

Deployment

Build the uberjar and deploy to any platform:

clojure -T:build clean && clojure -T:build uber
BND_ENV=prod java -jar target/boundary-*-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://boundary-app.org


License

Copyright 2024–2026 Thijs Creemers. All rights reserved.

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