Real-time Polling App (Kahoot Clone)

Frontend
Backend
Logic
External package
Database
Build a complete polling app: create/join polls with 5-char game codes, real-time voting system, and animated result bars with percentages.
Top Chrono (Episode 1)
Back to the Academy

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

  1. Create two folders:
    • frontend: for the user interface
    • backend: for database and routes

Backend organization

  1. Create a tables folder for data structures
  2. Create a services folder for API routes

2. Poll Creation Page

Create the CreatePoll page

  1. Create a new page CreatePoll
  2. Create a main container:
    • Width: 100%
    • Height: 100vh
    • Background: blue color

Content structure

  1. 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

  1. In the tables folder, create a Polls table
  2. 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

  1. In services, create a Poll folder
  2. Create a createPolls route (POST method)
  3. Define the body with same fields as the table

Insertion logic

  1. Create a dbinsert node
  2. Select the Polls table
  3. Connect body to data field

5. Variables and Frontend Binding

Create page variables

  1. In CreatePoll, create 5 variables:
    • question (string)
    • answer1 (string)
    • answer2 (string)
    • answer3 (string)
    • answer4 (string)

Bind inputs

  1. 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

  1. Add an onClick event on the validation button
  2. Call the createPolls route

Generate the gameId

  1. Pull a wire from body to create a create object
  2. Connect variables:
    • get question → question
    • get answer1 → answer1
    • get answer2 → answer2
    • get answer3 → answer3
    • get answer4 → answer4
  3. 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

  1. Create a new Home page
  2. 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

  1. Change CreatePoll's path so Home is the default page

Navigation to CreatePoll

  1. On "Create Your Own Poll" button, add onClick event
  2. Create a get router
  3. Add a get property → select push
  4. Create a lambda function
  5. Define path: /createpoll
  6. Connect everything

Test: click button → redirect to CreatePoll

8. Retrieve Poll by ID

Create the getPollsBySlug route

  1. In services/Poll, create getPollsBySlug route
  2. Method: GET
  3. Define body: gameId (string)

Retrieval logic

  1. Create a dbfind on Polls table
  2. Add a filter on gameId
  3. Retrieve gameId from body (get property)
  4. Add first after dbfind to get an object instead of array
  5. Connect to response

Test with Execute: send gameId → retrieve poll object

9. Join Poll from Home

Variables and binding

  1. In Home, create a gameId variable
  2. Bind text input to model gameId

Validation event

  1. On "Validate" button, add onClick event
  2. Call getPollsBySlug
  3. Send gameId in body

Conditional redirect

  1. Retrieve response
  2. Cast response to boolean (if response exists = true)
  3. Create get router → push
  4. Create path with concatenation:
    • /poll/ + gameId
  5. Connect conditionally

Test: invalid gameId = nothing / valid gameId = redirect to /poll/{gameId}

10. Poll Page (Polls)

Create the page

  1. Create a new Polls page
  2. Define path: /poll/:id
  3. 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

  1. Create a new store CurrentPoll
  2. Define schema:
poll (object)
  ├─ question (string)
  ├─ answer1 (string)
  ├─ answer2 (string)
  ├─ answer3 (string)
  ├─ answer4 (string)
  └─ gameId (string)

Fill store from Home

  1. In Home logic (after getPollsBySlug)
  2. Create a get CurrentPoll
  3. Create a set property → select poll
  4. Connect response from getPollsBySlug

Advantage: avoids a new backend call from Polls page

12. Display Question

Dynamic property

  1. In question's custom text on Polls page
  2. Write {question} to create property
  3. Define question = CurrentPoll.poll.question

Test: join a poll → question displays

13. ButtonVote Component

Create the component

  1. Create a new component ButtonVote
  2. Define properties:
    • value (string): answer text
    • index (number): answer position (0-3)
  3. Set button content = value

Style the button

  1. Add appropriate styles (padding, background, border-radius, etc.)

14. Display Answer Buttons

Template to iterate over answers

  1. In button zone, create a template
  2. Logic: for
  3. Iterate on: CurrentPoll.poll transformed to array
    • Create array with [answer1, answer2, answer3, answer4]

Instantiate component

  1. Add ButtonVote component in template
  2. Define properties:
    • value: template.value
    • index: template.index

All 4 buttons display automatically

15. Database - Votes Table

Create the table

  1. In tables, create a Votes table
  2. Define columns:
    • gameId (string)
    • voteIndex (number): index of voted answer (0-3)

16. Vote Creation Route

Create createVote route

  1. In services, create a Vote folder
  2. Create a createVote route (POST method)
  3. Define body:
    • voteIndex (number)
    • gameId (string)

Insertion and retrieval logic

  1. Create a dbinsert on Votes
  2. Connect body to data
  3. After insertion, create a dbfind on Votes
  4. Add a filter on gameId (retrieved from body with get property)
  5. Connect to response

Response returns all votes for the poll

17. Vote Logic in ButtonVote

onClick event

  1. In ButtonVote component, add onClick event
  2. Call createVote route
  3. 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

  1. In services/Vote, create getVotesByGameId (GET)
  2. Body: gameId (string)
  3. Expected response:
    • votes: array of 4 numbers (vote count per answer)
    • totalVotes: total vote count
    • percentages: array of 4 percentages

Retrieve votes

  1. Create a dbfind on Votes
  2. Add a filter on gameId

Install ES-Toolkit package

  1. Go to Library
  2. Install ES-Toolkit package

Group votes with countBy

  1. Use countBy from ES-Toolkit
  2. Parameters:
    • List: dbfind result
    • Grouping function: item.voteIndex
  3. Cast item to indicate it has voteIndex field
  4. Use get property to extract object

Result: object like {0: 2, 1: 5, 2: 1, 3: 3} (vote count per index)

Transform to array

  1. Create a new array of 4 numbers
  2. Name indices: 0, 1, 2, 3
  3. Use spread on countBy object
  4. Connect each value to corresponding index

Calculate total

  1. Use get length on dbfind result

Calculate percentages

  1. Create a map on votes array
  2. For each element:
    • Divide by totalVotes
    • Multiply by 100
  3. Connect to response

Structure response

  1. 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

  1. Click "+" 4 times to create 4 empty objects in votes
  2. Reset store

20. SetVotes Function in Store

Create the function

  1. In CurrentPoll store, create a SetVotes function
  2. This function updates results after each vote

Function logic

  1. Call getVotesByGameId
  2. Create body with store's gameId (CurrentPoll.poll.gameId)

Update percentages

  1. Create a map on CurrentPoll.votes
  2. 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

  1. Create another set property for votes
  2. Same logic: spread → at → index

Update totalVotes

  1. Create a set property for totalVotes
  2. Connect from response.totalVotes

Finalize

  1. Connect map to function output
  2. Click Export

Optimization possibility: break down into multiple functions for readability

21. Call SetVotes After Vote

Modify ButtonVote

  1. In ButtonVote's onClick logic
  2. After createVote call
  3. Call store's SetVotes function

Now each vote automatically updates results

22. Display Result Bars

Create bars template

  1. In results zone, create a template
  2. Logic: for
  3. Iterate on: CurrentPoll.votes

Style the container

  1. Enlarge container to see bars
  2. Define styles:
    • Display: flex
    • Align-items: flex-end (bottom alignment)
    • Justify-content: space-around

Create bars

  1. In template, create a block (the bar)
  2. Define height dynamically:
    • Height: template.value.percentage (e.g., "25%")
  3. Add centered custom text to display vote count
  4. 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: :id in 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