System Architecture
Overview
Varasto is a modular web application designed for inventory management. It runs as a small set of services orchestrated by Docker Compose. The frontend is exposed directly on its own port and proxies API/admin traffic to the backend; there is no separate reverse proxy.
Components
1. Frontend (Refine + Vite)
- Tech Stack: React, TypeScript, Refine, Vite, Tailwind CSS.
- Role: Single Page Application (SPA) serving the user interface, published directly on
:5173. - Proxy: The Vite dev server forwards
/api,/admin,/static, and/gotobackend:8000, so the frontend origin behaves like a single entry point. - Routing:
/*: Main application routes./login: Authentication page.
2. Backend (Django REST Framework)
- Tech Stack: Python, Django, Django REST Framework.
- Role: API provider, business logic, and database interaction.
- Deployment:
backendservice listening on container port:8000, mapped to host:8001(host8000is left free to avoid the common Portainer conflict). - Database: PostgreSQL.
3. Database (PostgreSQL)
- Role: Persistent data storage.
- Models:
Company,User,ApiKey,ProductModel,PhysicalProduct,ProductBatch,Movement, etc. (There is noLicensemodel — licensing lives onCompany.)
Topology
db (postgres :5432)
backend (django :8000 -> 8001:8000) # host 8001 (8000 left free for Portainer)
frontend (vite :5173 -> 5173:5173) # proxies /api,/admin,/static,/go -> backend:8000
docs (mkdocs :8002)
demo-client (nginx :8081)
Polymorphic Product Layering
A product's polymorphic behaviour is split across two parallel abstractions on purpose — keep them distinct when extending the system:
| Layer | Base class | Lives in | Responsibility |
|---|---|---|---|
| Write / ledger | ProfileBehavior |
inventory/strategies.py |
How a movement is committed to the ledger (bulk vs batch vs serialized vs assembled). |
| UI / calculation | BaseEngine |
inventory/engines/ |
How the widget renders inputs and computes the stock delta (e.g. counter, converter, dimension, bucket, time_based, tracker). |
The mapping is profile → {tracking_mode, engine_type} → {behavior, engine},
resolved by inventory/profiles.py (PROFILE_MAP) and
inventory/engines/factory.py. A given ProductModel therefore picks up both
a ProfileBehavior (write path) and a BaseEngine (read/UI path) from its single
profile field. See Strategies for the full table.
Directory Structure
portable-inventory/
├── backend/ # Django Application
│ ├── config/ # Settings & URLs
│ ├── core/ # Auth, Tenants, Middleware
│ ├── inventory/ # Domain Logic (Models, Services)
│ └── requirements.txt
├── frontend/ # React Application
│ ├── src/
│ │ ├── features/ # Feature-based Modules
│ │ └── components/ # Shared UI Components
├── docs/ # This documentation (MkDocs)
├── docker-compose.yml # Orchestration
└── Makefile # Helper commands