Note
This whole project is done via vibe-coding, with all markdowns expect this message and some backend tweaks for hosting backend.
A secure, anonymous, and modern voting platform with end-to-end encryption
Features β’ Demo β’ Installation β’ API Docs β’ Contributing
- Overview
- Features
- Tech Stack
- System Requirements
- Installation
- Configuration
- Usage
- API Documentation
- Database Schema
- Security
- Deployment
- Real World Case Study
- Troubleshooting
- Contributing
- License
- Support
AnonVote is a universal, template-based voting system API that allows seamless integration with any third-party authentication system. It provides a secure, anonymous polling platform where users can vote on various topics while maintaining complete privacy.
- π Anonymous by Design: User identities are encrypted and votes are double-hashed
- π Universal Integration: Easy API integration with any existing authentication system
- π Real-time Results: Live vote counting with dynamic updates
- π¨ Modern UI: Beautiful, responsive interface with dark/light mode
- β‘ High Performance: Optimized database queries and caching
- π‘οΈ Enterprise Security: Laravel Sanctum authentication with encrypted data storage
- β Anonymous voting with encrypted user identities
- β Flexible vote types: Upvote only, Downvote only, or Both
- β Real-time vote counting and live results
- β User-defined display names for anonymity
- β Create unlimited polls with custom settings
- β Vote modification (change or remove votes)
- β Responsive design for all devices
- π End-to-end encryption for user data
- π Double-hashed anonymous vote tracking
- π Laravel Sanctum API token authentication
- π CSRF protection
- π SQL injection prevention
- π XSS attack protection
- π¨ Modern, clean interface
- π Dark/Light mode toggle
- π± Fully responsive design
- β‘ Smooth animations and transitions
- π Real-time notifications
- π Visual percentage bars for results
- π§ RESTful API architecture
- π§ Database agnostic (MySQL, PostgreSQL, SQLite)
- π§ Easy third-party integration
- π§ Comprehensive API documentation
- π§ Docker support
- π§ Environment-based configuration
- Framework: Laravel 10.x
- Language: PHP 8.2+
- Authentication: Laravel Sanctum
- Database: MySQL 8.0+ / PostgreSQL / SQLite
- Cache: Redis (optional)
- Framework: React 18.x
- Build Tool: Vite
- Styling: Tailwind CSS 3.x
- Icons: Lucide React
- HTTP Client: Fetch API
- Containerization: Docker & Docker Compose
- Web Server: Nginx / Apache
- SSL: Let's Encrypt
- CI/CD: GitHub Actions (optional)
| Component | Version |
|---|---|
| PHP | 8.2 or higher |
| MySQL | 8.0 or higher |
| Node.js | 18.x or higher |
| Composer | 2.x |
| NPM/Yarn | Latest |
| Memory | 2GB RAM |
| Storage | 1GB free space |
| Component | Version |
|---|---|
| PHP | 8.3+ |
| MySQL | 8.0+ |
| Node.js | 20.x LTS |
| Memory | 4GB RAM |
| Storage | 5GB free space |
-
Install XAMPP or Laragon
-
Install Composer
# Download from https://getcomposer.org/Composer-Setup.exe # Run installer and follow instructions
-
Install Node.js
# Download from https://nodejs.org/ # Install LTS version
-
Verify Installations
php --version composer --version node --version npm --version mysql --version
# Clone the repository
git clone https://github.com/mr0erek/anonvote.git
cd anonvote
# Navigate to backend
cd v1/backend
# Install dependencies
composer install
# Copy environment file
copy .env.example .env
# Generate application key
php artisan key:generate
# Configure database in .env file
# DB_CONNECTION=mysql
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=anonvote
# DB_USERNAME=root
# DB_PASSWORD=
# Create database
# Open MySQL in XAMPP/Laragon control panel
# Create database named 'anonvote'
# Run migrations
php artisan migrate
# Install Sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
# Start server
php artisan serve
# Backend running at http://localhost:8000# Open new terminal
cd anonvote/v1/frontend
# Install dependencies
npm install
# Start development server
npm run dev
# Frontend running at http://localhost:3000# Update system
sudo apt update && sudo apt upgrade -y
# Install PHP and extensions
sudo apt install php8.2 php8.2-cli php8.2-fpm php8.2-mysql \
php8.2-xml php8.2-curl php8.2-mbstring php8.2-zip \
php8.2-bcmath php8.2-tokenizer php8.2-gd -y
# Install MySQL
sudo apt install mysql-server -y
sudo systemctl start mysql
sudo systemctl enable mysql
# Secure MySQL
sudo mysql_secure_installation
# Install Composer
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
sudo chmod +x /usr/local/bin/composer
# Install Node.js (using NodeSource)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install nodejs -y
# Install Git
sudo apt install git -y
# Verify installations
php --version
composer --version
node --version
npm --version
mysql --version# Clone repository
git clone https://github.com/mr0erek/anonvote.git
cd anonvote/v1/backend
# Install PHP dependencies
composer install
# Copy environment file
cp .env.example .env
# Generate application key
php artisan key:generate
# Create database
sudo mysql -u root -pCREATE DATABASE anonvote CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'anonvote_user'@'localhost' IDENTIFIED BY 'your_secure_password';
GRANT ALL PRIVILEGES ON anonvote.* TO 'anonvote_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;# Edit .env file
nano .env
# Update database credentials:
# DB_DATABASE=anonvote
# DB_USERNAME=anonvote_user
# DB_PASSWORD=your_secure_password
# Run migrations
php artisan migrate
# Install Sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
# Set permissions
sudo chown -R $USER:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cache
# Start server
php artisan serve
# Backend running at http://localhost:8000# Open new terminal
cd anonvote/v1/frontend
# Install dependencies
npm install
# Start development server
npm run dev
# Frontend running at http://localhost:3000# Install Homebrew (if not installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Install PHP
brew install php@8.2
# Install MySQL
brew install mysql
brew services start mysql
# Secure MySQL
mysql_secure_installation
# Install Composer
brew install composer
# Install Node.js
brew install node@20
# Verify installations
php --version
composer --version
node --version
npm --version
mysql --version# Clone repository
git clone https://github.com/mr0erek/anonvote.git
cd anonvote/v1/backend
# Install dependencies
composer install
# Copy environment file
cp .env.example .env
# Generate application key
php artisan key:generate
# Create database
mysql -u root -pCREATE DATABASE anonvote CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'anonvote_user'@'localhost' IDENTIFIED BY 'your_secure_password';
GRANT ALL PRIVILEGES ON anonvote.* TO 'anonvote_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;# Edit .env file
nano .env
# Update database credentials
# Run migrations
php artisan migrate
# Install Sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
# Start server
php artisan serve# Open new terminal
cd anonvote/v1/frontend
# Install dependencies
npm install
# Start development server
npm run dev- Docker Desktop (Windows/Mac) or Docker Engine (Linux)
- Docker Compose
# Clone repository
git clone https://github.com/yourusername/anonvote.git
cd anonvote
# Copy environment file
cp .env.example .env
# Build and start containers
docker-compose up -d
# Run migrations
docker-compose exec backend php artisan migrate
# Access application
# Backend: http://localhost:8000
# Frontend: http://localhost:3000
# MySQL: localhost:3306Create docker-compose.yml:
version: '3.8'
services:
# MySQL Database
mysql:
image: mysql:8.0
container_name: anonvote-mysql
restart: unless-stopped
environment:
MYSQL_DATABASE: anonvote
MYSQL_ROOT_PASSWORD: root_password
MYSQL_USER: anonvote_user
MYSQL_PASSWORD: secure_password
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
networks:
- anonvote-network
# Laravel Backend
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: anonvote-backend
restart: unless-stopped
working_dir: /var/www
volumes:
- ./backend:/var/www
ports:
- "8000:8000"
depends_on:
- mysql
environment:
DB_HOST: mysql
DB_DATABASE: anonvote
DB_USERNAME: anonvote_user
DB_PASSWORD: secure_password
networks:
- anonvote-network
command: php artisan serve --host=0.0.0.0 --port=8000
# React Frontend
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: anonvote-frontend
restart: unless-stopped
volumes:
- ./frontend:/app
- /app/node_modules
ports:
- "3000:3000"
depends_on:
- backend
networks:
- anonvote-network
command: npm run dev
volumes:
mysql_data:
networks:
anonvote-network:
driver: bridgeCreate backend/Dockerfile:
FROM php:8.2-fpm
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
zip \
unzip
# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /var/www
# Copy application files
COPY . .
# Install dependencies
RUN composer install --optimize-autoloader --no-dev
# Set permissions
RUN chown -R www-data:www-data /var/www \
&& chmod -R 775 /var/www/storage /var/www/bootstrap/cache
EXPOSE 8000
CMD ["php", "artisan", "serve", "--host=0.0.0.0", "--port=8000"]Create frontend/Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]Edit backend/.env:
# Application
APP_NAME=AnonVote
APP_ENV=local
APP_KEY=base64:generated_key_here
APP_DEBUG=true
APP_URL=http://localhost:8000
# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=anonvote
DB_USERNAME=anonvote_user
DB_PASSWORD=your_secure_password
# Sanctum
SANCTUM_STATEFUL_DOMAINS=localhost:3000,127.0.0.1:3000
# Session
SESSION_DRIVER=database
SESSION_LIFETIME=120
# Cache
CACHE_DRIVER=file
QUEUE_CONNECTION=database
# Mail (optional)
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=nullEdit frontend/src/App.jsx or create frontend/.env:
VITE_API_BASE_URL=http://localhost:8000/api
VITE_APP_NAME=AnonVoteUpdate in frontend/src/App.jsx:
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api';Edit backend/config/cors.php:
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => [
'http://localhost:3000',
'http://127.0.0.1:3000',
'https://yourdomain.com' // Add production domain
],
'allowed_headers' => ['*'],
'supports_credentials' => true,
];Development Mode:
# Terminal 1 - Backend
cd backend
php artisan serve
# Terminal 2 - Frontend
cd frontend
npm run devProduction Mode:
# Backend
cd backend
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan serve
# Frontend
cd frontend
npm run build
# Serve the dist/ folder with Nginx/Apache-
Login: Navigate to
http://localhost:3000- Enter User ID (from your 3rd party system)
- Choose an anonymous display name
- Click "Enter Voting Platform"
-
Create Poll: Click "Create Poll"
- Enter poll title
- Add description
- Select vote type (Upvote/Downvote/Both)
- Click "Create Poll"
-
Vote: Click upvote or downvote buttons
- See real-time results
- Change your vote anytime
- Click again to remove vote
To integrate AnonVote with your existing system:
// Your existing authentication system
const userToken = await yourAuthSystem.login(username, password);
// Send to AnonVote API
const response = await fetch('http://localhost:8000/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
external_user_id: userToken.userId,
display_name: userToken.chosenNickname,
real_identity: userToken.email // This will be encrypted
})
});
const { token } = await response.json();
// Use this token for all subsequent API callshttp://localhost:8000/api
All endpoints except /auth/login require authentication via Bearer token.
Headers:
Authorization: Bearer {your-token}
Content-Type: application/json
Accept: application/jsonPOST /auth/login
Authenticate user with third-party credentials.
Request Body:
{
"external_user_id": "user123",
"display_name": "Anonymous User",
"real_identity": "user@example.com"
}Response:
{
"success": true,
"token": "1|aBC123dEf456...",
"user": {
"id": 1,
"display_name": "Anonymous User"
}
}GET /auth/me
Get authenticated user information.
Response:
{
"success": true,
"user": {
"id": 1,
"display_name": "Anonymous User"
}
}POST /auth/logout
Revoke current authentication token.
Response:
{
"success": true,
"message": "Logged out successfully"
}GET /polls
Get all active polls with vote counts.
Response:
{
"success": true,
"polls": [
{
"id": 1,
"title": "Should we implement dark mode?",
"description": "Vote on whether dark mode should be a priority",
"vote_type": "both",
"upvotes_count": 45,
"downvotes_count": 12,
"total_voters": 57,
"user_vote": null,
"creator": {
"id": 1,
"display_name": "Anonymous User"
},
"created_at": "2024-01-15T10:30:00.000000Z"
}
]
}GET /polls/{id}
Get detailed information about a specific poll.
Response:
{
"success": true,
"poll": {
"id": 1,
"title": "Should we implement dark mode?",
"description": "Vote on whether dark mode should be a priority",
"vote_type": "both",
"upvotes_count": 45,
"downvotes_count": 12,
"total_voters": 57,
"user_vote": "up",
"upvote_percentage": 78.9,
"downvote_percentage": 21.1
}
}POST /polls
Create a new poll.
Request Body:
{
"title": "New Poll Title",
"description": "Detailed description of the poll",
"vote_type": "both",
"expires_at": "2024-12-31T23:59:59Z"
}Vote Types:
both- Allow both upvote and downvoteupvote- Only upvote alloweddownvote- Only downvote allowed
Response:
{
"success": true,
"message": "Poll created successfully",
"poll": {
"id": 2,
"title": "New Poll Title",
"description": "Detailed description of the poll",
"vote_type": "both",
"upvotes_count": 0,
"downvotes_count": 0,
"total_voters": 0
}
}POST /polls/{pollId}/vote
Cast or change a vote on a poll.
Request Body:
{
"vote_type": "up"
}Vote Types:
up- Upvotedown- Downvote
Response:
{
"success": true,
"message": "Vote recorded successfully",
"poll": {
"id": 1,
"upvotes_count": 46,
"downvotes_count": 12,
"total_voters": 58,
"user_vote": "up"
}
}Note: If user already voted with the same type, the vote will be removed.
DELETE /polls/{pollId}/vote
Remove your vote from a poll.
Response:
{
"success": true,
"message": "Vote removed successfully"
}DELETE /polls/{id}
Delete a poll (only poll creator).
Response:
{
"success": true,
"message": "Poll deleted successfully"
}Error Response (403):
{
"success": false,
"message": "Unauthorized"
}400 Bad Request:
{
"success": false,
"message": "Only upvotes are allowed for this poll",
"errors": {
"vote_type": ["Invalid vote type for this poll"]
}
}401 Unauthorized:
{
"message": "Unauthenticated."
}404 Not Found:
{
"success": false,
"message": "Poll not found"
}422 Validation Error:
{
"message": "The given data was invalid.",
"errors": {
"title": ["The title field is required."],
"vote_type": ["The selected vote type is invalid."]
}
}500 Server Error:
{
"success": false,
"message": "Internal server error"
}CREATE TABLE users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
external_user_id VARCHAR(255) UNIQUE NOT NULL,
display_name VARCHAR(255) NOT NULL,
encrypted_identity TEXT NOT NULL,
last_login TIMESTAMP NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
INDEX idx_external_user_id (external_user_id)
);CREATE TABLE polls (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
creator_id BIGINT UNSIGNED NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
vote_type ENUM('upvote', 'downvote', 'both') DEFAULT 'both',
is_active BOOLEAN DEFAULT TRUE,
upvotes_count INT DEFAULT 0,
downvotes_count INT DEFAULT 0,
total_voters INT DEFAULT 0,
expires_at TIMESTAMP NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_active_created (is_active, created_at)
);CREATE TABLE votes (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
poll_id BIGINT UNSIGNED NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
encrypted_user_hash VARCHAR(255) NOT NULL,
vote_type ENUM('up', 'down') NOT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
UNIQUE KEY unique_poll_user (poll_id, user_id),
FOREIGN KEY (poll_id) REFERENCES polls(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_encrypted_hash (encrypted_user_hash)
);-
User Identity Encryption: Real user identities are encrypted using Laravel's encryption
Crypt::encryptString($realIdentity)
-
Anonymous Vote Hashing: Votes are double-hashed to prevent tracking
Hash::make($userId . $pollId . config('app.key'))
-
API Token Security: Laravel Sanctum provides secure token-based authentication
- β Always use HTTPS in production
- β
Keep
.envfile secure and never commit it - β Regularly update dependencies
- β Use strong database passwords
- β Enable rate limiting on API endpoints
- β Implement CSRF protection
- β Sanitize all user inputs
- β Use prepared statements (Laravel does this automatically)
Add to app/Http/Kernel.php:
protected $middlewareGroups = [
'api' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class.':60,1',
],
];- Set
APP_ENV=productionin.env - Set
APP_DEBUG=falsein.env - Generate strong
APP_KEY - Use strong database passwords
- Configure proper CORS origins
- Set up SSL certificate (HTTPS)
- Configure proper file permissions
- Set up database backups
- Configure error logging
- Set up monitoring
- Enable caching
- Optimize autoloader
server {
listen 80;
server_name api.yourdomain.com;
root /var/www/anonvote/backend/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/anonvote/frontend/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}<VirtualHost *:80>
ServerName api.yourdomain.com
DocumentRoot /var/www/anonvote/backend/public
<Directory /var/www/anonvote/backend/public>
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/anonvote-error.log
CustomLog ${APACHE_LOG_DIR}/anonvote-access.log combined
</VirtualHost>
<VirtualHost *:80>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
DocumentRoot /var/www/anonvote/frontend/dist
<Directory /var/www/anonvote/frontend/dist>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost># Install Certbot
sudo apt install certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com -d api.yourdomain.com
# Auto-renewal (runs twice daily)
sudo systemctl status certbot.timerCreate deploy.sh:
#!/bin/bash
echo "π Deploying AnonVote..."
# Backend deployment
cd /var/www/anonvote/backend
git pull origin main
composer install --optimize-autoloader --no-dev
php artisan migrate --force