This page shows the main parts of Cluster ERP and how they connect: one FastAPI server, 28 modules loaded into it, an in-process EventBus between the modules, two clients, and a PostgreSQL database. It is a map, not a manual. Each part links to the page that documents it.
In short¶
- One FastAPI application is built by
create_appand serves both the API and the web app (src/cluster_erp/server/app.py:1287,src/cluster_erp/server/app.py:1633-1644). - The module registry discovers the packages under
cluster_erp.modulesand loads each one into the application (src/cluster_erp/core/module_registry.py:19,src/cluster_erp/core/module_registry.py:58-72). - Modules talk to each other only through the
EventBusand the contracts incore/contracts.py(src/cluster_erp/core/event_bus.py:43-49,src/cluster_erp/core/contracts.py:1-6). - Every request that touches business data gets one SQLAlchemy session on PostgreSQL through asyncpg (
src/cluster_erp/server/dependencies.py:160-200,src/cluster_erp/core/database.py:95-102). - The web app and the desktop client draw their screens from module metadata served at
GET /api/v1/_meta(src/cluster_erp/server/app.py:1518-1556).
How it works¶
The diagram shows the parts and the direction of the calls between them.
flowchart LR
browser["Browser: webapp/"]
desktop["Desktop client: PySide6"]
subgraph server["Server process"]
app["create_app: FastAPI"]
mw["Middleware stack"]
shared["Shared routes: server/routes"]
mods["28 modules: ModuleBase"]
bus["EventBus"]
core["core: models, metadata, change_history"]
end
db[("PostgreSQL")]
storage[("File storage")]
browser -->|"HTTP /app and /api/v1"| app
desktop -->|"HTTP /api/v1"| app
app --> mw
mw --> shared
mw --> mods
mods <-->|"Event and Query contracts"| bus
shared --> core
mods --> core
core --> db
shared --> storageIn prose, the diagram says the following:
- A browser loads the web app from
/appand calls the API under/api/v1. The desktop client calls the same API withhttpx(src/cluster_erp/desktop/core/api_client.py:7,src/cluster_erp/desktop/core/api_client.py:86-87). - Each request passes the middleware stack before it reaches a route. See request pipeline and middleware.
- Routes belong either to a module (prefix
/api/v1/<module>,src/cluster_erp/core/module_base.py:36-38) or to the shared routes insrc/cluster_erp/server/routes/thatcreate_appmounts directly (src/cluster_erp/server/app.py:1432-1450). See shared server routes. - Modules exchange data through the
EventBuswith frozen dataclass contracts. See event bus and contracts reference. - Modules and shared routes use the platform core in
src/cluster_erp/core/: the base model, metadata classes, change history, money helpers, search, and storage. - Data lives in PostgreSQL. Uploaded files live in a storage backend, local disk or S3, created at start-up (
src/cluster_erp/server/app.py:65-75). See attachments, notes, and storage.
Server and start-up¶
python -m cluster_erp.server.main runs uvicorn with the factory flag (src/cluster_erp/server/main.py:39-41). The lifespan context creates the database handle, the EventBus, the module registry, and the storage backend, then loads the modules and runs start-up passes (src/cluster_erp/server/app.py:58-196). See server startup and app factory.
Modules¶
Each module subclasses ModuleBase and implements initialize, register_routes, register_events, shutdown, and get_metadata (src/cluster_erp/core/module_base.py:61-74). See module lifecycle and the modules section.
Metadata and the clients¶
get_metadata returns a ModuleMeta object that describes screens, forms, lists, and actions (src/cluster_erp/core/metadata.py:719). The server serializes all modules' metadata into one document with an ETag (src/cluster_erp/server/app.py:1518-1556). See metadata model, the web app section, and the desktop section.
Database¶
Database wraps one async SQLAlchemy engine and one session factory (src/cluster_erp/core/database.py:53-105). The schema comes from Alembic revisions in migrations/ (alembic.ini:2). In multi-tenant mode each tenant has its own database (src/cluster_erp/config/settings.py:78-84). See database and sessions and multi-tenancy.
Why it is built this way¶
The project's written engineering rules forbid direct communication between modules and require the EventBus for it. It also forbids breaking the "app-level plus middleware" architecture and requires a metadata-driven web app. The code comment in src/cluster_erp/server/app.py:1573-1575 states why the server also serves the web app: the browser reaches the API on the same origin with no CORS.
Rules and boundaries¶
- No module imports another module, except
cluster_erp.modules.auth.guards. The testtests/test_architecture_boundaries.py:46-57enforces this. - Every module has a
module.pyand at least one route decorator. The testtests/test_architecture_boundaries.py:60-72enforces this.
Interaction with other parts¶
The platform section documents the core in depth. The identity and access section covers authentication, permissions, and tenancy. The API section lists the HTTP routes. The operations section covers deployment.
Limits and open points¶
- The desktop client's default API address is a local development address (
src/cluster_erp/desktop/core/api_client.py:82). How a production desktop install is pointed at a server is documented in the desktop section.
Related pages¶
Verified against commit 92f8051 on 2026-11-13.