Real-Time Voting and Polls for Your Website

Real-Time Voting and Polls for Your Website

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1284
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1240
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    982
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1031
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1104
  • image_website-_0.webp
    Website development for Red Pear
    553

Real-Time Voting and Polls for Your Website

Imagine: a conference with 10,000 participants, organizers launch a vote, and results update every 30 seconds via regular AJAX polling. Users complain about delays, the server crashes under 10,000 requests per second. Sound familiar? Choosing real-time voting solves two problems: instant result delivery and reduced server load. We have implemented such systems for 30+ projects — from corporate surveys to large-scale live streams.

Why SSE Instead of Polling?

Polling means N requests per second, where N is the number of users. With 500 users — 500 req/s wasted. SSE or WebSocket give one connection per user, through which the server sends data only when it changes. For voting, two mechanisms work: SSE (Server-Sent Events) and WebSocket. A third option — polling — we do not consider: 1 request per second for 500 concurrent users is 500 req/s load just to check "nothing changed." SSE is a unidirectional stream from server to client, described in the EventSource specification. For polls this is sufficient: the vote is sent via ordinary POST, the result arrives via SSE. WebSocket is justified if you need immediate feedback (animation "your vote accepted") or additional interactive elements.

Parameter SSE WebSocket
Direction Unidirectional (server → client) Bidirectional
Implementation complexity Low (native EventSource) Medium (requires WebSocket server)
Infrastructure No sticky sessions Requires sticky session or separate server
Performance Up to 10,000 connections per PHP worker Up to 100,000 connections on Node.js
Browser support All modern, except IE All modern

How to Prevent Duplicate Voting?

For authorized users — unique constraint (option_id, user_id) and check in controller. For anonymous voting — protection via IP + fingerprint. Fingerprint is generated on the frontend (library fingerprintjs) and passed in the header. This is not absolute protection but sufficient for most cases.

$fingerprint = $request->header('X-Client-Fingerprint'); $alreadyVoted = PollVote::where('poll_id', $poll->id) ->where(function ($q) use ($request, $fingerprint) { $q->where('ip', $request->ip()) ->orWhere('fingerprint', $fingerprint); })->exists(); 

Additionally, you can use Redis locks: Cache::lock('vote:'.$poll->id.':'.$userId, 10)->get() — this prevents simultaneous submission from one account.

How to Scale Voting to Thousands of Participants?

A PHP application with SSE keeps the connection open. 1,000 concurrent users = 1,000 PHP workers. This is expensive. Solution: offload broadcasting via Pusher or Laravel Echo Server (socket.io). Then the SSE controller is no longer needed — the client subscribes to a channel, the server publishes a poll.updated event to Redis, Laravel Echo broadcasts to all subscribers.

// After recording a vote broadcast(new PollUpdated($poll->id, $counts))->toOthers(); 
Echo.channel(`poll.${pollId}`) .listen('PollUpdated', ({ counts }) => updateBars(counts)); 

This architecture handles hundreds of thousands of connections on a single Node.js process. For monitoring, use Laravel Horizon: it shows the number of active SSE workers and response time. This saves up to 70% on server infrastructure costs compared to direct SSE workers.

Data Schema and Optimization

CREATE TABLE polls ( id BIGSERIAL PRIMARY KEY, title VARCHAR(500) NOT NULL, is_multiple BOOLEAN NOT NULL DEFAULT false, is_active BOOLEAN NOT NULL DEFAULT true, ends_at TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE TABLE poll_options ( id BIGSERIAL PRIMARY KEY, poll_id BIGINT NOT NULL REFERENCES polls(id) ON DELETE CASCADE, label VARCHAR(255) NOT NULL, position SMALLINT NOT NULL DEFAULT 0 ); CREATE TABLE poll_votes ( id BIGSERIAL PRIMARY KEY, option_id BIGINT NOT NULL REFERENCES poll_options(id), user_id BIGINT REFERENCES users(id), ip INET, voted_at TIMESTAMP NOT NULL DEFAULT NOW(), UNIQUE(option_id, user_id) ); 

Aggregation is computed via a materialized view or direct COUNT. Under peak load (live stream, 5,000+ participants), it is better to store counters separately and increment via Redis: HINCRBY poll:42:counts 1 1.

Client-Side and Sending Votes

const pollId = 42; const source = new EventSource(`/api/polls/${pollId}/stream`); source.onmessage = (event) => { const { counts } = JSON.parse(event.data); updateBars(counts); }; source.onerror = () => { console.warn('SSE reconnecting...'); }; function updateBars(counts) { const total = Object.values(counts).reduce((a, b) => a + Number(b), 0); document.querySelectorAll('[data-option-id]').forEach(el => { const id = el.dataset.optionId; const pct = total > 0 ? Math.round((counts[id] || 0) / total * 100) : 0; el.querySelector('.bar').style.width = pct + '%'; el.querySelector('.label').textContent = pct + '%'; }); } async function vote(optionId) { const resp = await fetch(`/api/polls/${pollId}/vote`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }, body: JSON.stringify({ option_id: optionId }), }); if (resp.status === 409) { showMessage('You have already voted'); } } 

Implementing an SSE Endpoint in Laravel

Route::get('/api/polls/{poll}/stream', function (Poll $poll) { return response()->stream(function () use ($poll) { while (true) { if (connection_aborted()) break; $counts = PollVote::selectRaw('option_id, COUNT(*) as votes') ->whereIn('option_id', $poll->options->pluck('id')) ->groupBy('option_id') ->pluck('votes', 'option_id'); $data = json_encode(['counts' => $counts, 'ts' => now()->timestamp]); echo "data: {$data}\n\n"; ob_flush(); flush(); sleep(2); } }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'X-Accel-Buffering' => 'no', ]); }); 

X-Accel-Buffering: no is a mandatory header when using Nginx as a proxy; otherwise data will accumulate in the buffer.

How to Test Real-Time Voting?

Use tools: Postman for sending votes, k6 for load testing, browser console for checking SSE connections. Main scenarios: 1) check receiving updates after voting; 2) simulate simultaneous votes from a thousand users; 3) check resilience on connection drop (SSE auto-reconnects).

Common Mistakes and Solutions

  • N+1 queries: when retrieving the list of votes with lazy-loaded options. Use with('options') in Eloquent.
  • Missing X-Accel-Buffering: without this header, Nginx buffers the SSE stream, and users see data in chunks.
  • Ignoring connection_aborted(): without it, PHP workers continue hanging, consuming memory.
  • Not using Redis for counters: direct COUNT from DB on each update creates load. Redis increments are much faster.

Development Timelines

Stage Time
Basic voting (SSE, authorized) 2–3 days
Anonymous voting + anti-duplicate +1 day
Multi-option polls + history +1 day
Scaling via Pusher/Echo +2 days
Administrative interface 2–3 days

We guarantee a bug-free period of 30 days after delivery, provide documentation, and train your team. To evaluate your project, contact us — get a detailed plan and precise timeline. Request a consultation to ensure your voting runs smoothly under any load.