pnpm workspace monorepo using TypeScript. Each package manages its own dependencies.
- Monorepo tool: pnpm workspaces
- Node.js version: 24
- Package manager: pnpm
- TypeScript version: 5.9
- API framework: Express 5
- Database: PostgreSQL + Drizzle ORM
- Validation: Zod (
zod/v4),drizzle-zod - API codegen: Orval (from OpenAPI spec)
- Build: esbuild (CJS bundle)
artifacts-monorepo/
├── artifacts/ # Deployable applications
│ └── api-server/ # Express API server
├── lib/ # Shared libraries
│ ├── api-spec/ # OpenAPI spec + Orval codegen config
│ ├── api-client-react/ # Generated React Query hooks
│ ├── api-zod/ # Generated Zod schemas from OpenAPI
│ └── db/ # Drizzle ORM schema + DB connection
├── scripts/ # Utility scripts (single workspace package)
│ └── src/ # Individual .ts scripts, run via `pnpm --filter @workspace/scripts run <script>`
├── pnpm-workspace.yaml # pnpm workspace (artifacts/*, lib/*, lib/integrations/*, scripts)
├── tsconfig.base.json # Shared TS options (composite, bundler resolution, es2022)
├── tsconfig.json # Root TS project references
└── package.json # Root package with hoisted devDeps
Every package extends tsconfig.base.json which sets composite: true. The root tsconfig.json lists all packages as project references. This means:
- Always typecheck from the root — run
pnpm run typecheck(which runstsc --build --emitDeclarationOnly). This builds the full dependency graph so that cross-package imports resolve correctly. Runningtscinside a single package will fail if its dependencies haven't been built yet. emitDeclarationOnly— we only emit.d.tsfiles during typecheck; actual JS bundling is handled by esbuild/tsx/vite...etc, nottsc.- Project references — when package A depends on package B, A's
tsconfig.jsonmust list B in itsreferencesarray.tsc --builduses this to determine build order and skip up-to-date packages.
pnpm run build— runstypecheckfirst, then recursively runsbuildin all packages that define itpnpm run typecheck— runstsc --build --emitDeclarationOnlyusing project references
Express 5 API server. Routes live in src/routes/ and use @workspace/api-zod for request and response validation and @workspace/db for persistence.
- Entry:
src/index.ts— readsPORT, starts Express - App setup:
src/app.ts— mounts CORS, JSON/urlencoded parsing, routes at/api - Routes:
src/routes/index.tsmounts sub-routers;src/routes/health.tsexposesGET /health(full path:/api/health) - Depends on:
@workspace/db,@workspace/api-zod pnpm --filter @workspace/api-server run dev— run the dev serverpnpm --filter @workspace/api-server run build— production esbuild bundle (dist/index.cjs)- Build bundles an allowlist of deps (express, cors, pg, drizzle-orm, zod, etc.) and externalizes the rest
Database layer using Drizzle ORM with PostgreSQL. Exports a Drizzle client instance and schema models.
src/index.ts— creates aPool+ Drizzle instance, exports schemasrc/schema/index.ts— barrel re-export of all modelssrc/schema/<modelname>.ts— table definitions withdrizzle-zodinsert schemas (no models definitions exist right now)drizzle.config.ts— Drizzle Kit config (requiresDATABASE_URL, automatically provided by Replit)- Exports:
.(pool, db, schema),./schema(schema only)
Production migrations are handled by Replit when publishing. In development, we just use pnpm --filter @workspace/db run push, and we fallback to pnpm --filter @workspace/db run push-force.
Owns the OpenAPI 3.1 spec (openapi.yaml) and the Orval config (orval.config.ts). Running codegen produces output into two sibling packages:
lib/api-client-react/src/generated/— React Query hooks + fetch clientlib/api-zod/src/generated/— Zod schemas
Run codegen: pnpm --filter @workspace/api-spec run codegen
Generated Zod schemas from the OpenAPI spec (e.g. HealthCheckResponse). Used by api-server for response validation.
Generated React Query hooks and fetch client from the OpenAPI spec (e.g. useHealthCheck, healthCheck).
Utility scripts package. Each script is a .ts file in src/ with a corresponding npm script in package.json. Run scripts via pnpm --filter @workspace/scripts run <script>. Scripts can import any workspace package (e.g., @workspace/db) by adding it as a dependency in scripts/package.json.
Company: MS-BETON s.r.o. (concrete company, Žilina region, Slovakia) Language: Slovak throughout (all UI text)
React + Vite public website. Pages:
/— Home with hero, product highlights/cennik— Cenník (price list, reads from admin data)/vozovy-park— Vozový park (fleet, static)/admin/login— Admin login (credentials:msbeton/Msbeton2023)/admin/dashboard— Admin panel (tabs: Betóny, Služby, Doprava, Klienti)
Design: Primary gold #EDC531, Secondary navy #001D3D, Font: Montserrat
CSS classes: .concrete-bg (no overlay), .concrete-navy (navy overlay), .concrete-light (light overlay)
Admin data is stored in PostgreSQL (via the api-server) with localStorage as a write-through cache:
- On app load:
syncFromServer()inApp.tsxfetches all data from API → updates localStorage - On save: writes to localStorage immediately (sync) + calls API (fire and forget)
- When server data arrives: dispatches
admin-data-syncedevent → AdminDashboard re-keys tabs
DB table: admin_config — key/value JSONB store
Admin API routes (in api-server): GET/PUT /api/admin/{categories,delivery,services,clients,transport-zones,transport-settings,client-accounts}
Client API routes: POST /api/client/login
Frontend files:
artifacts/web/src/lib/adminData.ts— data access + sync logic (includesClientAccount)artifacts/web/src/lib/api.ts— fetch wrappers for admin + client APIartifacts/web/src/lib/adminAuth.ts— btoa auth checkartifacts/web/src/lib/clientAuth.ts— client session (localStorage) + login via API
- PUMPA / MIX tabs with custom vehicle SVG icons
- Client login widget: ID + password, stored in localStorage, shows
Zľava X%badge - Default test client: ID
20/ password1234, 20% discount, group B - Prídavné hadice: +/− counter + range slider, 0–100 m
- Rozbehová chémia: checkbox service
- Výsledok tabs: HOTOVOSŤ (cash, s DPH) / FAKTÚRA (bez DPH + s DPH)
- Discount: crossed-out original → discounted on every line item
- Export PDF: jsPDF-generated branded invoice (navy/gold header, A4)
- Export SMS: plain-text summary copied to clipboard
- Transport: zone-based from adminData + minimum fee (trucks × minimumFee)
- Betónpumpa hourly rate: 112.50 €/hod (from adminData services)
- Čakačka mixéra: 8 €/15 min (from adminData services)
- Min. objednávka: 5 m³; Dosah výložníka pumpy: 28 m
- DPH (VAT): 23% (Slovakia 2025)
- Truck capacity (mix): 9 m³/vozidlo; Minimum fee: 62.50 €/vozidlo