A secure and transparent electronic voting platform built with React, Node.js, and Express.
- GitHub Repository: https://github.com/David-mwendwa/electronic-voting-system
- Frontend (Netlify): https://evspolls.netlify.app
- Backend API (Render): https://electronic-voting-system-nxqt.onrender.com/api/v1
- User authentication and authorization
- Secure voting system
- Real-time results and analytics
- Admin dashboard with settings management
- Voter and candidate management
- Election creation and management
- System-wide settings control (maintenance mode, registration toggle)
- Role-based access control
- Frontend: React 18, Vite, TailwindCSS
- Backend: Node.js, Express
- Database: MongoDB (via Mongoose)
- Authentication: JWT
- State Management: React Context API
- Routing: React Router v6
- UI Components: Custom components with TailwindCSS
electronic-voting-system/
├── frontend/ # Frontend React application
│ ├── public/ # Static files
│ └── src/ # Source files
│ ├── components/ # Reusable UI components
│ ├── context/ # React context providers
│ ├── pages/ # Page components
│ ├── styles/ # Global styles
│ └── utils/ # Utility functions
│
├── backend/ # Backend Node.js server
│ ├── config/ # Configuration files
│ ├── controllers/ # Route controllers
│ ├── middleware/ # Custom middleware
│ ├── models/ # Database models
│ ├── routes/ # API routes
│ ├── utils/ # Utility functions
│ └── server.js # Main server file
│
├── .gitignore # Git ignore file
└── package.json # Root package.json with project scripts
From the root directory, you can run:
| Command | Description |
|---|---|
npm run dev |
Start both frontend and backend in development |
npm run server |
Start only the backend server |
npm run build |
Build the frontend for production |
npm start |
Start the production server |
npm test |
Run tests for both frontend and backend |
npm run install:all |
Install all dependencies (root, frontend, backend) |
- Node.js (v16+)
- npm (v8+)
- MongoDB (local or Atlas)
-
Clone the repository
git clone https://github.com/David-mwendwa/electronic-voting-system.git cd electronic-voting-system -
Install all dependencies
# Install all dependencies (root, frontend, and backend) # This will also install concurrently globally if needed npm run install:all
This will:
- Install root dependencies (primarily for development tooling)
- Install frontend dependencies in the
frontenddirectory - Install backend dependencies in the
backenddirectory
-
Environment Setup
- Create
.envfiles in bothfrontendandbackenddirectories - See
.env.examplefiles for required environment variables
- Create
-
Development Mode
# Start both frontend and backend development servers npm run dev- Frontend will be available at
http://localhost:3000 - Backend API will be available at
http://localhost:5000
- Frontend will be available at
-
Running Servers Individually
# Start only the frontend npm run dev --prefix frontend # Start only the backend npm run server
-
Production Build
# Build frontend for production npm run build # Start production server npm start
This will serve the optimized frontend build and start the production backend server.
VITE_API_BASE_URL=http://localhost:5000/api
# Add other frontend environment variables herePORT=5000
MONGODB_URI=your_mongodb_connection_string
JWT_SECRET=your_jwt_secret_key
NODE_ENV=development
# Add other backend environment variables hereThe backend is deployed as an API-only service on Render.
Key environment variables on Render:
MONGO_URL– MongoDB connection string template (e.g.mongodb+srv://<USER>:<PASSWORD>@cluster/db-name)MONGO_PASSWORD– Password used to replace<PASSWORD>inMONGO_URLJWT_SECRET– Long random secret string used to sign JWTsJWT_LIFETIME– JWT lifetime (e.g.7d)COOKIE_LIFETIME– Cookie lifetime in days (e.g.7)NODE_ENV–productionPORT– Optional; Render usually sets this automatically
Typical Render setup:
- Service type: Web Service
- Environment: Node
- Build command (from
backend/):npm install - Start command (from
backend/):npm start - Root directory for the service:
backend
The backend exposes its API at:
https://electronic-voting-system-nxqt.onrender.com/api/v1
The backend does not serve the React app; it only serves JSON APIs.
The frontend is a Vite React app deployed on Netlify from the repo root using netlify.toml.
netlify.toml:
[build]
base = ""
command = "cd frontend && npm install && npm run build"
publish = "frontend/dist"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200- Build command:
cd frontend && npm install && npm run build - Publish directory:
frontend/dist - SPA routing: the redirect rule ensures all routes (e.g.
/admin,/dashboard) serveindex.html, fixing 404s on reload.
The frontend uses frontend/src/api/apiClient.js to determine the API base URL:
const API_BASE_URL = (() => {
if (import.meta.env.VITE_API_BASE_URL) {
return import.meta.env.VITE_API_BASE_URL;
}
if (import.meta.env.MODE === 'development') {
return 'http://localhost:5000/api/v1';
}
return 'https://electronic-voting-system-nxqt.onrender.com/api/v1';
})();For Netlify production, you can either:
- Rely on the default Render URL above, or
- Set
VITE_API_BASE_URLin Netlify to override it (e.g. if the Render URL changes).
The Axios client automatically attaches the JWT as a Bearer token on every request:
apiClient.interceptors.request.use((config) => {
const token =
localStorage.getItem('token') || sessionStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});From the backend/ directory:
npm install
npm run dev # or npm start for production-like runBackend will listen on http://localhost:5000 (and expose APIs under /api/v1).
From the frontend/ directory:
npm install
npm run devVite will start the frontend dev server (commonly on http://localhost:5173). The API client will automatically use http://localhost:5000/api/v1 in development if VITE_API_BASE_URL is not set.
Ensure the backend CORS config (backend/server.js) allows the frontend origin and required methods:
app.use(
cors({
origin: ['https://evspolls.netlify.app', 'http://localhost:3000'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
})
);If you see a preflight error mentioning a specific method (e.g. PATCH), make sure it is listed in methods.
Check that:
JWT_SECRETis set on Render and matches what the backend expects.JWT_LIFETIMEis a valid duration string (e.g.7d).COOKIE_LIFETIMEis a number of days (e.g.7).- You have cleared old tokens from
localStorage/sessionStoragewhen changing secrets.
The decoded JWT should have an exp value that is later than iat by the configured lifetime.
If reloading a route like /admin or /dashboard shows the Netlify 404 page, ensure the [[redirects]] block in netlify.toml is present as above so that all unknown routes serve index.html for React Router to handle.
This project is licensed under the MIT License - see the LICENSE file for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
David Mwendwa - @DavidMwens - davidmw022@gmail.com
Project Link: https://github.com/David-mwendwa/electronic-voting-system