The Worst Voting Platform is a real time yes\no community voting platform powered by Pusher. Submit a question, and everyone votes yes or no. It's that simple.
You can see the demo by going to theworstvotingplatform.com
The code is available on Github and has three components to get you up and running:
This project spawned because I wanted a public real time voting platform where anyone anywhere could ask a yes\no question and get an answer from an audience. It could be what they want for dinner, if X is a good movie, etc. It came from watching my co-workers play a quiz game at lunch on their smartphones. I began really wondering how on earth they could possibly, with very little latency, show the next question at the same time to everyone everywhere. Pusher could be the solution to this problem. With this contest being announced, I finally had a reason to play with it and see if I could make something that could do something like that.
I had a lot of concerns about using pusher to publish voting events and question events. I had some issues with requiring authentication of users. Realistically, I could let my server just assume everyone is authenticated but that seemed a little odd to do (that voids the whole point of authenticating). Likewise, I would gain and lose some ability to deal with tampering of votes. The bonus is Pusher would throttle the abuse after 10 - the downside is they could just open up 9 - 10 windows or connections and I would still have no idea who they are. I don't have a nice solution to this problem yet.
For now, I decided that it would probably be more straight forward to implement a very slim API layer to feed the data into redis.
I've used Node, but I had no true understanding of it going into this. I am not a Node developer, and my JS experience stops at frontend. After about 25 minutes, I found out that I have absolutely no idea how to make a process "run forever". Traditionally, I would use a while loop and simply check, sleep, check, sleep, etc. However, this did not work at all in Node. I attempted to design a while loop that slept, but the sleeping was holding up unrelated promises. For the backend to work, it needed to do the following (order mattered as well):
- Wait for a question from a user
- Broadcast the question when one is available
- Wait X seconds
- Count the total for "Yes" and "No" and broadcast the results
I knew that using redis was a safe bet (rpush, lpop) for a stack like implementation of questions to be served. Additionally, I could also use redis to store the votes. Being lazy, I ended up just using two keys (yes and no keys, literally), and a question stack.
I not only needed it to run forever but also needed it to be running non-stop. I googled all over for "node run forever", "node don't exit", etc. Being hopelessly confused at this point, I asked my friend, author of Chinnbot.tv, to help me out. He sent me some videos about the event loop in Node. I began to understand a bit about how to handle this, although I'm still slightly confused on the specifics of how node really knows when there is no more work. Here is what I ended up with:
- Set yes and no to zero, call async function to poll redis for new questions, sleep 5 seconds if there is no question.
- Once we have a question, emit a 'new question' event
- Broadcast the new question, then emit 'voting-done' after 8 seconds
- Voting-done will tally the questions, and then broadcast the results
- 5 seconds later, we will return to #1
I fully expected this model to overflow the stack, since, it appears to me, they are all recursively operating. However, it looks like the event loop doesn't really have that problem since it would place it in a queue and run in the next tick (hopefully I'm correct in my understanding of this).
Everything else is in Javscript land, might as well make the web server use it too. Configuring nginx upstream to express was easy and I used foreverJS to manage it (as well as the backend). I was very pleased with how easy the whole experience was.
I used Vue and Tailwind for the frontend. Working with Vue is always fun, especially with the vue-cli to generate your configs. I wanted to use Prettier, but it seemed to be at odds with the ES6 recommended standard in eslint (disagreement over semicolons). I ended up just sticking with the standard set and vue essential. I also took this time to use Atom instead of Sublime - I love it.
Dear JS Developers: What do you prefer for linting?
I didn't plan on actually having anything to submit for this contest due to some time constraints and being very close to burnout. However, when it turned out working I couldn't help but laugh. So, it started as a joke but I can't help but feel slightly attached to it at this point :)
I have a lot of interesting ideas with how we can implement Pusher in a production environment for some real world uses. I can see this product making it into our stack for real time notifications of backend events. It's so simple and easy that I need to be careful that I pick it when it's right and not because I just want to play with it. I had a lot of fun building this thing, and it's a nice proof of concept if I ever wanted to take the next step to building "The Best Voting Platform".
Probably a twitch stream feature. It would be cool for a streamer to be able to ask questions and people can just click the button to vote right on the video. I would switch it to use client events to vote for this, rather than my own tiny backend.