App de Sondage Temps Réel (Clone Kahoot)

Frontend
Backend
Logic
External package
Database
Construire une app de sondage complète : créer/rejoindre des polls avec codes à 5 caractères, système de vote temps réel, et barres de résultats animées avec pourcentages.
Top Chrono (Épisode 1)
Retour à l'Académie

Application de sondage en temps réel

Résumé express : Créer une application complète type Kahoot permettant de créer et rejoindre des sondages avec votes en temps réel et affichage des résultats en barres animées.


1. Architecture du projet

  1. Créer deux dossiers :
    • frontend : pour l'interface utilisateur
    • backend : pour la base de données et les routes

Organisation du backend

  1. Créer un dossier tables pour les structures de données
  2. Créer un dossier services pour les routes API

2. Page de création de sondage

Créer la page CreatePoll

  1. Créer une nouvelle page CreatePoll
  2. Créer un conteneur principal :
    • Width : 100%
    • Height : 100vh
    • Background : couleur bleue

Structure du contenu

  1. Ajouter un bloc de contenu avec :
    • Un custom text pour le titre
    • Un bloc bleu contenant :
      • 1 text input pour la question
      • 4 text input pour les choix de réponses
      • 1 button pour valider

3. Base de données - Table Polls

Créer la table

  1. Dans le dossier tables, créer une table Polls
  2. Définir les colonnes :
    • question (string)
    • answer1 (string)
    • answer2 (string)
    • answer3 (string)
    • answer4 (string)
    • gameId (string)

gameId : identifiant à 5 caractères pour faciliter la saisie, comme dans Kahoot

4. Route de création de sondage

Créer la route createPolls

  1. Dans services, créer un dossier Poll
  2. Créer une route createPolls (méthode POST)
  3. Définir le body avec les mêmes champs que la table

Logique d'insertion

  1. Créer un node dbinsert
  2. Sélectionner la table Polls
  3. Connecter le body au champ data

5. Variables et liaison frontend

Créer les variables de page

  1. Dans CreatePoll, créer 5 variables :
    • question (string)
    • answer1 (string)
    • answer2 (string)
    • answer3 (string)
    • answer4 (string)

Lier les inputs

  1. Pour chaque text input, définir le model correspondant :
    • Premier input → question
    • Inputs suivants → answer1, answer2, answer3, answer4

6. Logique de création avec UUID

Événement onClick du bouton

  1. Ajouter un événement onClick sur le bouton de validation
  2. Appeler la route createPolls

Générer le gameId

  1. Tirer un fil depuis le body pour créer un create object
  2. Connecter les variables :
    • get question → question
    • get answer1 → answer1
    • get answer2 → answer2
    • get answer3 → answer3
    • get answer4 → answer4
  3. Pour le gameId :
    • Aller dans Librairie → cocher Utils
    • Utiliser UUID pour générer un identifiant unique
    • Ajouter un slice (0, 5) pour ne garder que les 5 premiers caractères
    • Connecter au champ gameId

Tester : remplir les champs → valider → vérifier dans la table Polls

7. Page d'accueil (Home)

Créer la page Home

  1. Créer une nouvelle page Home
  2. Design similaire à CreatePoll avec :
    • Un input pour entrer le gameId
    • Un bouton pour rejoindre un sondage
    • Un bouton "Create Your Own Poll" pour accéder à CreatePoll

Définir comme page d'accueil

  1. Changer le path de CreatePoll pour que Home soit la page par défaut

Navigation vers CreatePoll

  1. Sur le bouton "Create Your Own Poll", ajouter un événement onClick
  2. Créer un get router
  3. Ajouter un get property → sélectionner push
  4. Créer une fonction lambda
  5. Définir le chemin : /createpoll
  6. Connecter le tout

Tester : cliquer sur le bouton → redirection vers CreatePoll

8. Récupération d'un sondage par ID

Créer la route getPollsBySlug

  1. Dans services/Poll, créer une route getPollsBySlug
  2. Méthode : GET
  3. Définir le body : gameId (string)

Logique de récupération

  1. Créer un dbfind sur la table Polls
  2. Ajouter un filter sur gameId
  3. Récupérer le gameId depuis le body (get property)
  4. Ajouter un first après le dbfind pour obtenir un objet au lieu d'un tableau
  5. Connecter à la réponse

Tester avec Execute : envoyer un gameId → récupérer l'objet du sondage

9. Rejoindre un sondage depuis Home

Variables et liaison

  1. Dans Home, créer une variable gameId
  2. Lier le text input au model gameId

Événement de validation

  1. Sur le bouton "Validate", ajouter un événement onClick
  2. Appeler getPollsBySlug
  3. Envoyer le gameId dans le body

Redirection conditionnelle

  1. Récupérer la réponse
  2. Caster la réponse en boolean (si réponse existe = true)
  3. Créer un get router → push
  4. Créer le chemin avec concatenation :
    • /poll/ + gameId
  5. Connecter conditionnellement

Tester : gameId invalide = rien / gameId valide = redirection vers /poll/{gameId}

10. Page de sondage (Polls)

Créer la page

  1. Créer une nouvelle page Polls
  2. Définir le path : /poll/:id
  3. Design avec 3 zones :
    • Zone supérieure : question du sondage
    • Zone centrale : résultats (barres de votes)
    • Zone inférieure : boutons de réponses

11. Store pour les données du sondage

Créer le store CurrentPoll

  1. Créer un nouveau store CurrentPoll
  2. Définir le schéma :
poll (object)
  ├─ question (string)
  ├─ answer1 (string)
  ├─ answer2 (string)
  ├─ answer3 (string)
  ├─ answer4 (string)
  └─ gameId (string)

Remplir le store depuis Home

  1. Dans la logique de Home (après getPollsBySlug)
  2. Créer un get CurrentPoll
  3. Créer un set property → sélectionner poll
  4. Connecter la réponse de getPollsBySlug

Avantage : évite un nouvel appel au backend depuis la page Polls

12. Affichage de la question

Propriété dynamique

  1. Dans le custom text de la question sur la page Polls
  2. Écrire {question} pour créer la propriété
  3. Définir question = CurrentPoll.poll.question

Tester : rejoindre un sondage → la question s'affiche

13. Composant ButtonVote

Créer le composant

  1. Créer un nouveau composant ButtonVote
  2. Définir les propriétés :
    • value (string) : texte de la réponse
    • index (number) : position de la réponse (0-3)
  3. Définir le contenu du bouton = value

Styliser le bouton

  1. Ajouter les styles appropriés (padding, background, border-radius, etc.)

14. Affichage des boutons de réponse

Template pour itérer sur les réponses

  1. Dans la zone des boutons, créer un template
  2. Logique : for
  3. Itérer sur : CurrentPoll.poll en transformant en tableau
    • Créer un tableau avec [answer1, answer2, answer3, answer4]

Instancier le composant

  1. Ajouter le composant ButtonVote dans le template
  2. Définir les propriétés :
    • value : template.value
    • index : template.index

Les 4 boutons s'affichent automatiquement

15. Base de données - Table Votes

Créer la table

  1. Dans tables, créer une table Votes
  2. Définir les colonnes :
    • gameId (string)
    • voteIndex (number) : index de la réponse votée (0-3)

16. Route de création de vote

Créer la route createVote

  1. Dans services, créer un dossier Vote
  2. Créer une route createVote (méthode POST)
  3. Définir le body :
    • voteIndex (number)
    • gameId (string)

Logique d'insertion et récupération

  1. Créer un dbinsert sur Votes
  2. Connecter le body à data
  3. Après insertion, créer un dbfind sur Votes
  4. Ajouter un filter sur gameId (récupéré du body avec get property)
  5. Connecter à la réponse

La réponse renvoie tous les votes du sondage

17. Logique de vote dans ButtonVote

Événement onClick

  1. Dans le composant ButtonVote, ajouter un événement onClick
  2. Appeler la route createVote
  3. Créer le body :
    • voteIndex : get index (propriété du composant)
    • gameId : CurrentPoll.poll.gameId

Tester : cliquer sur un bouton → nouveau vote ajouté dans la table Votes

18. Route de calcul des résultats

Créer la route getVotesByGameId

  1. Dans services/Vote, créer getVotesByGameId (GET)
  2. Body : gameId (string)
  3. Réponse attendue :
    • votes : tableau de 4 nombres (nombre de votes par réponse)
    • totalVotes : nombre total de votes
    • percentages : tableau de 4 pourcentages

Récupérer les votes

  1. Créer un dbfind sur Votes
  2. Ajouter un filter sur gameId

Installer le package ES-Toolkit

  1. Aller dans Librairie
  2. Installer le package ES-Toolkit

Regrouper les votes avec countBy

  1. Utiliser countBy de ES-Toolkit
  2. Paramètres :
    • Liste : résultat du dbfind
    • Fonction de regroupement : item.voteIndex
  3. Caster l'item pour indiquer qu'il a un champ voteIndex
  4. Faire un get property pour extraire l'objet

Résultat : objet de type {0: 2, 1: 5, 2: 1, 3: 3} (nombre de votes par index)

Transformer en tableau

  1. Créer un new array de 4 nombres
  2. Nommer les indices : 0, 1, 2, 3
  3. Utiliser spread sur l'objet countBy
  4. Connecter chaque valeur à l'index correspondant

Calculer le total

  1. Utiliser get length sur le résultat du dbfind

Calculer les pourcentages

  1. Créer un map sur le tableau de votes
  2. Pour chaque élément :
    • Diviser par totalVotes
    • Multiplier par 100
  3. Connecter à la réponse

Structurer la réponse

  1. Créer un create object avec :
    • votes : tableau de votes
    • totalVotes : nombre total
    • percentages : tableau de pourcentages

19. Store pour les résultats

Ajouter au store CurrentPoll

votes (array of objects) - 4 éléments
  └─ percentage (string)
  
totalVotes (number)

Initialiser le store

  1. Cliquer 4 fois sur le "+" pour créer 4 objets vides dans votes
  2. Reset le store

20. Fonction SetVotes dans le store

Créer la fonction

  1. Dans le store CurrentPoll, créer une fonction SetVotes
  2. Cette fonction met à jour les résultats après chaque vote

Logique de la fonction

  1. Appeler getVotesByGameId
  2. Créer le body avec le gameId du store (CurrentPoll.poll.gameId)

Mettre à jour les pourcentages

  1. Créer un map sur CurrentPoll.votes
  2. Pour chaque élément :
    • Créer un set property pour percentage
    • Récupérer la valeur avec spread de la réponse
    • Utiliser at avec l'index (cocher "index" dans le map)
    • Caster l'index en string
    • Concatener avec le symbole %

Mettre à jour les votes

  1. Créer un autre set property pour votes
  2. Même logique : spread → at → index

Mettre à jour totalVotes

  1. Créer un set property pour totalVotes
  2. Connecter depuis response.totalVotes

Finaliser

  1. Connecter le map à l'output de la fonction
  2. Cliquer sur Export

Possibilité d'optimisation : décomposer en plusieurs fonctions pour plus de lisibilité

21. Appeler SetVotes après le vote

Modifier ButtonVote

  1. Dans la logique onClick de ButtonVote
  2. Après l'appel à createVote
  3. Appeler la fonction SetVotes du store

Maintenant, chaque vote met à jour automatiquement les résultats

22. Affichage des barres de résultats

Créer le template des barres

  1. Dans la zone des résultats, créer un template
  2. Logique : for
  3. Itérer sur : CurrentPoll.votes

Styliser le conteneur

  1. Agrandir le conteneur pour voir les barres
  2. Définir les styles :
    • Display : flex
    • Align-items : flex-end (alignement en bas)
    • Justify-content : space-around

Créer les barres

  1. Dans le template, créer un bloc (la barre)
  2. Définir la hauteur dynamiquement :
    • Height : template.value.percentage (ex: "25%")
  3. Ajouter un custom text centré pour afficher le nombre de votes
  4. Ajouter une transition pour l'animation

Tester : voter → les barres s'animent en temps réel !

Récapitulatif des concepts clés

Concept Utilisation
UUID + slice Générer des identifiants courts (5 caractères)
Router push Navigation programmatique entre pages
Path parameters URL dynamique avec :id
Store functions Encapsuler la logique de mise à jour
countBy (ES-Toolkit) Regrouper et compter des éléments
Map Transformer des tableaux
Template (for) Itérer pour afficher des listes
Concatenation Construire des URLs et strings dynamiques
Cast to boolean Vérifier l'existence d'une réponse
Transitions CSS Animations fluides

Architecture complète

Frontend
├─ Home (page d'accueil)
│   ├─ Input gameId
│   ├─ Bouton "Join Poll"
│   └─ Bouton "Create Your Own Poll"
├─ CreatePoll (création)
│   ├─ Input question
│   ├─ 4 inputs réponses
│   └─ Bouton validation
└─ Polls (sondage actif)
    ├─ Question
    ├─ Barres de résultats
    └─ Boutons de vote (composant ButtonVote)

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)

Conseils pratiques

  • gameId à 5 caractères : slice(0, 5) sur UUID pour simplicité
  • Store functions : centraliser la logique complexe
  • Utiliser ES-Toolkit pour des opérations avancées sur les tableaux
  • Path parameters : :id dans l'URL pour les pages dynamiques
  • Animations CSS : ajouter des transitions pour un effet visuel professionnel
  • Cast to boolean : transformer une réponse en condition (existe = true)
  • Map avec index : cocher "index" pour accéder à la position
  • Toujours reset le store après modification du schéma
  • Concatenation : pour construire des URLs dynamiques
  • Penser à exporter les fonctions du store