minor update
EDR Platform
Monorepo for the Ethio-Djibouti Railway digital platform. Hosts two product lines — Freight Management and Passenger Management — each with a NestJS API plus React portal and back-office web apps, sharing TypeScript types, NestJS utilities, and a React component library.
Tech Stack
Backend
| Layer | Tech |
|---|---|
| Runtime | Node.js ≥ 20 |
| Framework | NestJS 11 (modular architecture) |
| Language | TypeScript 5 (strict mode, project-wide) |
| ORM | TypeORM 0.3 (UUID PKs, soft deletes, snake_case columns) |
| Database | PostgreSQL 16 (one DB per domain) |
| Validation | class-validator + class-transformer |
| API docs | Swagger via @nestjs/swagger |
| Messaging | @nestjs/microservices (inter-service ready) |
| Testing | Jest + Supertest |
Frontend
| Layer | Tech |
|---|---|
| Framework | React 18 + Vite 5 |
| Language | TypeScript 5 (strict) |
| Routing | React Router v6 |
| Styling | Tailwind CSS v4 (@tailwindcss/vite) + tailwind-merge + class-variance-authority |
| UI primitives | Radix UI (meta radix-ui package, shadcn-style components) |
| Icons | lucide-react |
| State / Data | Zustand (client state) · TanStack Query (server state) |
| HTTP | Axios |
| Auth UI | @tria-plc/iamui-common (external IAM) |
Shared Packages
| Package | Purpose |
|---|---|
@edr/types |
Shared TypeScript interfaces and enums |
@edr/api-common |
NestJS decorators, filters, interceptors, pipes, BaseEntity, BaseRepository |
@edr/ui-common |
Shared React components (DashboardLayout, Sidebar, Button, Modal, etc.) and theme tokens |
@edr/eslint-config |
Shared ESLint configs (base / nestjs / react) |
@edr/tsconfig |
Shared TypeScript configs |
@edr/prettier-config |
Shared Prettier configuration |
Tooling
- pnpm 9 — workspace package manager (sole supported PM)
- Turborepo 2 — task orchestrator with caching
- Husky + lint-staged + commitlint — pre-commit ESLint/Prettier and conventional-commit enforcement
- Docker Compose — local Postgres instances + production stack (see
infrastructure/) - Nginx — reverse proxy / static asset server in production
Planned Integrations
- MinIO — S3-compatible object storage for documents (object keys already shaped under
edr-freight/{linkedType}/{ref}/{filename})
Architecture
High-level layout
┌──────────────────────────────────────────────────────────────────┐
│ EDR Platform (monorepo) │
├──────────────────────┬───────────────────────────────────────────┤
│ Freight domain │ Passenger domain │
│ ┌────────────────┐ │ ┌────────────────┐ │
│ │ freight-portal │ │ │ passenger- │ │
│ │ (React) │ │ │ portal (React)│ │
│ ├────────────────┤ │ ├────────────────┤ │
│ │ freight- │ │ │ passenger- │ │
│ │ backoffice │ │ │ backoffice │ │
│ └───────┬────────┘ │ └───────┬────────┘ │
│ │ │ │ │
│ ▼ │ ▼ │
│ ┌────────────────┐ │ ┌────────────────┐ │
│ │ freight-api │ │ │ passenger-api │ │
│ │ (NestJS) │ │ │ (NestJS) │ │
│ └───────┬────────┘ │ └───────┬────────┘ │
│ │ │ │ │
│ ▼ │ ▼ │
│ ┌────────────────┐ │ ┌────────────────┐ │
│ │ postgres- │ │ │ postgres- │ │
│ │ freight │ │ │ passenger │ │
│ └────────────────┘ │ └────────────────┘ │
└──────────────────────┴───────────────────────────────────────────┘
Shared (workspace) packages
@edr/types · @edr/api-common · @edr/ui-common · @edr/{eslint,tsconfig,prettier}-config
Domain isolation
- One database per domain.
postgres-freight(port 5433, dbedr_freight) andpostgres-passenger(port 5434, dbedr_passenger). No cross-database joins. Cross-domain data flows only through API calls or message queues. - Each domain owns its data model. Freight bookings/consignments/shipments/trains/invoices/documents live only in the freight DB; passenger journeys/tickets live only in the passenger DB.
NestJS module pattern (per feature)
modules/<feature>/
entities/<feature>.entity.ts // extends BaseEntity (UUID, timestamps, soft delete)
dto/<verb>-<feature>.dto.ts // class-validator DTOs
<feature>.module.ts // wires controller + service + repository
<feature>.controller.ts // HTTP layer only — no business logic
<feature>.service.ts // business logic
<feature>.repository.ts // extends BaseRepository<Entity>; services inject this, NEVER `Repository<T>` directly
Conventions enforced across the codebase:
- All entities have UUID primary keys (
@PrimaryGeneratedColumn('uuid')). - All entities inherit
createdAt/updatedAt/deletedAtfromBaseEntity(soft delete). - DB columns use
snake_casevia@Column({ name: '...' }); TS properties staycamelCase. - No
synchronize: truein production — schema changes go through TypeORM migrations. - ESLint + Prettier run on pre-commit via Husky + lint-staged.
- Conventional-commits enforced via commitlint.
Frontend application structure
apps/edr-freight-web/portal/src/
App.tsx // routes + sidebar definition
main.tsx // React Router + QueryClient providers
components/
Breadcrumbs.tsx // shared local UI
ui/ // shadcn-style primitives (Button, Input, Dialog, Label, Textarea)
pages/
customers/ // CustomersPage + CustomerDetailPage + NewCustomerPage (dialog)
bookings/ // ... + multimodal Transport Legs editor
consignments/
tracking/ // Shipment grid + table view toggle
trains/ // Fleet roster
billing/ // Invoices
documents/ // MinIO-shaped document library
hooks/ // TanStack Query hooks (per feature)
services/ // Axios clients (per feature)
store/ // Zustand stores
lib/ // utilities (`cn` helper, formatters)
Each feature folder typically contains: *Page.tsx (list), *DetailPage.tsx, New*Page.tsx (create/edit dialog with mode: "create" | "edit"), Delete*Dialog.tsx, and a *.mock.ts seed file used by the current mock UI.
Shared layout (@edr/ui-common)
DashboardLayout provides the sidebar + header shell shared across all freight and passenger web apps:
- Sidebar — brand-tinted, icon-led navigation. Main brand color:
#10B981(rgb(16, 185, 129)— emerald-500). Icon containers use the filled style: brand-color background with a white icon. - Header — language picker, notifications, user dropdown (click-driven, click-outside / Escape close); host apps opt into a light/dark theme toggle via
enableThemeToggle(Tailwind class-based, persisted inlocalStorage).
Auth integration (planned)
Authentication is provided by an external @edr/iamui-common / @tria-plc/iamapi-common package. Do not implement login/JWT/password logic in this repo. Use placeholder TODO comments next to controllers and @CurrentUser decorators (in @edr/api-common) until the integration ships.
Apps & Ports
| App | Package name | Purpose | Port |
|---|---|---|---|
edr-freight-api |
@edr/freight-api |
NestJS API for freight | 3001 |
edr-freight-web/portal |
@edr/freight-portal |
React frontend for freight customers | 5173 |
edr-freight-web/backoffice |
@edr/freight-backoffice |
React frontend for freight employees | 5183 |
edr-passenger-api |
@edr/passenger-api |
NestJS API for passengers | 3002 |
edr-passenger-web/portal |
@edr/passenger-portal |
React frontend for passenger customers | 5174 |
edr-passenger-web/backoffice |
@edr/passenger-backoffice |
React frontend for passenger employees | 5184 |
edr-freight-web and edr-passenger-web are grouping folders, not workspace packages. Each holds independent portal/ and backoffice/ workspace packages declared in pnpm-workspace.yaml.
Getting Started
Prerequisites
- Node.js ≥ 20
- pnpm 9 (
corepack enable && corepack prepare pnpm@9.12.0 --activate) - Docker (for local Postgres)
Install
pnpm install
Start local databases
docker compose -f infrastructure/docker/docker-compose.db.dev.yml up -d
Run apps
pnpm dev # every app
pnpm dev:freight # freight API + portal + backoffice
pnpm dev:passenger # passenger API + portal + backoffice
Common scripts
| Script | Description |
|---|---|
pnpm install |
Install workspace dependencies |
pnpm dev |
Run every app in watch mode |
pnpm build |
Build every package and app |
pnpm test |
Run all tests |
pnpm lint |
Lint everything |
pnpm type-check |
Type-check every package |
pnpm format |
Format with Prettier |
Repository Layout
.
├── apps/
│ ├── edr-freight-api/ NestJS — freight backend
│ ├── edr-freight-web/
│ │ ├── portal/ React — freight customer portal
│ │ └── backoffice/ React — freight back-office
│ ├── edr-passenger-api/ NestJS — passenger backend
│ └── edr-passenger-web/
│ ├── portal/ React — passenger customer portal
│ └── backoffice/ React — passenger back-office
├── packages/
│ ├── api-common/ Shared NestJS utilities + BaseEntity/Repository
│ ├── types/ Shared TS types/enums
│ ├── ui-common/ Shared React components + theme
│ └── config/
│ ├── eslint/ @edr/eslint-config
│ ├── tsconfig/ @edr/tsconfig
│ └── prettier/ @edr/prettier-config
├── infrastructure/
│ ├── docker/ docker-compose files (db.dev / dev / prod)
│ └── nginx/ Production nginx config
├── CLAUDE.md Developer guide for AI-assisted work
├── turbo.json Turborepo task pipeline
├── pnpm-workspace.yaml Workspace manifest
└── README.md
Standards (recap)
- TypeScript strict mode is enabled in every package.
- pnpm only — never run
npm installoryarn. - Conventional commits — enforced via commitlint on every commit.
- NestJS 4-layer pattern —
module → controller → service → repository. - Repository injection — services inject the custom
*Repositoryclass, notRepository<T>. - Controllers are thin — no business logic; delegate to services.
- Migrations only — never enable TypeORM
synchronizein production. - One DB per domain — no cross-database joins.
See CLAUDE.md for the deeper developer guide used during AI-assisted contributions.