Company: Chameleon
Project: EV Adoption Tools
Team: Web/App
This repository is a Monorepo containing the Vite + React frontend web application, the Express + Node.js backend API, and consolidated Python services. The JavaScript packages are managed through npm workspaces, while the Python environment and dependencies are managed through uv.
Frontend (Client): Vite, React, Chart.js, Leaflet
Backend (Server): Node.js, TypeScript, Express.js, MongoDB, JWT, Nodemailer
Python services: FastAPI, uv
Before you begin, ensure you have the following installed:
Since this is a monorepo, you need to manage multiple .env files.
- Root
.env: Store shared, non-secret local configuration here. server/node-api/.env: Store backend-only secrets and backend-specific configuration here (these will override any duplicate variables found in the root .env).client/web-app/.env: Any configuration exposed to the frontend must begin with theVITE_*prefix.**/.env.example: Add any newly introduced variables to the corresponding.env.examplefile with an empty / placeholder value.
Create a .env file in the root directory.
This file controls the shared variables (for both frontend and backend).
For now, it only contains which PORT the server should listen, and where the frontend should send the request to.
There's an .env.example file provided that you can copy.
PORT=8080
VITE_API_URL="http://localhost:${PORT}/api"Create a separate .env file in client/web-app/.env.
This file is dedicated exclusively to the frontend client and must use the VITE_ prefix for any variables exposed to the application.
There's an .env.example file provided that you can copy.
VITE_GOOGLE_MAPS_API_KEY=ABCD1234 (provided that key is separate from the one used at the backend)
VITE_GA_TRACKING_ID=XXXRunning with Docker?
client/web-app/.envonly applies tonpm run dev:client. Vite inlines these values at build time, anddocker compose buildpasses them in as build args that Compose interpolates from the root.env(or your shell) β it never readsclient/web-app/.env. Also addVITE_GOOGLE_MAPS_API_KEYandVITE_GA_TRACKING_IDto the root.env(see the root.env.example) before building, or the containerised app will ship without them.
Create a separate .env file in /server/node-api/.env.
There's an .env.example file provided that you can follow.
MONGODB_URI = mongodb://<<address>>:<<port>>/EVAT
JWT_SECRET = 'abc123'
CLIENT_ORIGIN = "http://localhost:3000"
# Use "none" with HTTPS when the frontend and API are on different sites.
COOKIE_SAME_SITE = "lax"
GOOGLE_MAPS_API_KEY=ABCD1234
GOOGLE_AI_API_KEY=ABCD1234
GOOGLE_APPLICATION_CREDENTIALS="./google-credentials.json"
EMAIL_USER = "sender@example.com"
EMAIL_PASS = "See Nodemailer section"
ADMIN_EMAIL = "receiver@example.com"
PYTHON_API_URL = "http://127.0.0.1:5000"
RELIABILITY_API_URL = "http://127.0.0.1:5000/reliability"Because we use NPM workspaces, you do not need to navigate into individual folders to install packages.
-
Install uv, the Python project manager used by EVAT. For example:
# macOS with Homebrew brew install uv # macOS or Linux with the standalone installer curl -LsSf https://astral.sh/uv/install.sh | sh # Windows with WinGet winget install --id=astral-sh.uv -e
Verify that it is available with
uv --version. -
Install all JavaScript and Python dependencies from the repository root:
npm run install:all
To prepare only the Python environment, run
npm run python:sync. uv createsserver/python-services/.venvand installs the versions recorded inserver/python-services/uv.lock; manual activation is not required. -
Start the dev stack: From the root of the repository, run:
npm run dev
or alternatively,
- Start the Backend API:
npm run dev:server
- Start the Frontend Web App:
npm run dev:client
- Start the Python ML services:
Run the Python tests with
npm run dev:python
npm run test:python.
- Start the Backend API:
The repo ships a Compose stack (web + api + pythonsvc) so the app can be
run without installing Node or Python locally. MongoDB is not included β the API
connects to the company instance via MONGODB_URI.
cp server/node-api/.env.example server/node-api/.env # fill in secrets
cp .env.example .env # optional: Maps key, custom ports
docker compose build # first build: 5-15 min
docker compose up -d| Service | URL |
|---|---|
| Web app | http://localhost:3000 |
| Node API (Swagger) | http://localhost:8080/api/docs |
| Python ML service | http://localhost:5000/docs |
The API publishes on 8080 by default. Override any host port from the
root .env with WEB_HOST_PORT, API_HOST_PORT or PY_HOST_PORT if it
clashes with something else on your machine.
This project can also be started by pulling the prebuilt Docker images from Docker Hub, then building the frontend image locally because Vite requires build-time environment variables. This is the flow currently used for the EVAT deployment workflow.
Log in to Docker Hub:
docker login -u evat26This authenticates the local Docker client with the Docker Hub account used for the EVAT images.
Pull the published images:
docker pull evat26/monorepo:web-latest
docker pull evat26/monorepo:api-latest
docker pull evat26/monorepo:python-latestThese commands download the latest available images for:
web-latestβ frontend React appapi-latestβ Node.js backend APIpython-latestβ Python ML / FastAPI service
Create the shared Docker network so the API and Python service can communicate:
docker network create evat 2>/dev/null || trueevat is a shared bridge network used so services can resolve each other by container name instead of localhost.
Start the API container:
docker run -d \
--name evat-api \
-p 8080:8080 \
--env-file ./server/node-api/.env \
evat26/monorepo:api-latest-druns the container in detached mode--name evat-apigives the container a fixed name-p 8080:8080maps the API container port to the host port--env-file ./server/node-api/.envloads backend variables such asMONGODB_URI,JWT_SECRET, and Google credentials
Start the Python service container:
docker run -d \
--name evat-pythonsvc \
--network evat \
-p 5000:5000 \
--env-file ./server/node-api/.env \
evat26/monorepo:python-latestThis connects the Python service to the same Docker network as the API so backend services can communicate internally over the Docker network, while keeping the public port exposed on host 5000.
Load frontend environment variables for the Vite build:
set -a
. ./client/web-app/.env
set +aThis exports the variables from client/web-app/.env into the current shell so they can be passed to the Docker build command as build arguments.
Convert all VITE_* variables into Docker build args:
BUILD_ARGS=()
for key in $(env | cut -d= -f1 | grep '^VITE_'); do
BUILD_ARGS+=("--build-arg" "$key=${!key}")
doneThis ensures values such as VITE_API_URL and VITE_GOOGLE_MAPS_API_KEY are passed to the Docker build. These values must be injected during build time because Vite compiles them into the final frontend bundle.
Build the frontend image locally:
docker build "${BUILD_ARGS[@]}" -t evat26/monorepo:web-latest ./client/web-appThis builds the React app into a static production bundle and packages it into an Nginx-based image. The frontend is built locally because the web app depends on Vite env vars being embedded during compilation.
Run the web app container:
docker run -d --name evat-web -p 3000:80 evat26/monorepo:web-latestThis starts the frontend on host port 3000 and serves the compiled static site via Nginx.
This flow is useful when you want to pull the backend and Python services from a shared registry while keeping the frontend build local so that the correct Vite configuration is baked into the final web image.
- Go to the Google Cloud Console and create a project with billing enabled.
- Under 'API & Services', enable:
- Places API (New),
- Places API,
- Distance Matrix API,
- Directions API,
- Maps Javascript API, and
- Geocoding API.
- Under 'Credentials', click Create Credentials -> API key. Copy this into GOOGLE_MAPS_API_KEY.
- Click Create Credentials -> Service account. Name it, assign the Viewer role, and click 'Done'.
- Click your new service account -> Keys -> Add key -> Create new key -> JSON.
- Move the downloaded JSON file into server/node-api/ and rename it to google-credentials.json.
- Ensure this path matches the GOOGLE_APPLICATION_CREDENTIALS variable in your backend .env.
- EVAT uses Nodemailer for sending admin email 2FA codes. Currently, it is set up for a fixed Gmail sender address (EMAIL_USER) to an admin (ADMIN_EMAIL).
- To set up your Gmail account, follow Nodemailer's Gmail Instructions.
- Generate an 'App Password' (a 16-character string like abcd efgh ijkl mnop) and paste it into EMAIL_PASS.
Backend testing is implemented using Jest. Python testing is implemented using pytest.
Location: Backend tests are located in server/node-api/test/. The folder structure mirrors the src/ directory (e.g., tests for controllers/user-controller.ts live in test/controllers/user-controller.test.ts).
Pattern: Tests must be written using the AAA pattern (Arrange, Act, Assert).
Structure: Use nested describe() blocks (outer for the file, inner for the function) and use test('Description of what should happen') for clarity.
Run the following commands from the root of the monorepo.
To run the backend tests:
npm run test:serverTo run the Python tests:
npm run test:python(Tip: We highly recommend using the Jest Test Explorer VSCode extension for debugging).
For local Python setup, model training support, service deployment, Docker usage, verification, and troubleshooting, see the Machine Learning Deployment Guide.
For new-student onboarding, repository and dataset orientation, model and endpoint ownership, testing, release procedures, and future priorities, see the Machine Learning Handover Guide.
For the end-to-end data, training, evaluation, artifact, and prediction architecture, see the Machine Learning Pipeline Architecture.
For the explainable EV readiness score and recommendation introduced by task 013S1, see Personalised EV Usage Prediction and Scoring Recommendation.
Invalid Token Error: An invalid token error is currently occurring when performing GET /api/vehicle, even though the Bearer token appears correct when checked in the code. Needs investigation.