Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about Javascript.

In the post Node.js and NPM: don’t be afraid of them! I showed you how to install Node.js and NPM, so it’s time to use it for something more interesting than just prompting some random text.

With Node.js we can create a web server.

Sooner or later in your career as a JavaScript developer, you will need to configure a web server.


All posts in this tutorial series:

Step 1: how to install Node.js and npm, and what to do with them

Step 2: how to set up a web server with Node.js, and why you should do it.

Step 3: using Visual Studio Code to develop HTML5 games with Phaser and webpack.

Step 4: how to publish and distribute your HTML5 games with Phaser and webpack.

Step 5: moving from JavaScript to TypeScript.


In some cases, writing a JavaScript script embedded in a HTML page and simply running it in the browser will no longer work, especially if you are making HTML5 games.

A web page that just won’t work

Create a file called simplepage.html with this content:

HTML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
    </head>
    <body>
        <script>
            fetch('./content.txt').then(response => response.text()).then(text => document.body.innerHTML = text);
        </script>
    </body>
</html>

As you can see, it should read the content of content.txt file and output it to the body.

Create content.txt file in the same path of simplepage.html with this content:

Hello world, this is JavaScript running again!

And now just open simplepage.html in your web browser. I use Chrome, but you can use the one you prefer.

You should see something like this:

If you open the console, you will see an error, saying something about CORS policy.

An easy and simple page like this one just does not work once opened directly in the web browser.

What is CORS policy?

Cross-Origin Resource Sharing (CORS) is a security feature implemented in web browsers that governs how web pages from one domain can request and access resources from another domain.

It is a crucial security mechanism to prevent potential security vulnerabilities that can arise when different websites interact with each other.

When a web page on one domain (the “origin”) tries to make a request for resources, such as data, images, or scripts, from another domain, the browser enforces the Same-Origin Policy by default.

This policy restricts web pages from making requests to domains different from the one that served the web page itself.

And no, being on the same folder does not mean being on the same domain, as you just witnessed. How can we overcome this? With a web server.

What is a web server?

A web server is a computer program or software that serves requests made by clients over the World Wide Web (WWW) or a private network.

It acts as a mediator between the client, typically a web browser, and the server-side resources such as web pages, images, videos, and other files.

Some popular web server software includes Apache HTTP Server, Nginx, and Node.js.

We already know how to install Node.js, how can we turn it into a web server?

My first Node.js web server

Let’s create a new file called nodeserver.js with this content:

JavaScript
const http = require('http');
 
const hostname = '127.0.0.1';
const port = 3000;
 
const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Hello World');
});
 
server.listen(port, hostname, () => {
    console.log(`Server running at http://${hostname}:${port}/`);
});

Now execute the JavaScript file we just created typing this in your command-line shell, once in the same directory:

node nodeserver.js

This way:

As you can see, the script will keep running until you stop it, on Windows, with Ctrl-C.

Don’t stop it at the moment, and open your browser to http://127.0.0.1:3000.

This is what you should see:

Which is a local web server showing the content created on the fly by nodeserver.js.

This is something, but it’s not what we are looking for. We want our web server to be able to load a page which is already on our disk, rather than creating one on the fly.

A web server able to launch a page

Let’s create another file called nodepagelauncher.js with this content:

JavaScript
const http = require('http');
const fs = require('fs')
 
const hostname = '127.0.0.1';
const port = 3000;
 
const server = http.createServer((req, res) => {
    res.writeHead(200, { 'content-type': 'text/html' })
    fs.createReadStream('simplepage.html').pipe(res)
});
 
server.listen(port, hostname, () => {
    console.log(`Server running at http://${hostname}:${port}/`);
});

This is trying to build a web server and call simplepage.html, which is the page we tried to run before, being blocked by the CORS policy.

And let’s execute it, as usual, with

node nodepagelauncher.js

This way:

Point the browser to http://127.0.0.1:3000 and you will see… nothing. No errors, but also no content.

But if you look at the elements, or at the web page source, you will see the page has been successfully loaded, it’s just it’s not fetching content.txt.

This happens because our server is only able to read simplepage.html.

At each request, it will always deal with simplepage.html.

We need the web server to be able to read more pages.

A web server able to launch more pages

Let’s create a new file called nodemultiplepagelauncher.js with this content:

JavaScript
const http = require('http');
const fs = require('fs')
 
const hostname = '127.0.0.1';
const port = 3000;
 
const server = http.createServer((req, res) => {
    console.log('serving ' + req.url);
    switch(req.url) {
        case '/content.txt' :
            res.writeHead(200, { 'content-type': 'text/plain' })
            fs.createReadStream('content.txt').pipe(res);
            break;
        default :
            res.writeHead(200, { 'content-type': 'text/html' })
            fs.createReadStream('simplepage.html').pipe(res);              
    }   
});
 
server.listen(port, hostname, () => {
    console.log(`Server running at http://${hostname}:${port}/`);
});

This time we should handle both a default page, our simplepage.html file, and content.txt file.

Then, you know what to do: launch it with

node nodemultiplepagelauncher.js

This way:

And finally, if you refresh the page at http://127.0.0.1:3000, you should see simplepage.html properly rendered.

Thanks to a web server, we were able to render a web page we would never be able to render by just running it in a web browser.

And look at the command-line shell:

It’s serving the root page, which we called simplepage.html, then content.txt and also favicon.ico.

We can say we managed to handle imported files.

Since HTML5 games make intensive use of external content, such as images and sound files, it’s very important to have a web server installed when performing tests locally.

Obviously, it would be impossible to handle all different resources with a switch statement, so we need a more universal web server script.

A web server just working fine

There is no need to reinvent the wheel, so here is a nice script by The Jared Wilcurt which is perfect for our needs:

JavaScript
// npm-Free Server by The Jared Wilcurt
// All you need to run this is an installed copy of Node.JS
// Put this next to the files you want to serve and run: node server.js

// Require in some of the native stuff that comes with Node
var http = require('http');
var url = require('url');
var path = require('path');
var fs = require('fs');
// Port number to use
var port = process.argv[2] || 8000;
// Colors for CLI output
var WHT = '\033[39m';
var RED = '\033[91m';
var GRN = '\033[32m';

// Create the server
http.createServer(function (request, response) {

    // The requested URL, like http://localhost:8000/file.html => /file.html
    var uri = url.parse(request.url).pathname;
    // get the /file.html from above and then find it from the current folder
    var filename = path.join(process.cwd(), uri);

    // Setting up MIME-Type (YOU MAY NEED TO ADD MORE HERE) <--------
    var contentTypesByExtension = {
        '.html': 'text/html',
        '.css':  'text/css',
        '.js':   'text/javascript',
        '.json': 'text/json',
        '.svg':  'image/svg+xml'
    };

    // Check if the requested file exists
    fs.exists(filename, function (exists) {
        // If it doesn't
        if (!exists) {
            // Output a red error pointing to failed request
            console.log(RED + 'FAIL: ' + filename);
            // Redirect the browser to the 404 page
            filename = path.join(process.cwd(), '/404.html');
        // If the requested URL is a folder, like http://localhost:8000/catpics
        } else if (fs.statSync(filename).isDirectory()) {
            // Output a green line to the console explaining what folder was requested
            console.log(GRN + 'FLDR: ' + WHT + filename);
            // redirect the user to the index.html in the requested folder
            filename += '/index.html';
        }

        // Assuming the file exists, read it
        fs.readFile(filename, 'binary', function (err, file) {
            // Output a green line to console explaining the file that will be loaded in the browser
            console.log(GRN + 'FILE: ' + WHT + filename);
            // If there was an error trying to read the file
            if (err) {
                // Put the error in the browser
                response.writeHead(500, {'Content-Type': 'text/plain'});
                response.write(err + '\n');
                response.end();
                return;
            }

            // Otherwise, declare a headers object and a var for the MIME-Type
            var headers = {};
            var contentType = contentTypesByExtension[path.extname(filename)];
            // If the requested file has a matching MIME-Type
            if (contentType) {
                // Set it in the headers
                headers['Content-Type'] = contentType;
            }

            // Output the read file to the browser for it to load
            response.writeHead(200, headers);
            response.write(file, 'binary');
            response.end();
        });

    });

}).listen(parseInt(port, 10));

// Message to display when server is started
console.log(WHT + 'Static file server running at\n  => http://localhost:' + port + '/\nCTRL + C to shutdown');

You can save it as server.js, and launch it with:

node server.js

this way:

If you get some kind of security alert, like me, just allow access, then point your browser to http://localhost:8000/ to get, unfortunately, another error.

It’s normal, because now we are running something like an actual web server, looking for a index.html page.

Rename simplepage.html to index.html and refresh the browser, and you should have your page displayed properly:

Will it work with any page? Sure!

Let’s try with the example built with Phaser Editor 2D.

First, let’s try to open it directly from the web browser, with no web server running:

We are getting a CORS error because we aren’t allowed to load the Phaser 3 logo, and all we can see is that wireframe placeholder.

Once started the web server, the page will be properly loaded with all assets, and we can see it working flawlessly.

This is why you should use Node.js to start a web server.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.