Architecture
Strawly is built as a set of independently deployable Docker services orchestrated by Docker Compose. This page describes how the pieces fit together.
Services
Backend
- Language: TypeScript / Node.js
- Framework: Express.js
- Port: 3001 (default)
- Purpose: Central REST API. Handles authentication (JWT), user management, and aggregates data from all modules for the frontend.
- Database: PostgreSQL via Prisma ORM
- Schema ownership: The backend is the sole owner of the database schema. It runs all migrations. Modules never run migrations.
Frontend
- Language: TypeScript / Node.js
- Framework: Next.js
- Port: 3000 (default)
- Purpose: Web UI. Communicates exclusively with the backend API — it does not talk to modules or the database directly.
PostgreSQL
- Port: 5432 (internal only — not exposed to the host by default)
- Purpose: Shared relational database for all services
- Data separation: Modules scope their data using a
providerfield on shared tables (e.g.provider: 'azure')
Migrations (one-shot container)
- Runs
prisma migrate deployagainst the database on startup - Exits after completion — it is not a long-running service
- Starts before the backend to ensure schema is up to date
Seeder (one-shot container)
- Seeds initial data (e.g. the default admin account, module registry entries)
- Runs after migrations and exits
- Safe to run on an already-seeded database (idempotent)
Modules (optional)
Each module is an independent service. See Modules Overview for the current list.
Data flow
User → Frontend (Next.js :3000)
↓ REST API calls
Backend (Express :3001)
↓ Prisma ORM
PostgreSQL (:5432)
↑ Prisma ORM (read/write scoped by provider)
Module (e.g. :3002)
↑ Azure / AWS APIs (external)
The frontend never communicates with modules directly. The backend queries the shared database where modules have written their findings.
Startup order
Services start in dependency order:
- postgres — database
- migrations — schema migration (waits for postgres)
- seeder — initial data (waits for migrations)
- backend — API (waits for seeder)
- modules — optional services (wait for backend and postgres, start in parallel)
- frontend — UI (waits for backend)
Health checks on each service gate the next tier from starting.
Module registry
The backend maintains a modules table (seeded by the seeder and updated by modules at startup). The frontend reads this registry to know which module sections to display in the sidebar. When a module is disabled and removed, its registry entry becomes inactive and the UI hides its section.
Repository structure
Each service lives in its own git repository and is built into its own Docker image:
| Repository | Image | Description |
|---|---|---|
Strawly/backend |
codeberg.org/strawly/strawly-backend |
Express.js API |
Strawly/frontend |
codeberg.org/strawly/strawly-frontend |
Next.js UI |
Strawly/optimizations-azure |
codeberg.org/strawly/strawly-optimizations-azure |
Azure module |
Strawly/deployment |
— | Orchestration scripts and config |
Images are built by Forgejo Actions on every push to main and on version tags.
Secrets and configuration
All sensitive configuration is passed via environment variables at runtime (never baked into images). The deployment repository provides:
.env.example— template with all required variablesgenerate-secrets.sh— generates cryptographically random values forJWT_SECRETandCREDENTIALS_ENCRYPTION_KEY
Licensing
The backend, frontend, and all modules are dual-licensed under AGPL-3.0-or-later (open source) and a commercial license. For commercial licensing enquiries, get in touch.