A full-stack, grid-aware EV charging management and optimization platform built for multi-tenant charging sites.
The system combines a React + Vite frontend with a FastAPI + PostgreSQL backend and provides:
- Multi-tenant authentication with JWT
- Charger and vehicle management
- EV charging session lifecycle management
- Priority-based charging allocation
- Site power-cap enforcement
- Automatic allocator and charging simulation loops
- Billing and invoice generation
- Real-time WebSocket updates
- REST API documentation through FastAPI / Swagger UI
- Docker Compose support for local PostgreSQL + API development
┌─────────────────────────────┐
│ React Frontend │
│ Vite + TypeScript │
│ Port: 3000 │
└──────────────┬──────────────┘
│ REST / WebSocket
▼
┌─────────────────────────────┐
│ FastAPI Backend │
│ Port: 8000 │
│ │
│ JWT Auth │
│ Tenant Management │
│ Charger Management │
│ Vehicle Management │
│ Charging Sessions │
│ Smart Allocator │
│ Billing / Invoices │
│ WebSocket Events │
└──────────────┬──────────────┘
│ Async SQLAlchemy
▼
┌─────────────────────────────┐
│ PostgreSQL 16 │
│ Port: 5432 │
└─────────────────────────────┘
- React 19
- TypeScript
- Vite
- Tailwind CSS
- Lucide React
- Recharts
- Motion
- Python 3.12
- FastAPI
- Uvicorn
- SQLAlchemy 2.x (async)
- asyncpg
- Pydantic / Pydantic Settings
- JWT authentication
- Passlib + bcrypt
- WebSockets
- PostgreSQL 16
- Docker
- Docker Compose
- Git / GitHub
cse-hackthon/
│
├── backend/
│ └── backend/
│ ├── app/
│ │ ├── api/
│ │ │ ├── allocator.py
│ │ │ ├── auth.py
│ │ │ ├── chargers.py
│ │ │ ├── invoices.py
│ │ │ ├── sessions.py
│ │ │ ├── simulator.py
│ │ │ ├── tenants.py
│ │ │ ├── vehicles.py
│ │ │ └── websocket.py
│ │ │
│ │ ├── core/
│ │ │ ├── config.py
│ │ │ └── security.py
│ │ │
│ │ ├── db/
│ │ │ ├── base.py
│ │ │ ├── init_db.py
│ │ │ └── session.py
│ │ │
│ │ ├── models/
│ │ ├── schemas/
│ │ └── main.py
│ │
│ ├── tests/
│ ├── .env.example
│ ├── Dockerfile
│ ├── docker-compose.yml
│ └── requirements.txt
│
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ ├── context/
│ │ ├── hooks/
│ │ ├── services/
│ │ ├── types/
│ │ ├── App.tsx
│ │ └── main.tsx
│ ├── .env.example
│ ├── package.json
│ ├── tsconfig.json
│ └── vite.config.ts
│
└── README.md
Do not commit
node_modules,dist, Python virtual environments,.envfiles, or other generated files.
Install the following before running the project:
- Git
- Python 3.12+
- Node.js 18+
- npm
- Docker Desktop (recommended)
- Docker Compose
Verify:
python --version
node --version
npm --version
docker --version
docker compose versiongit clone <YOUR_GITHUB_REPOSITORY_URL>
cd cse-hackthonThe backend is located at:
backend/backend
Move into the backend directory:
cd backend/backendDocker Compose starts:
- PostgreSQL 16
- FastAPI backend
- Database initialization
- API server
Linux/macOS:
cp .env.example .envWindows PowerShell:
Copy-Item .env.example .envEdit .env before using the project outside local development.
docker compose up --buildThe API will be available at:
http://localhost:8000
Swagger UI:
http://localhost:8000/docs
ReDoc:
http://localhost:8000/redoc
Health check:
http://localhost:8000/health
Stop the services:
docker compose downStop and remove the PostgreSQL volume as well:
docker compose down -v
down -vdeletes the local PostgreSQL data volume. Use it only when you intentionally want a clean database.
Docker is recommended, but the backend can also be run locally.
From:
backend/backend
Windows:
python -m venv .venv
.venv\Scripts\activateLinux/macOS:
python3 -m venv .venv
source .venv/bin/activatepython -m pip install --upgrade pip
pip install -r requirements.txtYou need a running PostgreSQL instance.
Create a database named:
ev_optimizer
The default development credentials in .env.example are:
POSTGRES_SERVER=localhost
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=ev_optimizer
Create your local environment file:
cp .env.example .envThen make sure the PostgreSQL values match your local database.
python -m app.db.init_dbuvicorn app.main:app --reload --host 0.0.0.0 --port 8000Backend:
http://localhost:8000
Create:
backend/backend/.env
Use .env.example as the template.
Example:
APP_NAME=EV Charging Optimizer
APP_ENV=development
DEBUG=true
POSTGRES_SERVER=localhost
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=ev_optimizer
SECRET_KEY=replace-this-with-a-long-random-secret
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=60
ALLOCATOR_INTERVAL_SECONDS=60
SITE_POWER_CAP_KW=50
WS_HEARTBEAT_INTERVAL=30
CORS_ALLOWED_ORIGINS=["http://localhost:3000"]| Variable | Purpose | Example |
|---|---|---|
APP_NAME |
Application name | EV Charging Optimizer |
APP_ENV |
Runtime environment | development |
DEBUG |
Debug mode | true |
POSTGRES_SERVER |
PostgreSQL hostname | localhost / db |
POSTGRES_PORT |
PostgreSQL port | 5432 |
POSTGRES_USER |
PostgreSQL username | postgres |
POSTGRES_PASSWORD |
PostgreSQL password | postgres |
POSTGRES_DB |
Database name | ev_optimizer |
SECRET_KEY |
JWT signing secret | Strong random secret |
ALGORITHM |
JWT algorithm | HS256 |
ACCESS_TOKEN_EXPIRE_MINUTES |
JWT expiration | 60 |
ALLOCATOR_INTERVAL_SECONDS |
Automatic allocator interval | 60 |
SITE_POWER_CAP_KW |
Default site power cap | 50 |
WS_HEARTBEAT_INTERVAL |
WebSocket heartbeat interval | 30 |
CORS_ALLOWED_ORIGINS |
Allowed frontend origins | ["http://localhost:3000"] |
Never push the real .env file to GitHub.
Generate a strong production secret for:
SECRET_KEY=...Do not reuse the placeholder value from .env.example.
Open a new terminal from the repository root:
cd frontendInstall dependencies:
npm installCreate the environment file.
Windows PowerShell:
Copy-Item .env.example .envLinux/macOS:
cp .env.example .envSet:
VITE_API_BASE_URL=http://localhost:8000/api/v1Start the frontend:
npm run devThe frontend will be available at:
http://localhost:3000
Open two terminals.
cd backend/backend
docker compose up --buildcd frontend
npm install
npm run devOpen:
http://localhost:3000
The frontend communicates with:
http://localhost:8000/api/v1
The WebSocket endpoint is:
ws://localhost:8000/api/v1/ws/live/{tenant_id}?token={JWT_TOKEN}
The backend uses JWT-based authentication.
POST /api/v1/auth/registerThe registration endpoint accepts:
{
"username": "site-a",
"password": "demo123"
}POST /api/v1/auth/loginThe login endpoint uses OAuth2 form fields:
username=site-a
password=demo123
A successful login returns an access token.
Use it for protected requests:
Authorization: Bearer <ACCESS_TOKEN>Base URL:
http://localhost:8000/api/v1
| Module | Prefix | Examples |
|---|---|---|
| Authentication | /auth |
Register, login |
| Tenants | /tenants |
Current tenant, update site limits |
| Chargers | /chargers |
Create, list, start/stop sessions |
| Sessions | /sessions |
Active sessions, completion |
| Vehicles | /vehicles |
Create, list, update priority |
| Invoices | /invoices |
Generate and inspect invoices |
| Allocator | /allocator |
Run allocator, inspect status |
| WebSocket | /ws |
Live tenant updates |
Complete interactive API documentation is available at:
http://localhost:8000/docs
The project uses a priority-based charging allocator to distribute limited site power among active charging sessions.
The system supports:
HIGH
MEDIUM
LOW
priority tiers.
The allocator considers:
- Site power capacity
- Active sessions
- Requested charging power
- Vehicle priority
- Charger availability
- Existing allocated power
The automatic allocator runs periodically using:
ALLOCATOR_INTERVAL_SECONDS=60The site-level default power limit is:
SITE_POWER_CAP_KW=50The allocator can also be triggered manually:
POST /api/v1/allocator/runThe backend starts background simulation tasks during application startup.
These simulate charging activity and periodically publish updates to connected WebSocket clients.
The application also seeds demo data when it starts, including sample tenants, chargers, vehicles, and sessions.
This makes it possible to demonstrate the dashboard without manually creating every record first.
The frontend establishes a WebSocket connection for live charging updates.
Endpoint:
ws://localhost:8000/api/v1/ws/live/{tenant_id}?token={JWT_TOKEN}
The backend can broadcast events such as:
- Initial state
- Telemetry updates
- Allocation updates
- Session started
- Session completed
- Charger status changes
The frontend automatically reconnects when the WebSocket connection is lost.
The backend includes invoice generation and invoice breakdown endpoints.
Available operations include:
POST /api/v1/invoices
GET /api/v1/invoices
GET /api/v1/invoices/{invoice_id}
POST /api/v1/invoices/generate/{period}
GET /api/v1/invoices/{invoice_id}/breakdown
Refer to /docs for the exact request and response schemas.
Backend tests are located in:
backend/backend/tests
Run tests locally:
cd backend/backend
pytest -vWith Docker:
docker compose exec api pytest -vRun with coverage:
pytest --cov=app --cov-report=term-missingBefore deploying to production:
- Generate a strong
SECRET_KEY. - Set
APP_ENV=production. - Set
DEBUG=false. - Use a production PostgreSQL database.
- Update
POSTGRES_*credentials. - Restrict
CORS_ALLOWED_ORIGINS. - Use HTTPS for the API.
- Use
wss://instead ofws://for WebSocket connections. - Store secrets in the deployment platform's secret manager/environment settings.
- Do not expose database credentials in source control.
The application already prevents non-development startup when the default JWT secret is still being used.
Create a production build:
cd frontend
npm run buildPreview the production build locally:
npm run previewThe build output is generated in:
frontend/dist
Do not commit dist/ unless your deployment workflow specifically requires it.
Before the first push, make sure secrets and generated files are ignored.
At minimum, your repository should not contain:
.env
.env.local
.venv/
venv/
node_modules/
dist/
__pycache__/
.pytest_cache/
The backend and frontend already contain .gitignore files for most of these.
From the repository root:
git statusCheck tracked environment files:
git ls-files | findstr /I ".env"On Linux/macOS:
git ls-files | grep -E '(^|/)\.env($|\.)'If a real .env file is already tracked, remove it from Git tracking before pushing:
git rm --cached backend/backend/.env
git rm --cached frontend/.envThen commit the removal.
If a real secret was ever committed to a public repository, rotate that secret immediately. Removing the file from the latest commit does not make an exposed secret safe.
If the repository does not already have a root .gitignore, create one with:
# Environment files
.env
.env.*
!.env.example
# Python
__pycache__/
*.py[cod]
*.so
.venv/
venv/
env/
.pytest_cache/
.coverage
htmlcov/
*.egg-info/
# Node
node_modules/
dist/
coverage/
# IDE
.vscode/
.idea/
# OS
.DS_Store
Thumbs.db
# Logs
*.log
# Local database / dumps
*.dumpFrom the repository root:
git statusAdd the files:
git add .Commit:
git commit -m "Initial full-stack EV charging optimizer"Create a GitHub repository, then connect it:
git remote add origin <YOUR_GITHUB_REPOSITORY_URL>Push:
git branch -M main
git push -u origin mainFor future changes:
git add .
git commit -m "Describe your changes"
git push| Service | URL |
|---|---|
| Frontend | http://localhost:3000 |
| Backend | http://localhost:8000 |
| Swagger UI | http://localhost:8000/docs |
| ReDoc | http://localhost:8000/redoc |
| Health Check | http://localhost:8000/health |
| PostgreSQL | localhost:5432 |
Check:
docker compose psMake sure the database container is healthy.
For Docker Compose, the backend connects to:
db:5432
For local Python execution, it normally connects to:
localhost:5432
Therefore, use different POSTGRES_SERVER values depending on how you run the backend.
Check:
VITE_API_BASE_URL=http://localhost:8000/api/v1Then make sure the backend is actually running:
http://localhost:8000/health
Restart Vite after changing .env:
npm run devCheck:
- The user is authenticated.
- The JWT token is valid.
- The backend is running.
- The frontend
VITE_API_BASE_URLpoints to the correct backend. - The tenant ID is valid.
For local development:
ws://localhost:8000/api/v1/ws/live/{tenant_id}?token={JWT_TOKEN}
For HTTPS deployments, use:
wss://...
Check which application is using:
8000
3000
5432
Then stop the conflicting process or change the port configuration.
When contributing:
- Keep backend and frontend configuration separate.
- Update
.env.examplewhen adding a new environment variable. - Never commit secret values.
- Add tests for backend behavior changes.
- Keep API schemas synchronized with frontend types.
- Document new endpoints in the project README or API docs.
- Run the application locally before opening a pull request.
Current implementation includes:
- ✅ React/Vite frontend
- ✅ FastAPI backend
- ✅ PostgreSQL persistence
- ✅ Multi-tenant authentication
- ✅ JWT security
- ✅ Charger management
- ✅ Vehicle management
- ✅ Charging sessions
- ✅ Priority-based allocator
- ✅ Background charging simulation
- ✅ WebSocket live updates
- ✅ Billing and invoices
- ✅ Docker Compose development environment
- ✅ API documentation
- ✅ Backend tests
Add your project's license here, for example:
MIT License
If this repository is intended for a hackathon submission, replace this section with the license or usage terms required by your team or organizers.
Never commit API keys, JWT secrets, database passwords, or other credentials to GitHub.
Use:
.env
for local secrets and:
.env.example
for safe configuration templates containing placeholders only.