
Nymfonicat
The multi-tenant runtime, rebuilt for Node.js.
Nymfonicat is a Node.js multi-tenant frontend runtime built with Express 5, Pug, MySQL through Knex, Redis-backed sessions, Webpack, Nginx, Mercure, Composer-managed hybrid packages, and native C/Go build hooks. It preserves Symfonicat’s domain, subdomain, endpoint, parcel, module, middleware, environment, and application model.
# Mental model
Nymfonicat has a database-backed control plane and a JSON-backed public runtime. The protected core UI changes MySQL rows, then syncs them to config/nymfonicat.json. Public requests resolve their composition from that generated configuration.
A bare host and top-level runtime shell.
A reusable affix with its own runtime composition.
A literal/wildcard path pattern with optional trailing catch.
The frontend entry used to render a runtime scope.
An attached browser feature with a registered Node back end.
An optional domain, subdomain, or endpoint context.
# Quick start
git clone https://github.com/symfonicat/node nymfonicat
cd nymfonicat
cp .env.example .env
docker compose up -d --build
touch nymfonicat.lockThe app entrypoint applies pending migrations automatically. When the core tables are empty, it also seeds the example catalog and writes config/nymfonicat.json. Set NYMFONICAT_AUTO_BOOTSTRAP=0 when you want to manage that lifecycle manually.
Add 127.0.0.1 example.com and 127.0.0.1 subdomain1.example.com to /etc/hosts. Nginx publishes port 80 only after the Express health check passes. The core area remains disabled until nymfonicat.lock exists.
# Public runtime
The Express catch-all resolves the active domain, subdomain, endpoint, and optional application context. Core routes, module routes, build assets, and explicit Express routes are mounted first so the public renderer cannot swallow them.
- Resolve hostA matched domain renders its domain shell; a matched affix selects the subdomain shell.
- Match endpointRepeatable arguments accept literals or
*;catchpermits additional path segments. - Merge scopeParcel, modules, middleware, and environment values are assembled for the active context.
- Render PugDomain, subdomain, endpoint, or application templates produce the response.
# Core administration
/core redirects to /core/d. Server-rendered Pug screens with Bootstrap styling manage domains, subdomains, endpoints, parcels, middleware, applications, environment values, file uploads, and JSON synchronization.
Domains
Subdomains
Endpoints
Parcels
Middleware
Applications
Environment groups and values
File uploads
JSON dump/load actions live at /core/y/dump and /core/y/load; /core/u is the sync alias.
# Modules and the hybrid package system
Modules attach to Domains, Subdomains, or Endpoints. Browser requests post to /m/{module-id}; the router executes only a registered module attached to the active scope and reconstructs context from Nymfonicat or Symfonicat-compatible headers. Bodies and responses support ordinary JSON plus the shared Brotli media types.
'nymfonicat/core/main'.log('module active!')
const result = await 'nymfonicat/core/main'.json({
test: true,
})
'nymfonicat/core/main'.log(
'/m/nymfonicat/core/main result:',
result,
)One package, two server runtimes
Symfonicat and Nymfonicat use the same Composer-installed feature package. The package owns one public module id in vendor/package/name form and one set of browser assets, but supplies a runtime-specific server adapter: PHP under src/ for Symfony and Node.js under modules/ for Express.
symfonicat/analytics/
├── composer.json # Composer discovery metadata
├── package.json # optional Node package metadata
├── assets/
│ ├── module/main/index.js # one browser module for both runtimes
│ └── parcel/analyticsparcel/index.js
├── modules/main.js # Nymfonicat Node.js backend
└── src/Module/AnalyticsModule.php # Symfonicat PHP backendassets/module/main/index.js is compiled and served by both runtimes. It calls the same module id and shared .json(), .html(), and .log() browser API.
assets/parcel/analyticsparcel/index.js is the reusable shell entry. The same parcel source can implement a Domain, Subdomain, or Endpoint in either runtime.
modules/main.js handles the module request in Node.js and receives the reconstructed runtime context.
src/Module/AnalyticsModule.php handles the equivalent request through attributed Symfony module routes.
Discovery and build flow
- Install onceComposer places the hybrid package under
vendor/vendor/package. - Opt inNymfonicat accepts
nymfonicat.packageNamemetadata or Composerextra.symfonicat: true. - Discover both layersPackage discovery registers the Node backend and finds module and parcel entries under the installed package.
- Compile shared assetsThe Composer helper runs Webpack before app startup; active scopes receive only their selected parcel and attached modules.
- Dispatch locallyThe browser calls one stable module id. Nymfonicat routes it to the Node backend; Symfonicat routes the same feature contract to PHP.
Keep ids, action names, payload shapes, response formats, and browser behavior aligned between the PHP and Node implementations. Server code is intentionally separate; assets/module and assets/parcel are intentionally shared.
Middleware order
Domain middleware runs first, followed by Subdomain and Endpoint middleware. Each Node module receives { context, req, res, response, middleware } and may mutate context, return response fields, or send through Express directly.
# Environment resolution
Environment keys are grouped by parent and exposed as readable nested browser JSON.
window.env = {
"colors": {
"primary": "blue"
}
}- Parcelbase value
- Domaintenant override
- Subdomainaffix override
- Endpointpath override
- Applicationapplication override
# Assets and packages
Webpack scans the root and opted-in Composer packages for module and parcel entries. It builds public/build/app.js, public/build/app.css, and discovered bundles. Active runtime scopes receive only their active modules and parcel tags.
Discovery roots
assets/module/, assets/parcel/, assets/parcels/, and their package equivalents under vendor/**/**.
Composer build gate
The one-shot Composer service installs hybrid packages and runs the Webpack build before the app starts. Both services share the image-seeded node_modules volume, while Composer packages stay on the vendor bind mount.
# Configuration
The public runtime reads config/nymfonicat.json. npm run sync serializes the MySQL nymfonicat_* tables into that file. A checked-in config/nymfonicat.example.json supports local experiments and tests.
{ "nymfonicat": { "packageName": "vendor/package" } }
{ "extra": { "symfonicat": true } }# Database lifecycle
Automatic bootstrap is idempotent: every app start runs migrations, but seed and sync run only when the parcel, domain, and endpoint tables are all empty. The same commands remain available for explicit control.
npm run migrateApply Knex migrations for runtime resources, scoped environment values, middleware/module joins, and uploads.
npm run seedLoad example domain, subdomain, endpoint, application, env, middleware, module, and parcel rows.
npm run syncSerialize MySQL state into deterministic runtime JSON.
npm run buildDiscover root and Composer package entries and compile the Webpack asset graph.
# Native C and Go hooks
Projects under native/c/**/binding.gyp compile as Node native addons.
Projects under native/go/**/go.mod compile to native/go/{name}/bin/{name}.
core/native supplies JavaScript fallbacks when native artifacts have not been built.
# Mercure
The Compose stack includes a Mercure hub. Server publishing uses MERCURE_URL and MERCURE_JWT_SECRET; public pages receive MERCURE_PUBLIC_URL through window.nymfonicat.mercurePublicUrl.
# Complete 71-file atlas
All 71 project files that implement or configure the documented runtime are indexed below. Vendored Bootstrap distribution files are intentionally excluded.
Runtime entry2 files
Admin4 files
Modules7 files
Runtime services9 files
Database4 files
Templates14 files
templates/application/main.pug↗templates/core/_resource-form.pug↗templates/core/create.pug↗templates/core/edit.pug↗templates/core/env.pug↗templates/core/files.pug↗templates/core/index.pug↗templates/core/locked.pug↗templates/core/not-found.pug↗templates/core/resource.pug↗templates/domain/main.pug↗templates/endpoint/main.pug↗templates/layouts/base.pug↗templates/subdomain/main.pug↗Assets11 files
assets/admin.js↗assets/app.js↗assets/module/main/index.js↗assets/parcel/domainparcel/index.js↗assets/parcel/endpointparcel/index.js↗assets/parcel/subdomainparcel/index.js↗assets/scss/_overrides.scss↗assets/scss/_variables-dark.scss↗assets/scss/_variables.scss↗assets/scss/bootstrap.scss↗assets/styles.scss↗Build and operations9 files
Native examples4 files
Configuration4 files
# Runtime checks
npm run checkThe check initializes Express without Redis, verifies endpoint argument serialization, confirms the seeded middleware attaches to example.com, and executes that middleware through the runtime runner.