Production-like commercial operations MVP for order management, inventory reservation, and ERP synchronization — built as a learning and portfolio project.
Quick start · Features · Architecture · Configuration · API · Testing · Deployment
Start with Project Status for limitations. Detailed guides cover inventory reservations, ERP synchronization, and architecture decisions.
OrderFlow Operations is a modular monolith that simulates a commercial operations platform. It covers the lifecycle from customer and product catalog management through order creation, inventory reservation (including kit expansion), and asynchronous ERP synchronization via a transactional outbox pattern.
The repository is organized as an npm workspace monorepo with four applications/packages:
| Package | Purpose |
|---|---|
@orderflow/api |
Express REST API, outbox worker, PostgreSQL persistence |
@orderflow/web |
React SPA (Vite) for operational UI |
@orderflow/erp-mock |
Simulated external ERP with idempotent order ingestion |
@orderflow/contracts |
Shared Zod schemas, enums, and TypeScript types |
This project is not production-ready. It is an MVP / study project intended to demonstrate architecture patterns, domain modeling, and operational workflows. Do not deploy to production without a full security, scalability, and compliance review.
Known limitations at v0.1.0:
- Single-node deployment model (no horizontal scaling)
- JWT stored in HTTP-only cookies without refresh-token rotation
- ERP integration targets a local mock service, not a real ERP
- No observability stack (metrics, tracing, alerting)
- Integration tests require Docker (Testcontainers) for PostgreSQL
- Customer & product catalog — Create, list, view and update records with pagination, search, and role-based access
- Kit products — Bundle products composed of simple components with quantity multipliers
- Inventory management — On-hand, reserved, and available quantities with manual adjustments
- Order lifecycle — Create orders with stock reservation, pending-stock fallback, and cancellation
- Transactional outbox — Reliable ERP sync with retries, attempt logging, and manual retry
- Dashboard — Order counts by status, sales totals, low-stock alerts, integration health
- Audit trail — Entity-level audit logs for orders and system events
- Role-based access — SALES, MANAGER, and FINANCE roles with route-level enforcement
OrderFlow follows a modular monolith layout: each domain module owns its HTTP layer, application use cases, and infrastructure repositories within a single deployable API process.
flowchart TB
subgraph Client
WEB[React SPA<br/>@orderflow/web]
end
subgraph API["@orderflow/api (Express)"]
AUTH[Auth Module]
CUST[Customers]
PROD[Products]
INV[Inventory]
ORD[Orders]
INT[Integrations]
DASH[Dashboard]
OUTBOX[Outbox Worker]
end
subgraph External
ERP[ERP Mock<br/>@orderflow/erp-mock]
end
DB[(PostgreSQL)]
WEB -->|REST + cookies| API
API --> DB
OUTBOX -->|HTTP POST| ERP
ERP --> DB
See docs/architecture.md for module boundaries, data flow, and ADR references.
| Layer | Technology |
|---|---|
| Runtime | Node.js 24+ |
| Language | TypeScript 5.9 |
| API | Express 5, Zod validation |
| Frontend | React 19, Vite 7, TanStack Query, React Router 7 |
| Database | PostgreSQL 16 |
| Auth | JWT (HTTP-only cookie), bcrypt |
| Testing | Vitest, Testing Library |
| Tooling | ESLint 9, Prettier, npm workspaces |
- Node.js >= 24.0.0 (see
.nvmrc) - npm included with Node.js 24
- Docker with Compose v2 for the local database; Docker is also required by the integration test suite
- A local PostgreSQL 16 instance can replace Compose for development, but not the Testcontainers-based tests
- Git
The following commands use Bash, Git Bash or a POSIX-compatible shell. All commands run from the repository root.
# Clone the repository
git clone https://github.com/luismpenholato/orderflow-operations.git
cd orderflow-operations
# Install dependencies
npm ci
# Compile the shared package before starting its consumers
npm run build -w @orderflow/contracts
# Start PostgreSQL and wait for its health check
docker compose up -d --wait
# Required by migration and seed scripts; use only for local development
export DATABASE_URL="postgresql://orderflow:orderflow_dev@localhost:5432/orderflow"
# Run migrations and seed demo data
npm run db:migrate
npm run db:seed
# Start API, web, and ERP mock concurrently
npm run devPowerShell: use the same commands, replacing the export line with:
$env:DATABASE_URL = "postgresql://orderflow:orderflow_dev@localhost:5432/orderflow"Keep that variable in the terminal running migrations, seeds and the applications. The migration and seed scripts do not load a root .env automatically.
If you edit shared contracts during development, run npm run dev -w @orderflow/contracts in another terminal to keep their compiled output up to date.
| Service | URL |
|---|---|
| Web UI | http://localhost:5173 |
| API | http://localhost:3000 |
| ERP Mock | http://localhost:3001 |
| API Health | http://localhost:3000/health |
Demo credentials (seed data, local only):
| Password | Role | |
|---|---|---|
sales@demo.orderflow.local |
Demo@12345 |
SALES |
manager@demo.orderflow.local |
Demo@12345 |
MANAGER |
finance@demo.orderflow.local |
Demo@12345 |
FINANCE |
Try the workflow as MANAGER: inspect inventory, create an order with available stock, then open its details and the integration monitor to follow ERP synchronization. SALES starts on Orders; FINANCE has read access to Orders and Dashboard.
orderflow-operations/
├── apps/
│ ├── api/ # REST API + outbox worker
│ │ └── src/
│ │ ├── modules/ # Domain modules (auth, customers, orders, …)
│ │ ├── shared/ # Database, errors, logging, validation
│ │ └── workers/ # Outbox poller and ERP client
│ ├── web/ # React SPA
│ │ └── src/
│ │ ├── pages/ # Route-level pages
│ │ ├── components/ # Shared UI components
│ │ └── api/ # Fetch client
│ └── erp-mock/ # Simulated ERP service
├── packages/
│ └── contracts/ # Shared Zod schemas and enums
├── scripts/
│ ├── migrations/ # SQL migration files
│ ├── migrate.js
│ ├── seed.js
│ └── build-release.sh
├── deploy/ # systemd, nginx, env templates
├── docs/ # Architecture, testing, deployment guides
├── .github/ # CI/CD workflows and templates
├── docker-compose.yml
├── vitest.config.ts
└── vitest.integration.config.ts
Export environment variables in the shell before starting the processes. The API and ERP mock also use dotenv, which reads .env from each process's working directory; npm workspace scripts run from their package directories.
Use the root .env.example and deploy/.env.example as references for local and deployment settings. Do not overwrite an existing environment file or commit secrets.
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgresql://orderflow:orderflow_dev@localhost:5432/orderflow |
PostgreSQL connection string |
API_PORT |
3000 |
API listen port |
JWT_SECRET |
dev-secret-change-in-production |
JWT signing secret |
JWT_EXPIRES_IN |
8h |
Token expiration |
CORS_ORIGIN |
http://localhost:5173 |
Allowed CORS origin |
ERP_MOCK_URL |
http://localhost:3001 |
ERP mock base URL |
OUTBOX_POLL_INTERVAL_MS |
5000 |
Outbox worker poll interval |
OUTBOX_MAX_RETRIES |
5 |
Max sync retries per message |
ERP_MOCK_PORT |
3001 |
ERP mock listen port |
ERP_MOCK_FAILURE_RATE |
0 |
Simulated failure rate (0–1) |
Additional settings in the API configuration:
JWT_SECRETis required whenNODE_ENV=production; the development fallback is not used in that mode.COOKIE_SECURE=trueis needed when serving authentication cookies over HTTPS in a shared deployment.ERP_REQUEST_TIMEOUT_MSdefaults to10000; it bounds each ERP HTTP request.OUTBOX_BATCH_SIZEdefaults to10.
PostgreSQL is provided via Docker Compose for local development:
Set DATABASE_URL first as shown in Quick Start.
docker compose up -d
npm run db:migrate # Applies scripts/migrations/*.sql
npm run db:seed # Idempotent demo data (skips if already seeded)Migrations are tracked in the schema_migrations table. The seed script creates demo users, customers, products (including kits), sample orders, and outbox messages.
Authentication uses email/password login with JWT stored in an HTTP-only cookie (orderflow_token). Business routes and /api/auth/me require a valid session. Login is public; logout clears the cookie without requiring an authenticated request.
| Role | Capabilities |
|---|---|
| SALES | Customers, products (read/write), orders (list/create) |
| MANAGER | All SALES capabilities + inventory, order cancel, integrations, dashboard |
| FINANCE | Orders (read), dashboard (read) |
Login is rate-limited (10 attempts per 15 minutes by default).
Base URL: http://localhost:3000
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /health |
No | Liveness probe |
| GET | /ready |
No | Readiness probe (checks DB) |
| POST | /api/auth/login |
No | Authenticate |
| POST | /api/auth/logout |
No | Clear session cookie |
| GET | /api/auth/me |
Cookie | Current user |
| GET/POST/PUT | /api/customers[/:id] |
SALES, MANAGER | List, create, view and update |
| GET/POST/PUT | /api/products[/:id] |
SALES, MANAGER | List, create, view and update |
| POST | /api/products/:id/components |
SALES, MANAGER | Configure kit components |
| GET | /api/inventory[/:productId] |
MANAGER | Inventory balances |
| POST | /api/inventory/:productId/adjustments |
MANAGER | Adjust stock |
| GET | /api/orders[/:id] |
SALES, MANAGER, FINANCE | List and view orders |
| POST | /api/orders |
SALES, MANAGER | Create an order |
| POST | /api/orders/:id/cancel |
MANAGER | Cancel an order |
| GET | /api/integrations/outbox, /api/integrations/attempts |
MANAGER | Monitor synchronization |
| POST | /api/integrations/outbox/:id/retry |
MANAGER | Retry an outbox message |
| GET | /api/dashboard/summary |
MANAGER, FINANCE | Operational summary |
HTTP request examples: docs/requests/orderflow.http
Square brackets in this summary indicate list/detail variants, not literal URL characters. The catalog has no DELETE endpoint; orders support creation and cancellation, not arbitrary updates.
When an order is created, kit line items are expanded into component products, consolidated, and checked against available stock (on_hand - reserved). If all components can be reserved, the order enters SYNC_PENDING and inventory is reserved atomically within the same database transaction. Otherwise, the order is created as PENDING_STOCK without reservations.
See docs/inventory-reservation.md.
Orders with a successful stock reservation enqueue an ORDER_CREATED outbox message in the same transaction. Orders in PENDING_STOCK do not enqueue that synchronization message. The in-process outbox worker polls pending messages, POSTs to the ERP mock with a stable idempotency key, records sync attempts, and transitions order status to SYNCED or SYNC_FAILED.
See docs/erp-synchronization.md.
The React SPA provides pages for dashboard, customers, products, inventory, orders (list/create/detail), and integration monitoring. API calls use cookie-based auth with credentials: "include" and automatic correlation ID headers.
Routes:
| Path | Page |
|---|---|
/login |
Login |
/ |
Dashboard |
/customers |
Customer list |
/products |
Product catalog |
/inventory |
Inventory balances |
/orders |
Order list |
/orders/new |
Create order |
/orders/:id |
Order details |
/integrations |
Outbox monitor |
npm run test:unit # API domain/application and frontend tests
npm run test:integration # Isolated PostgreSQL via Testcontainers; requires Docker
npm test # Both suites above
npm run validate # Full pipeline: format, lint, typecheck, test, buildThe integration setup starts its own PostgreSQL container and applies migrations. It does not reuse the development database selected by DATABASE_URL.
See docs/testing.md for strategy and coverage details.
npm run buildBuild order: @orderflow/contracts → @orderflow/api → @orderflow/erp-mock → @orderflow/web
Production start:
npm run start -w @orderflow/api
npm run start -w @orderflow/erp-mock
# Serve apps/web/dist through nginx for deploymentRun the API and ERP mock as separate processes. Vite preview is for inspecting a build locally, not the documented VPS serving setup.
Release artifacts: scripts/build-release.sh
VPS deployment uses systemd services, nginx reverse proxy, and PostgreSQL. See docs/deployment/vps.md for install, update, and rollback procedures.
Deploy assets live in deploy/:
deploy/systemd/— systemd unit filesdeploy/nginx/— nginx site configurationdeploy/install.sh— first-time install scriptdeploy/update.sh— update scriptdeploy/.env.example— environment template
| Workflow | Trigger | Purpose |
|---|---|---|
ci.yml |
Push/PR to main |
Lint, typecheck, test, build |
release.yml |
Tag v*.*.* |
Validate version, build tarball, GitHub Release |
deploy-vps.yml |
Manual (workflow_dispatch) |
Deploy to production VPS |
CI and Release use Node.js 24 and npm ci. Deploy VPS downloads an existing release and runs the remote update steps over SSH; it requires the configured production environment and VPS secrets. A push to main runs CI, not a deployment.
Contributions are welcome. Please read CONTRIBUTING.md and CODE_OF_CONDUCT.md before opening issues or pull requests.
Architecture decisions are documented as ADRs in docs/decisions/.
Report vulnerabilities privately following SECURITY.md. Do not open public issues for security concerns.
This MVP uses demo credentials in seed data and a default JWT secret — never use these in any shared or production environment.
Copyright (c) 2026 Luis Mauro Penholato
Released under the MIT License.
Built as a study project to explore modular monolith architecture, transactional outbox patterns, and operational domain modeling.