A deps-new template for creating Mycelium web applications. The generated project follows the Kit framework architecture, using Integrant for component lifecycle management.
clj -Tnew create :template io.github.mycelium-clj/mycelium-web-template :name yourname/yourappThis creates a yourapp/ directory with a ready-to-run web application.
Note: This requires deps-new v0.11+ installed as a tool:
clj -Ttools install-latest :lib io.github.seancorfield/deps-new :as new
| Library | Role |
|---|---|
| Mycelium | Schema-enforced workflow engine for request handling |
| Integrant | Component lifecycle and dependency injection |
| Kit | kit-core (Aero config loading) + kit-jetty (HTTP server component) |
| Reitit | Data-driven HTTP routing |
| Selmer | Django-style HTML templating |
| Ring | HTTP abstraction + ring-defaults middleware |
yourapp/
├── deps.edn
├── resources/
│ ├── system.edn # System configuration (Aero + Integrant)
│ ├── logback.xml # Logging configuration
│ └── html/
│ └── home.html # Selmer HTML template
├── src/clj/yourname/yourapp/
│ ├── core.clj # Application entry point
│ ├── config.clj # Config loading
│ ├── db.clj # Database integrant component
│ ├── web/
│ │ ├── handler.clj # Ring handler (Integrant components)
│ │ ├── middleware/
│ │ │ └── core.clj # Base middleware
│ │ └── routes/
│ │ └── pages.clj # Page routes with mycelium workflows
│ ├── cells/
│ │ └── home.clj # Mycelium cell definitions
│ └── workflows/
│ └── home.clj # Mycelium workflow definitions
├── env/
│ ├── dev/clj/
│ │ ├── user.clj # REPL development helpers
│ │ └── yourname/yourapp/
│ │ ├── env.clj # Dev profile defaults
│ │ └── dev_middleware.clj # Dev-only middleware
│ └── prod/clj/
│ └── yourname/yourapp/
│ └── env.clj # Prod profile defaults
└── test/clj/yourname/yourapp/
└── core_test.clj
Start the server:
clojure -M:dev:runVisit http://localhost:3000. Try http://localhost:3000/?name=YourName to see the workflow in action.
Start an nREPL and connect your editor:
clojure -M:dev -m nrepl.cmdlineThen in the REPL:
(dev-prep!) ;; prepare the system config
(go) ;; start all components
(reset) ;; reload code and restart
(halt) ;; stop the systemThe dev profile recompiles the Reitit router on every request, so route changes take effect immediately after (reset).
clojure -M:testThe application is configured via resources/system.edn using Aero reader tags for profile-based config and Integrant references for dependency wiring:
{:server/http
{:port #long #or [#env PORT 3000]
:handler #ig/ref :handler/ring}
:handler/ring
{:router #ig/ref :router/core ...}
:db/sqlite
{:dbname #or [#env DB_NAME "db/app.sqlite"]
:mycelium/doc "SQLite JDBC datasource. Use with next.jdbc..."}
:router/routes
{:routes #ig/refset :reitit/routes}
:reitit.routes/pages
{:db #ig/ref :db/sqlite}}The component dependency chain: server → handler → router → routes → resources. Resources (:db/sqlite, etc.) are injected into routes via #ig/ref and automatically passed to mycelium cell handlers.
Each HTTP request is handled by a mycelium workflow — a directed graph of cells (pure functions with schema contracts).
Cells are registered via defmethod and define their input/output schemas:
(defmethod cell/cell-spec :request/parse-home [_]
{:id :request/parse-home
:handler (fn [_resources data]
(let [params (get-in data [:http-request :query-params])
name (or (get params "name") "World")]
(assoc data :name name)))
:schema {:input [:map [:http-request :map]]
:output [:map [:name :string]]}})Workflows compose cells into pipelines:
(def workflow-def
{:cells {:start :request/parse-home
:render :page/render-home}
:pipeline [:start :render]})
(def compiled (myc/pre-compile workflow-def))Routes wire compiled workflows to HTTP endpoints using mycelium.middleware/workflow-handler, passing integrant-managed resources through to cell handlers:
(defn page-routes [opts]
[["/" {:get {:handler (mw/workflow-handler home/compiled {:resources opts})}}]])The workflow handler automatically passes the Ring request as {:http-request req} to the workflow input, injects opts as the resources map available to all cell handlers, and extracts the :html key from the result as the response body.
- Define cells in
src/clj/.../cells/— each cell receives[resources data]and returns an updated data map - Define a workflow in
src/clj/.../workflows/— compose cells with:pipeline(linear) or:edges+:dispatches(branching) - Add a route in
src/clj/.../web/routes/pages.clj— wire the compiled workflow to a path - Add a template in
resources/html/— Selmer template rendered by the UI cell
Resources are external dependencies (database connections, caches, HTTP clients) that cells can access at runtime. The template uses a convention-based approach:
1. Define the resource as an Integrant component with a :mycelium/doc description:
;; In resources/system.edn:
:db/sqlite
{:dbname #or [#env DB_NAME "db/app.sqlite"]
:mycelium/doc "SQLite JDBC datasource. Use with next.jdbc: (jdbc/execute! db [\"SQL\"]) for writes, (jdbc/execute! db [\"SELECT ...\"]) for reads."}
:cache/redis
{:uri #or [#env REDIS_URI "redis://localhost:6379"]
:mycelium/doc "Redis cache client. Use (cache/get client key) and (cache/set client key value ttl-ms)."}The :mycelium/doc key serves two purposes:
- Discovery: The sporulator identifies resources by the presence of this key (no hardcoded blocklist)
- Agent awareness: The LLM agent sees these descriptions when generating cell implementations, so it knows the API to use
2. Wire resources to routes via #ig/ref in :reitit.routes/pages:
:reitit.routes/pages
{:db #ig/ref :db/sqlite
:cache #ig/ref :cache/redis}All keys injected here are automatically passed as the resources map to every mycelium cell handler. No code changes needed in route files — the template's page-routes passes the full opts map through:
(defn page-routes [opts]
[["/" {:get {:handler (mw/workflow-handler home/compiled {:resources opts})}}]])3. Use resources in cells by declaring :requires and destructuring the resources map:
(defmethod cell/cell-spec :todo/list [_]
{:id :todo/list
:requires [:db]
:handler (fn [{:keys [db]} data]
(let [todos (jdbc/execute! db ["SELECT * FROM todos"])]
{:todos todos}))
:schema {:input [:map]
:output [:map [:todos [:vector :map]]]}
:doc "Lists all todo items from the database"})Adding a new resource only requires two changes:
- Add the Integrant component to
system.ednwith:mycelium/doc - Add
#ig/refto:reitit.routes/pages
No route code, middleware, or handler changes needed.
The Sporulator is a dev tool that designs and implements mycelium workflows via LLM agents. To use it:
1. Add the sporulator dependency to your :nrepl or :dev alias:
:nrepl {:extra-deps {io.github.mycelium-clj/sporulator {:local/root "../sporulator"}}
:main-opts ["-m" "nrepl.cmdline" "-i"]}2. Start the sporulator server from the REPL (see the generated user.clj for helpers):
(sporulator-go!) ;; starts on port 84203. Open the UI at http://localhost:5173 (requires the sporulator-ui dev server).
The sporulator automatically discovers resources from your system.edn via the :mycelium/doc convention. When designing workflows, cells that declare :requires [:db] will have the resource description included in the LLM prompt, so the agent knows how to use each resource correctly.
clj -T:build jar # Build JAR
clj -T:build install # Install to local Maven repo
clj -T:build deploy # Deploy to ClojarsThe template uses deps-new with custom data-fn and template-fn functions (following the Kit pattern):
- Template files live under
resources/io/github/mycelium_clj/mycelium_web_template/ - Content rendering uses Selmer with
<</>>delimiters to avoid conflict with{{}}in HTML templates data-fnreads all template files, renders<<ns-name>>and<<name>>substitutionstemplate-fnwrites rendered files to a temp directory and returns deps-new transform instructions- Namespace directories are automatically inserted under
src/clj/,test/clj/, andenv/paths
Copyright 2025 Mycelium Contributors. Distributed under the MIT License.