This repository contains a scalable framework for building a real-time quiz app, which can double up as a test-taking app in an e-learning scenario or be used for a Pub Quiz Friday with your work mates.
It provides full control of the sequence of events to the host and is able to simultaneously run any number of quiz rooms, with any number of people in each of those (using Node JS worker threads!).
The hosts have an option to add their own questions, optionally with images, via Google Sheets.
The real-time messaging is powered by Ably’s real-time infrastructure, meaning, it can have enterprise-level scalability without needing to change anything in the code. Ideally, you’d take this open-sourced framework as a starting point and customize it to make it your own.
This framework is built on top of the multiplayer games networking framework that allows continuous streaming of data between various players and the game server. This framework is a bit different in that it allows for a more on-demand progression of the app by giving adequate controls of the app flow to the host.
You can check out a blog article I wrote, to learn more about the uses of this framework.
Check out the functional demo for this realtime quiz framework.
- Clone this repository
git clone https://github.com/ably-labs/realtime-quiz-framework.git
- Change directory to the project folder and Install dependencies
npm install
cd realtime-quiz/
npm install
cd ..
TIP: you can do exactly the same thing with one command
by concatenating the commands with &&
.
Here we use the shorthand npm i
instead of npm install
.
npm i && cd realtime-quiz/ && npm i && cd ..
- Create a free account with Ably Realtime to get your Ably API KEY. Add a new file called
.env
and add the following, or use the example.env.example
and save that as.env
.(Remember to replace the placeholder with your own API Key. You can get your Ably API key from your Ably dashboard):
ABLY_API_KEY=<YOUR-ABLY-API-KEY>
PORT=5000
- Run the server
node server.js
-
Open the app in your browser at http://localhost:5000. Choose the quiz type and create a quiz room.
-
Copy the shareable link and open it in a separate browser window. This is best experienced in mobile view. Open multiple of these to simulate multiple players if you like.
-
Start the quiz when you are ready and have the players answer the questions as they appear.
Voila! Your live quiz framework is up and running. Customize this framework and make it your own. Feel free to share your quiz app with me on Twitter, I'll be happy to give it a shoutout!
server.js
This file has the main server thread. It performs three functions:
-
Serve the front-end VueJS app using Express.
-
Authenticate front-end clients with the Ably Realtime service using Token Auth strategy.
-
Create and manage Node JS worker threads when a host requests to create a quiz room.
quiz-room-server.js
This file represents a Node JS worker thread. A new instance of this file will run for every quiz room created.
After a live quiz session is finished, the relevant worker thread is killed. When a new player joins or leaves the worker thread communicates with the parent thread (i.e. main thread aka server.js) and lets it know the number of players (among other things).
quiz-default-questions.js
This file exports a JSON array with a set of quiz questions with options and correct answers. This will be used by the quiz-room-server
when a host chooses the "Host a randomly chosen quiz" option.
The client-side is written in VueJS with the following file structure (Note: Only the relevant files are listed here)
realtime-quiz
|___dist
|___public
| |___index.html
|___src
|___main.js
|___routes.js
|___App.vue
|___components
|
|___common
| |___Answer.vue
| |___OnlinePlayers.vue
| |___Question.vue
|
|___host
| |___AdminPanel.vue
| |___CreateQuizRoom.vue
| |___HostHome.vue
| |___Leaderboard.vue
| |___LiveStats.vue
|
|___player
|___PlayerHome.vue
-
dist
folderThe dist folder contains the built Vue app that is auto-generated when you run the
npm run build
command after finishing your work on the Vue app. Theindex.html
file in this folder is what’s served by our express server (for all routes, as routing is handled in the front-end usingvue-router
) -
public
folderThe public folder contains the
index.html
file inside which all the components will be rendered based on the app logic. -
src
folder,main.js
androutes.js
The src folder contains our app files starting with the main.js file which instantiates a new Vue instance with
App.vue
being the top-level component. We also instantiate the Vue Router instance in this file. The different components to be rendered based on the routes are listed in the routes.js file. -
App.vue
As this is the top-level component for our Vue app, it instantiates the Ably library using the Token Authentication strategy and passes on the
realtime
instance to its child components so they can use it as they need. -
components
folder This folder contains all the child components forApp.vue
. They are placed in different folders for a better context.The
common
folder has components that are common to the host of the quiz app and the players. Thehost
folder has components that are visible to the host only. Theplayer
folder has components that are visible to the player only. -
common/OnlinePlayers.vue
This component holds the logic and UI to show a staging area with a list of players who are online. New players are added to this list as they join in realtime. For every player, a thumbnail of their randomly chosen unique colored avatar along with a name as a tagline underneath appears.
-
common/Question.vue
This component holds the logic and UI to show a card with the question, optionally an image, and four options.
The players have buttons to choose one of the options whereas the host has the options listed as non-clickable divs as they won’t be answering the questions.
-
common/Answer.vue
This component holds the logic and UI to show a card with the answer. For the player, this component appears standalone and also indicates if the option they chose was correct or not. For the host, this component replaces the four options in the question.
-
host/HostHome.vue
This is the main component that is shown when anyone lands on the app. By default, all the hosts land on this page and they get a shareable URL to invite their players after they have chosen the type of quiz they'd like to host. This component allows the host to choose the quiz type and provides a way to upload their own questions if they need.
-
host/CreateQuizRoom.vue
This component holds the logic and UI to allow the host to create a new quiz room and get a shareable URL to invite players to that quiz room.
-
host/Leaderboard.vue
This component is visible to the host after every question. It shows the top five scorers in the quiz until that point. If the quiz has ended, the same component displays a full leaderboard with all the participants.
-
host/AdminPanel.vue
This component is visible to the host after every question, giving them options to show the next question when they are ready, or end the quiz midway through.
-
host/LiveStats.vue
This component is visible to the host at the time of a question being displayed. It shows live stats on how many players are still online and out of those, how many have already selected an option for that question. This component can be extended to include the names of the players who have answered, as they do, or any other live stats.
-
player/Playerhome.vue
This component is visible to the player. This is the first page they see when they follow a link shared by their host. It allows them to add their nickname and enter the quiz room created by their host. They’ll be waiting along with other players until the host decides to start the quiz.
If you want to learn more about the source code, you should check out the TUTORIAL.md for a more thorough breakdown.
Before we look at the architecture and design of the app, we need to understand a few concepts based on which this app is built
This app is designed in a way where the server can be considered as the single source of truth and is responsible to maintain the latest quiz state at all times.
All the players send their answers to the server, which in turn collates them together, computes the leaderboard, and sends this info to the host of the quiz. The server is also responsible for publishing new questions, the timer in between questions, and the correct answer after the time has elapsed or all the players have answered.
The client-side script will use this information from the server and render various components accordingly, ensuring all the players are fully in-sync.
The WebSockets protocol, unlike HTTP, is a stateful communications protocol that works over TCP. The communication initially starts off as an HTTP handshake, but if both the communicating parties agree to continue over WebSockets then the connection is elevated; giving rise to a full-duplex, persistent connection. This means the connection remains open for the duration that the application is in use. This gives the server a way to initiate any communication and send data to pre-subscribed clients, so they don’t have to keep sending requests inquiring about the availability of new data. Which is exactly what we need in our quiz!
This project uses Ably Realtime to implement WebSocket based realtime messaging between the server, host and the players. Ably, by default, deals with scalability, protocol interoperability, reliable message ordering, guaranteed message delivery, historical message retention and authentication, so we don't have to. This communication follows the Pub/Sub messaging pattern.
Pub/Sub messaging allows various front-end or back-end clients to publish some data and/or subscribe to some data. For any active subscriptions, these clients will receive asynchronous event callbacks when a new message is published.
In any realtime app, there's a lot of moving data involved. Channels help us group this data logically and let us implement subscriptions per channel. This allowing us to implement the custom callback logic for different scenarios. In the diagram above, each color would represent a channel.
Presence is an Ably feature using which you can track the connection status of various clients on a channel. In essence, you can see who has just come online and who has left using each client's unique clientId
-
All of Ably's messaging limits, broken down by package can be found in a support article.
-
We are currently performing load and performance tests on this framework and will update this guide with that info when it's available. If this is important to you, please leave a message to me directly on Twitter or reach out to Ably's support at [email protected]