This repository contains a minimal full-stack FuelEU Maritime compliance platform built as a technical assignment.
It includes:
-
backend/- Node.js + TypeScript
- Express HTTP API
- PostgreSQL integration
- FuelEU route, comparison, compliance balance, banking, and pooling logic
-
frontend/- React + TypeScript + Vite
- TailwindCSS UI
- Dashboard with four tabs:
- Routes
- Compare
- Banking
- Pooling
The project focuses on clean domain modeling, strict TypeScript, and hexagonal architecture separation between core business logic and framework-specific adapters.
Both frontend and backend follow a hexagonal structure.
backend/src/
├── core/
│ ├── domain/ # Domain models and core rules
│ ├── application/ # Use cases
│ └── ports/ # Interfaces / abstractions
├── adapters/
│ ├── inbound/http/ # Express controllers
│ └── outbound/postgres/ # PostgreSQL repository implementations
├── infrastructure/
│ ├── config/ # Environment config
│ ├── db/ # Database connection
│ └── server/ # App bootstrap
└── shared/ # Shared backend utilities
frontend/src/
├── core/
│ ├── domain/ # Shared frontend types/domain models
│ ├── application/ # Frontend service layer
│ └── ports/ # API port abstractions
├── adapters/
│ ├── ui/ # React UI components and tabs
│ └── infrastructure/ # HTTP API adapter
└── shared/ # Shared components, constants, and utilities
coredoes not depend on Express, React, or PostgreSQLportsdefine the contract used by the application layeradaptersimplement those contractsinfrastructurewires everything together
This keeps business logic isolated from frameworks and makes the code easier to test and evolve.
GET /routesPOST /routes/:id/baselineGET /routes/comparisonGET /compliance/cb?shipId&yearGET /compliance/adjusted-cb?shipId&yearGET /banking/records?shipId&yearPOST /banking/bankPOST /banking/applyPOST /pools
- Routes tab with filters and baseline selection
- Compare tab with comparison table and chart-like visualization
- Banking tab with compliance balance, bank/apply actions, and KPI display
- Pooling tab with adjusted CB selection, validation, and pool creation
- Dark mode toggle
- Hero background image
- Node.js 20+
- npm 10+
- PostgreSQL 14+ recommended
From the repository root:
npm installCopy the backend example env file:
cp backend/.env.example backend/.envDefault values expected by the backend:
PORT=3001
DB_HOST=localhost
DB_PORT=5432
DB_NAME=fueleu
DB_USER=postgres
DB_PASSWORD=postgresYou can also use DATABASE_URL if preferred.
Create a local database matching your env values, for example:
createdb fueleuOn startup, the backend creates required tables if they do not exist and seeds the initial route dataset.
npm run dev:backendBackend runs on:
http://localhost:3001
In a separate terminal:
npm run dev:frontendFrontend runs on:
http://localhost:5173
The Vite dev server proxies API requests to the backend on port 3001.
Run unit and integration tests:
npm run test --workspace backendnpm run build --workspace backendnpm run build --workspace frontendRequest:
curl "http://localhost:3001/routes"Example response:
[
{
"id": "1",
"routeId": "R001",
"shipId": "SHIP-001",
"vesselType": "Container",
"fuelType": "HFO",
"year": 2024,
"ghgIntensity": 91,
"fuelConsumption": 5000,
"distance": 12000,
"totalEmissions": 4500,
"isBaseline": true
}
]Request:
curl -X POST "http://localhost:3001/routes/R002/baseline"Example response:
{
"id": "2",
"routeId": "R002",
"shipId": "SHIP-002",
"vesselType": "BulkCarrier",
"fuelType": "LNG",
"year": 2024,
"ghgIntensity": 88,
"fuelConsumption": 4800,
"distance": 11500,
"totalEmissions": 4200,
"isBaseline": true
}Request:
curl "http://localhost:3001/compliance/cb?shipId=SHIP-002&year=2024"Example response:
{
"shipId": "SHIP-002",
"year": 2024,
"actualIntensity": 88,
"targetIntensity": 89.3368,
"energyInScopeMj": 196800000,
"cbGco2eq": 263106240
}Request:
curl -X POST "http://localhost:3001/banking/bank" \
-H "Content-Type: application/json" \
-d '{"shipId":"SHIP-002","year":2024,"amountGco2eq":1000}'Example response:
{
"shipId": "SHIP-002",
"year": 2024,
"cbBefore": 263106240,
"applied": 0,
"cbAfter": 263106240,
"availableBanked": 1000
}Request:
curl -X POST "http://localhost:3001/pools" \
-H "Content-Type: application/json" \
-d '{"year":2024,"shipIds":["SHIP-002","SHIP-003"]}'Example response:
{
"poolId": "generated-pool-id",
"year": 2024,
"poolSum": 0,
"valid": true,
"members": [
{
"shipId": "SHIP-002",
"year": 2024,
"cbBefore": 263106240,
"cbAfter": 0
},
{
"shipId": "SHIP-003",
"year": 2024,
"cbBefore": -263106240,
"cbAfter": 0
}
]
}- Backend tables are initialized automatically at startup.
- Seed route data is loaded automatically from the backend seed module.
- Pooling behavior depends on adjusted compliance balance, so banking/application actions can affect pool validity.
- The frontend currently uses sample operational defaults for banking/pooling flows to keep the dashboard easy to test locally.