One Approach To A Virtual Card Game With Firebase Realtime Database Part III: Server

Myles Tyrer
6 min readNov 24, 2020

This is part 3 in a series about making an online cribbage game using the Firebase Realtime Database. In part I, I set up a Firebase Database and created a virtual deck of cards with a shuffle method. In part II, I covered the rule of using firebase events to change the database and having the database control the DOM. In Part III I will start to show my approach to making a server that will create a unique and shareable url that allows people to play cribbage with another person no matter where they are in the world!

In this article I will create a Node server running Express to listen for and handle requests, and also use Node’s file system module to create and delete some unique game files. For the views, I will use ejs to translate my current html into a more dynamic format that accepts variables.

Overview

So, in essence, here’s what the server will do. When a request comes in to the root, ‘/’, the server will respond with a page that shows options to either create a new game, or join an existing game. Choosing to create a new game will create a unique 5 character key, and a temporary ejs file with that key in the filename, (something like game_vg6e4.ejs). Then it will redirect the response to serve up that newly created file. The important thing will be to pass the key into the client side javascript, creating a new unique firebase node just for the game with that key.

Lastly, to make sure the disk space doesn’t get out of hand, every 24 hours, the server will sweep the directory and delete any game ejs files that are more than 24 hours old.

Setting Up A Node Server

First we create a project with ‘npm init,’ then install express with ‘npm install express’, and the body parser with ‘npm install body-parser’. Create a file called App.js and import these modules:

const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const fs = require('fs');
const fsPromises = require('fs').promises;

This code sets up the server, the view engine, and the body parser, and will listen for requests on port 3000.

const app = express();
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname,'views'));
app.use(bodyParser.urlencoded({extended:false}));
app.use('/public', express.static("public"));
app.listen(3000)

Then to handle get requests to ‘/’ , it will serve up the intro page where users can make a new game or join an existing game.

app.get('/', (req, res, next) => {
res.render('intro');
})

Here’s the HTML inside the body of the intro page;

<main>
<div class="modal-wrapper">
<div class="message-wrapper">
<h1>Online Cribbage</h1>
<p>Enter the private gamekey you got from a friend or create a new game to play.</p>
</div>

<div class="form-wrapper">
<form id="join-game-form" action="/find-game" method="POST">
<label for="gameKey" class="gameKey-label">Private Game Key:</label>
<input id="gameKey" name="gameKey" type="text">
<br class="linebreak">
<button id="gameKey-button" type="submit">GO</button>
</form>
</div>

<div class="divider">OR</div>
<div class="form-wrapper">
<form action="/game" method="POST">
<button type="submit" class="new-game-button">START NEW GAME</button>
</form>
</div>
</div>
</main>

Details on my approach to styling are outside the scope of this article. I’ll do a follow-up article covering my approach to styling this project with the BEM model in scss. Anyway, here’s how the page looks to the user after applying some styles.

intro page

The START NEW GAME button on the intro page is inside a form that sends a POST request to ‘/game’ when clicked. In the next section we’ll see how the server handles that request.

Creating New Games And Then Routing Requests

Now that the skeleton is in place, the code needs to handle some different requests and route them correctly. This is where the magic happens.

When the user clicks “START NEW GAME” on the intro page, they will send a POST request to ‘/game’, so to catch it we need a middleware function listening for that specific type of request to that url.

// middleware function
app.post('/game', (req, res, next) => {
// read and write code here}

The code will read the contents of a template file “index.ejs” and then write that data into a new file with a unique gameKey. To read and write, we’ll use Node’s fs promises module inside the express middleware function. Using promises here ensures that all the data in the template file will be collected before we begin to write it into the new file, avoiding potential bugs.

Side note: I am only just dipping my toe into Node streams in this project, but streams have the potential to be a very powerful resource that I plan on learning much more about in the future.

Anyway, here is the asynchronous code for reading and writing the files.

const chars = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];let gameKey = '';fsPromises.readFile('./views/index.ejs')
.then((buffer) => {
// create the gameKey
for(let i=0; i<5; i++) {
gameKey += chars[Math.round(Math.random() * 35)]
}
const oldContent = buffer.toString();
return fsPromises.appendFile(`./views/gameFiles/game_${gameKey}.ejs`, oldContent);
}.then(() => {
res.redirect('/game/' + gameKey);
})

First, the above code will read the contents of the file located at ‘./views/index.ejs’, and store it in buffer. Then it creates the 5 character gameKey by looping over the chars array five times. Finally it returns the fs promises appendFile method which writes a new file with the template content. In the last block, after all the previous code has been guaranteed to run, the request gets redirected to a url pointing to the newly created file.

Serve The New File

Now, to handle the redirected get request is fairly straightforward. In a new middleware function below the previous one, just render the new file and pass the gameKey in as an ejs variable (called gameNum here to avoid double naming). In the next section, I’ll use the gameNum variable in the rendered ejs file on the client side.

app.get('/game/:url', (req, res, next) => {
let url = req.url.substr(6);
res.render(`./gameFiles/game_${url}`, {
gameNum: url
});
})

Creating Private Firebase Nodes On The Client Side

To see how the client side JS handles the firebase setup, see the previous article. Here I’m just going to cover the changes needed to accommodate the gameNum ejs variable.

Because ejs variables cannot be passed into the client side javaScript file, I’m storing the gameNum in a data attribute on a <span> tag in the HTML, then accessing that attribute in the client side js. Learn more about the data attribute in its documentation.

<!-- THIS SPAN HOLDS DATA FROM EJS -->
<!-- IN JS ACCESS IT WITH document.querySeletor('#data').dataset -->
<span id="data" data-gameKey="<%= gameNum %>"></span>

This approach in this circumstance feels suboptimal to me, and I would be happy to hear from anyone who has an idea for a better implementation.

Moving on, the client side JS will have to be tweaked slightly to get the gameNum data from the data span above and then use it to create a unique firebase Node.

let gameKey = document.querySelector('#data').dataset.gamekey;
let DeckReference = firebase.database().ref(`gameKey_${gameKey}`);

Now when the client loads the page the javaScript will look at the data in the <span>’s attribute, and create a Firebase node tied to that unique key. Now all the references to the database node (like the players’ hands, the crib, scoreboard, etc.) will be children of this new unique node, creating a private database for this one page.

Put it all together and we have a working server that generates shareable, unique urls for people to play cribbage with a friend!

The server in action on Firebase

Conclusion

That concludes the creation of new files and Firebase nodes for a unique game of cribbage. In the next article I’ll show how to clean up after the users are finished playing the game.

I’d love to hear your comments on my implementation here. Thanks!

--

--

Myles Tyrer

I’m a web developer focusing on interactive projects using React and Nodejs.