Real-time Polling App (Kahoot Clone)
Real-time Polling Application
Quick Summary: Build a complete Kahoot-style application for creating and joining polls with real-time voting and animated bar chart results.
1. Project Architecture
- Create two folders:
- frontend: for the user interface
- backend: for database and routes
Backend organization
- Create a tables folder for data structures
- Create a services folder for API routes
2. Poll Creation Page
Create the CreatePoll page
- Create a new page CreatePoll
- Create a main container:
- Width: 100%
- Height: 100vh
- Background: blue color
Content structure
- Add a content block with:
- A custom text for the title
- A blue block containing:
- 1 text input for the question
- 4 text input for answer choices
- 1 button to validate
3. Database - Polls Table
Create the table
- In the tables folder, create a Polls table
- Define columns:
- question (string)
- answer1 (string)
- answer2 (string)
- answer3 (string)
- answer4 (string)
- gameId (string)
gameId: 5-character identifier for easy input, like in Kahoot
4. Poll Creation Route
Create the createPolls route
- In services, create a Poll folder
- Create a createPolls route (POST method)
- Define the body with same fields as the table
Insertion logic
- Create a dbinsert node
- Select the Polls table
- Connect body to data field
5. Variables and Frontend Binding
Create page variables
- In CreatePoll, create 5 variables:
- question (string)
- answer1 (string)
- answer2 (string)
- answer3 (string)
- answer4 (string)
Bind inputs
- For each text input, set the corresponding model:
- First input → question
- Following inputs → answer1, answer2, answer3, answer4
6. Creation Logic with UUID
Button onClick event
- Add an onClick event on the validation button
- Call the createPolls route
Generate the gameId
- Pull a wire from body to create a create object
- Connect variables:
- get question → question
- get answer1 → answer1
- get answer2 → answer2
- get answer3 → answer3
- get answer4 → answer4
- For the gameId:
- Go to Library → check Utils
- Use UUID to generate a unique identifier
- Add a slice (0, 5) to keep only first 5 characters
- Connect to gameId field
Test: fill fields → validate → check in Polls table
7. Home Page
Create the Home page
- Create a new Home page
- Similar design to CreatePoll with:
- An input for entering gameId
- A button to join a poll
- A "Create Your Own Poll" button to access CreatePoll
Set as home page
- Change CreatePoll's path so Home is the default page
Navigation to CreatePoll
- On "Create Your Own Poll" button, add onClick event
- Create a get router
- Add a get property → select push
- Create a lambda function
- Define path:
/createpoll - Connect everything
Test: click button → redirect to CreatePoll
8. Retrieve Poll by ID
Create the getPollsBySlug route
- In services/Poll, create getPollsBySlug route
- Method: GET
- Define body: gameId (string)
Retrieval logic
- Create a dbfind on Polls table
- Add a filter on gameId
- Retrieve gameId from body (get property)
- Add first after dbfind to get an object instead of array
- Connect to response
Test with Execute: send gameId → retrieve poll object
9. Join Poll from Home
Variables and binding
- In Home, create a gameId variable
- Bind text input to model gameId
Validation event
- On "Validate" button, add onClick event
- Call getPollsBySlug
- Send gameId in body
Conditional redirect
- Retrieve response
- Cast response to boolean (if response exists = true)
- Create get router → push
- Create path with concatenation:
/poll/+ gameId
- Connect conditionally
Test: invalid gameId = nothing / valid gameId = redirect to
/poll/{gameId}
10. Poll Page (Polls)
Create the page
- Create a new Polls page
- Define path:
/poll/:id - Design with 3 zones:
- Top zone: poll question
- Center zone: results (vote bars)
- Bottom zone: answer buttons
11. Store for Poll Data
Create CurrentPoll store
- Create a new store CurrentPoll
- Define schema:
poll (object)
├─ question (string)
├─ answer1 (string)
├─ answer2 (string)
├─ answer3 (string)
├─ answer4 (string)
└─ gameId (string)
Fill store from Home
- In Home logic (after getPollsBySlug)
- Create a get CurrentPoll
- Create a set property → select poll
- Connect response from getPollsBySlug
Advantage: avoids a new backend call from Polls page
12. Display Question
Dynamic property
- In question's custom text on Polls page
- Write
{question}to create property - Define question =
CurrentPoll.poll.question
Test: join a poll → question displays
13. ButtonVote Component
Create the component
- Create a new component ButtonVote
- Define properties:
- value (string): answer text
- index (number): answer position (0-3)
- Set button content = value
Style the button
- Add appropriate styles (padding, background, border-radius, etc.)
14. Display Answer Buttons
Template to iterate over answers
- In button zone, create a template
- Logic: for
- Iterate on:
CurrentPoll.polltransformed to array- Create array with
[answer1, answer2, answer3, answer4]
- Create array with
Instantiate component
- Add ButtonVote component in template
- Define properties:
- value:
template.value - index:
template.index
- value:
All 4 buttons display automatically
15. Database - Votes Table
Create the table
- In tables, create a Votes table
- Define columns:
- gameId (string)
- voteIndex (number): index of voted answer (0-3)
16. Vote Creation Route
Create createVote route
- In services, create a Vote folder
- Create a createVote route (POST method)
- Define body:
- voteIndex (number)
- gameId (string)
Insertion and retrieval logic
- Create a dbinsert on Votes
- Connect body to data
- After insertion, create a dbfind on Votes
- Add a filter on gameId (retrieved from body with get property)
- Connect to response
Response returns all votes for the poll
17. Vote Logic in ButtonVote
onClick event
- In ButtonVote component, add onClick event
- Call createVote route
- Create body:
- voteIndex: get index (component property)
- gameId:
CurrentPoll.poll.gameId
Test: click button → new vote added to Votes table
18. Results Calculation Route
Create getVotesByGameId route
- In services/Vote, create getVotesByGameId (GET)
- Body: gameId (string)
- Expected response:
- votes: array of 4 numbers (vote count per answer)
- totalVotes: total vote count
- percentages: array of 4 percentages
Retrieve votes
- Create a dbfind on Votes
- Add a filter on gameId
Install ES-Toolkit package
- Go to Library
- Install ES-Toolkit package
Group votes with countBy
- Use countBy from ES-Toolkit
- Parameters:
- List: dbfind result
- Grouping function:
item.voteIndex
- Cast item to indicate it has voteIndex field
- Use get property to extract object
Result: object like
{0: 2, 1: 5, 2: 1, 3: 3}(vote count per index)
Transform to array
- Create a new array of 4 numbers
- Name indices: 0, 1, 2, 3
- Use spread on countBy object
- Connect each value to corresponding index
Calculate total
- Use get length on dbfind result
Calculate percentages
- Create a map on votes array
- For each element:
- Divide by totalVotes
- Multiply by 100
- Connect to response
Structure response
- Create a create object with:
- votes: votes array
- totalVotes: total count
- percentages: percentages array
19. Store for Results
Add to CurrentPoll store
votes (array of objects) - 4 elements
└─ percentage (string)
totalVotes (number)
Initialize store
- Click "+" 4 times to create 4 empty objects in votes
- Reset store
20. SetVotes Function in Store
Create the function
- In CurrentPoll store, create a SetVotes function
- This function updates results after each vote
Function logic
- Call getVotesByGameId
- Create body with store's gameId (
CurrentPoll.poll.gameId)
Update percentages
- Create a map on
CurrentPoll.votes - For each element:
- Create a set property for percentage
- Retrieve value with spread from response
- Use at with index (check "index" in map)
- Cast index to string
- Concatenate with
%symbol
Update votes
- Create another set property for votes
- Same logic: spread → at → index
Update totalVotes
- Create a set property for totalVotes
- Connect from
response.totalVotes
Finalize
- Connect map to function output
- Click Export
Optimization possibility: break down into multiple functions for readability
21. Call SetVotes After Vote
Modify ButtonVote
- In ButtonVote's onClick logic
- After createVote call
- Call store's SetVotes function
Now each vote automatically updates results
22. Display Result Bars
Create bars template
- In results zone, create a template
- Logic: for
- Iterate on:
CurrentPoll.votes
Style the container
- Enlarge container to see bars
- Define styles:
- Display: flex
- Align-items: flex-end (bottom alignment)
- Justify-content: space-around
Create bars
- In template, create a block (the bar)
- Define height dynamically:
- Height:
template.value.percentage(e.g., "25%")
- Height:
- Add centered custom text to display vote count
- Add transition for animation
Test: vote → bars animate in real-time!
Summary of Key Concepts
| Concept | Usage |
|---|---|
| UUID + slice | Generate short identifiers (5 characters) |
| Router push | Programmatic navigation between pages |
| Path parameters | Dynamic URL with :id |
| Store functions | Encapsulate update logic |
| countBy (ES-Toolkit) | Group and count elements |
| Map | Transform arrays |
| Template (for) | Iterate to display lists |
| Concatenation | Build dynamic URLs and strings |
| Cast to boolean | Check response existence |
| CSS Transitions | Smooth animations |
Complete Architecture
Frontend
├─ Home (home page)
│ ├─ gameId input
│ ├─ "Join Poll" button
│ └─ "Create Your Own Poll" button
├─ CreatePoll (creation)
│ ├─ Question input
│ ├─ 4 answer inputs
│ └─ Validation button
└─ Polls (active poll)
├─ Question
├─ Result bars
└─ Vote buttons (ButtonVote component)
Backend
├─ Tables
│ ├─ Polls (question, answer1-4, gameId)
│ └─ Votes (gameId, voteIndex)
└─ Services
├─ Poll
│ ├─ createPolls
│ └─ getPollsBySlug
└─ Vote
├─ createVote
└─ getVotesByGameId
Store
└─ CurrentPoll
├─ poll (object)
├─ votes (array)
├─ totalVotes (number)
└─ SetVotes() (function)
Practical Tips
- 5-character gameId: slice(0, 5) on UUID for simplicity
- Store functions: centralize complex logic
- Use ES-Toolkit for advanced array operations
- Path parameters:
:idin URL for dynamic pages - CSS animations: add transitions for professional visual effects
- Cast to boolean: transform response to condition (exists = true)
- Map with index: check "index" to access position
- Always reset store after schema modification
- Concatenation: to build dynamic URLs
- Remember to export store functions