CivicVotes is a production-ready Election & Voting Management System built with ASP.NET Core Web API. It provides comprehensive features for managing elections, candidates, and secure voting with role-based access control.
Live Demo (Frontend): civicvote-alpha.vercel.app Live Demo (Backend): https://fortune-c-p-api.onrender.com/
- Authentication
- Voting Endpoints
- Admin Endpoints
- Error Handling
- Election State Machine
- Database Schema
- Security Features
- Built-in Frontend for Testing
- Learning Resources
To provide a seamless experience for developers and testers, this project includes a basic, reactive frontend dashboard built with Vite, TypeScript, and Vanilla CSS.
- No External Tools Required: Test the entire flow (Registration β Login β Election Creation β Voting) directly in the browser without needing Postman or Swagger.
- Role-Based Simulation: Easily switch between
Voter,Candidate, andElectoral Administratorflows to verify access control logic. - Real-time API Interaction: Built on top of a custom
api.tsservice that handles JWT injection and refresh token rotation automatically.
- Navigate to the
frontenddirectory. - Run
npm installand thennpm run dev. - Open the provided local URL (typically
http://localhost:5173).
All authenticated endpoints require a JWT Bearer token in the Authorization header:
Authorization: Bearer <your_jwt_token>
| Role | Description |
|---|---|
Voter |
Can view elections, cast votes, view personal vote history |
Electoral Administrator |
Can create/manage elections, candidates, view all votes |
Candidate |
Reserved for future candidate-specific features |
Register a new user account.
Auth Required: No
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
firstName |
string | No | User's first name |
lastName |
string | No | User's last name |
email |
string | Yes | Must be unique |
password |
string | Yes | Min 10 chars, requires digit |
roles |
string[] | No | Defaults to ["Voter"] |
Response 201 Created:
{
"message": "User registered successfully."
}Error 400 Bad Request:
{
"DuplicateUserName": ["Username 'john@example.com' is already taken."],
"PasswordTooShort": ["Passwords must be at least 10 characters."]
}Authenticate and receive JWT + refresh token.
Auth Required: No
Request Body:
{
"email": "john@example.com",
"password": "SecurePass123"
}Response 200 OK:
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "dGhpcyBpcyBhIHJlZnJl..."
}Error 401 Unauthorized:
{
"message": "Authentication failed. Invalid email or password, or account may be locked."
}
β οΈ Lockout Policy: After 5 failed login attempts, the account is locked for 15 minutes.
Refresh an expired access token.
Auth Required: No
Request Body:
{
"accessToken": "expired.jwt.token",
"refreshToken": "valid-refresh-token"
}Response 200 OK:
{
"accessToken": "new.jwt.token",
"refreshToken": "new-refresh-token"
}Revoke the current user's refresh token (effectively logging out).
Auth Required: Yes (any role)
Request Body: None
Response 200 OK:
{
"message": "Refresh token revoked successfully."
}List all elections with pagination and search.
Auth Required: No
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
PageNumber |
int | 1 | Page number |
PageSize |
int | 10 | Items per page (max 50) |
SearchTerm |
string | null | Search by election title |
OrderBy |
string | "title" | Sort field (e.g., "title desc") |
Response 200 OK:
[
{
"id": "c9d4c053-49b6-410c-bc78-2d54a9991870",
"title": "Man of the Year",
"description": "Pick your candidate for who should be the man of the year.",
"startTime": "2024-01-01T09:00:00+00:00",
"endTime": "2024-01-30T18:00:00+00:00",
"status": "Ended",
"createdBy": "80abbca8-664d-4b20-b5de-024705497d4a",
"isCancelled": false,
"createdAt": "2023-12-15T10:00:00+00:00"
}
]Response Headers:
X-Pagination: {"CurrentPage":1,"TotalPages":1,"PageSize":10,"TotalCount":2,"HasPrevious":false,"HasNext":false}
Get a specific election by ID.
Auth Required: No
Cast a vote in an election.
Auth Required: Yes
Role Required: Voter
Request Body:
{
"candidateId": "5f91845c-8975-470a-8994-06d997235221"
}Get detailed election results with candidate vote counts and percentages.
Auth Required: No
Get all elections the current authenticated user has voted in.
Auth Required: Yes
Role Required: Voter
All admin endpoints require Authorization: Bearer <token> with the Electoral Administrator role.
Create a new election (created as Draft by default).
Publish a Draft election. This makes it available for voting.
Cancel an election.
Update an election.
Soft-delete an election.
Add a candidate to an election.
All errors follow the ProblemDetails (RFC 7807) format.
| Status Code | Meaning | Common Causes |
|---|---|---|
400 |
Bad Request | Validation failure, invalid state transition |
401 |
Unauthorized | Missing/expired JWT token |
403 |
Forbidden | Insufficient role permissions |
404 |
Not Found | Election or candidate not found |
409 |
Conflict | Duplicate vote attempt |
429 |
Too Many Requests | Rate limit exceeded (60 req/min) |
500 |
Internal Server Error | Unexpected server error |
The development of CivicVotes has been an intensive journey of learning and applying modern web technologies. Key resources that significantly contributed to the success of this project include:
- "Ultimate ASP.NET Core Web API" (Second Edition) by Marinko Spasojevic and Vladimir Pecanac. This comprehensive guide was instrumental in architecting the backend using modern best practices, including the Repository Pattern, Service Layer abstraction, and advanced security implementations like JWT Bearer authentication and Refresh Token rotation.
- Official TypeScript Documentation: Leveraging TypeScript on the frontend ensured deep type safety and improved developer efficiency by catching architectural errors early in the development cycle.
- Vite & Modern Frontend Tooling: Using Vite provided a premium development experience with blazing-fast HMR and an optimized build pipeline, which was crucial for maintaining high productivity throughout the project.
Developing this project provided invaluable experience in bridging complex architectural patterns with practical, production-ready implementation, focusing on security, performance, and user experience.
GET /health
Returns 200 OK with "Healthy" when the API is running.