Node.js Quick Tutorial

If not familiar with JavaScript then read this tutorial which covers a version of JavaScript compatible with most Internet browsers.

About

Node.js is not a programming language or framework. It is a runtime environment for running JavaScript code without an Internet browser and is popular for building server API's. Node.js is highly-scalable and is generally for data-intensive, or I/O-intensive, apps. It is not for computationally intensive applications like data encoding. This will just tie-up the system and degrade performance.

Node.js is a rapid application development (RAD) tool. While JavaScript in the browser is very messy, it can be written using the latest specification in Node.js which means that clean, efficient code can be written. Node.js has a large number of open-source libraries written for it to make it even better.

Node.js uses Google's latest JavaScript engine which translates JavaScript code to run on a machine. In 2009, Ryan Dahl got the idea to run JavaScript without the browser, so he embedded it in a program written in C++ and named it node.exe. Objects specific to the browser are not included in Node.js. Instead it is has several new objects for doing things like reading and writing data or creating HTTP servers, to name a few common ones.

Node.js is built around asynchronous command/operations.

Node.js Installation

This tutorial will use an installation of Ubuntu Linux v.18.04 to proceed with. It can safely be installed on a virtual machine if hardware is not available.

First, create a directory to work from. From a command prompt/terminal: mkdir node_project and cd node_project

If not already at the terminal, open up a terminal window. Execute node --version. This will show if Node.js is already installed on the machine.

If Node.js is not already installed, then issue a sudo apt install nodejs command which will install it. This can also be used to update it. If using MacOS or Windows, go to nodejs.org to download and install it.

One of the technologies that the text editor Visual Studio Code was designed for is Node.js. Use the Ubuntu Software library to install Visual Studio Code. Visual Studio Code is available for MacOS and Windows, too.

Coding for Node.js

The document object is not available to Node.js because it is part of the browser. But other, powerful objects are available to it. First, a very simple hello world program.

First App: Hello World

In Visual Studio Code, open the folder previously created. Create a new file and name it hello.js. Enter the code console.log(`hello world`);. Notice that tick marks are used for the quotes which is using modern JavaScript. Hit Ctrl+F5 to execute this code. The debug console window will open and display "hello world" in it. Note: to stop the execution of code or to stop debugging hit Shift+F5.

Modules

In Node.js, each file is considered a module. The variables and functions defined in the file are scoped to the module but they can be exported. Modify hello.js to export a function.

// hello.js
function sayHello () {
    console.log(`hello world`);
}

module.exports.sayHello = sayHello;

And the driver code for the hello module requires the use of the require keyword. require is unique to Node.js. Also notice that helloModule is defined as a constant with the const keyword. This prevents it from being changed accidentally.

// driver.js
const helloModule = require("./hello");

helloModule.sayHello();

From now one, use the file name index.js for the code.

Here is an example of using the os (operating system) module which comes installed with Node.js. Here, it queries the CPU's.

const os = require("os");

console.log(os.cpus());

Here is the output as logged to the console.

[ { model: 'Intel(R) Xeon(R) CPU E3-1290 V2 @ 3.70GHz',
    speed: 3691,
    times: { user: 3647000, nice: 2600, sys: 1061900, idle: 74947800, irq: 0 } },
  { model: 'Intel(R) Xeon(R) CPU E3-1290 V2 @ 3.70GHz',
    speed: 3691,
    times: 
     { user: 3734600,
       nice: 13200,
       sys: 1043200,
       idle: 75078500,
       irq: 0 } } ]

HTTP Module

A powerful module is the HTTP one. With it, an HTTP server can be created that listens for clients looking to exchange data. On createServer(), there is a parameter that takes a function. This is where most of the work is done in this example.

const http = require("http");
const server = http.createServer(function (request, response){
    if(request.url === "/") {
        response.setHeader("Content-Type", "application/json"); // tell the client what kind of data they are going to receive
        response.write(JSON.stringify({ name: "John", username: "JJ", id: 383141974 })); // send the data to the client
        response.end();
    }
});

server.listen(8000);

console.log("listening on port 8000");

Run this code and — on the same machine — open a browser and navigate to http://localhost:8000/.

Everytime a file is changed, the HTTP server must be stopped by hitting Shift+F5 and restarted by hitting Ctrl+F5. There is a utility called nodemon which automatically restarts the HTTP server when a file change is detected.

Express.js for Node.js

Express is a lightweight framework for building web applications with the HTTP module. Here, a CRUD server is going to be created. CRUD is an acronym for Create, Read, Update and Delete (data). This tutorial uses Express.js version 4.x, which has different requirements from earlier versions.

To install NPM Express, issue a sudo apt-get install npm and npm install express. Now, in code, instead of using the http module, use the express module.

const express = require("express");
const path = require("path");

const app = express();


// body parser middleware
app.use(express.json());
app.use(express.urlencoded( { extended: false } )); // this is to handle URL encoded data
// end parser middleware


// custom middleware to log data access
const log = function (request, response, next) {
    console.log(`${new Date()}: ${request.protocol}://${request.get('host')}${request.originalUrl}`);
	console.log(request.body); // make sure JSON middleware is loaded first
    next();
}
app.use(log);
// end custom middleware


// this array functions as a database
const users = [ { id: 1, username: "admin", deleted: false } ];


// enable static files pointing to the folder "public"
// this can be used to serve html, css and client-side js files, for example
app.use(express.static(path.join(__dirname, "public")));


// HTTP GET
// get all users that are not deleted
app.get("/api/users", function(request, response) {
    const ua = new Array();
    for(var i = 0; i < users.length; i++) { // one example of a looping through an array
        if(!users[i].deleted)
            ua.push({ id: users[i].id, username: users[i].username });
    }
    response.json(ua);
});


// HTTP GET
// returns a user with the matching ID
app.get("/api/users/:id", function(request, response) {
    const user = users.find(u => u.id === parseInt(request.params.id) && u.deleted === false);
    if(!user) // return 404 Object Not Found
        return response.status(404).json( { message: `id ${request.params.id} not found`} );
    response.json( { id: user.id, username: user.username } );
});


// HTTP POST
// create a new user (this code does NOT check for duplicate usernames)
app.post("/api/users", function(request, response) {
    const newUser = {
        id: users.length + 1,
        username: request.body.username,
        deleted: false
    };
    if(!newUser.username)
        return response.status(400).json( { message: "\"username\" required" } );
    users.push(newUser);
    response.json( { id: newUser.id, username: newUser.username } ); // send back the new user
});


// HTTP PUT
// update user
app.put("/api/users/:id", function(request, response) {
    const user = users.find(u => u.id === parseInt(request.params.id) && u.deleted === false);
    if(!user) // return 404 Object Not Found
        return response.status(404).json( { message: `id ${request.params.id} not found`} );
    user.username = request.body.username ? request.body.username : user.username;
    response.json( { id: user.id, username: user.username } ); // send back the updated user
});


// HTTP DELETE
// delete user
app.delete("/api/users/:id", function(request, response) {
    const user = users.find(u => u.id === parseInt(request.params.id) && u.deleted === false);
    if(!user) // return 404 Object Not Found
        return response.status(404).json( { message: `id ${request.params.id} not found`} );
    user.deleted = true;
    response.json( { message: `id ${request.params.id} deleted` } );
});


// set port from environment variable, or 8000
const PORT = process.env.PORT || 8000;

app.listen(PORT, () => console.log(`listening on port ${PORT}`));

To test these GET, POST, PUT and DELETE requests, use Postman which can be downloaded from the UBUNTU software store. POST requests (to create new users) will have JSON in the body. For exmaple:

{ "username": "Xavier" }

UUID for Express.js

UUID is a module for creating unique identifiers. Install UUID by issuing a npm install uuid command. Now, run this code and access it from a browser to reveal a globally unique identifier.

const http = require("http");
const uuid = require("uuid");
const server = http.createServer(function (request, response) {
    if(request.url === "/") {
        response.setHeader("Content-Type", "text/plain");
        response.write(uuid.v4()); // this can be used to create identifiers for records
        response.end();
    }
});

server.listen(8000);

console.log("listening to port 8000");

Now, the above EXPRESS server code rewritten to use UUID.

const express = require("express");
const uuid = require("uuid");
const path = require("path");

const app = express();


// body parser middleware
app.use(express.json());
app.use(express.urlencoded( { extended: false } )); // this is to handle URL encoded data


// custom middleware to log data access
const log = function (request, response, next) {
    console.log(`${new Date()}: ${request.protocol}://${request.get('host')}${request.originalUrl}`);
	console.log(request.body);
    next();
}
app.use(log);
// end middleware


// this array functions as a database
const users = [ { id: uuid.v4(), username: "admin", deleted: false } ]; // UUID USED HERE


// enable static files pointing to the folder "public"
app.use(express.static(path.join(__dirname, "public")));


// HTTP GET
// get all users
app.get("/api/users", function(request, response) {
    const ua = new Array();
    for(var i = 0; i < users.length; i++) {
        if(!users[i].deleted)
            ua.push({ id: users[i].id, username: users[i].username });
    }
    response.json(ua);
});


// HTTP GET
// returns a user with the matching ID
app.get("/api/users/:id", function(request, response) {
    const user = users.find(u => u.id === request.params.id && u.deleted === false);
    if(!user) // return 404 Object Not Found
        return response.status(404).json( { message: `id ${request.params.id} not found`} );
    response.json( { id: user.id, username: user.username } );
});


// HTTP POST
// create a new user (this code does NOT check for duplicate usernames)
app.post("/api/users", function(request, response) {
    const newUser = {
        id: uuid.v4(),  // UUID USED HERE
        username: request.body.username,
        deleted: false
    };
    if(!newUser.username)
        return response.status(400).json( { message: "\"username\" required" } );
    users.push(newUser);
    response.json( { id: newUser.id, username: newUser.username } ); // send back the new user
});


// HTTP PUT
// update user
app.put("/api/users/:id", function(request, response) {
    const user = users.find(u => u.id === request.params.id && u.deleted === false);
    if(!user) // return 404 Object Not Found
        return response.status(404).json( { message: `id ${request.params.id} not found`} );
    user.username = request.body.username ? request.body.username : user.username;
    response.json( { id: user.id, username: user.username } ); // send back the updated user
});


// HTTP DELETE
// delete user
app.delete("/api/users/:id", function(request, response) {
    const user = users.find(u => u.id === request.params.id && u.deleted === false);
    if(!user) // return 404 Object Not Found
        return response.status(404).json( { message: `id ${request.params.id} not found`} );
    user.deleted = true;
    response.json( { message: `id ${request.params.id} deleted` } );
});


// set port from environment variable, or 8000
const PORT = process.env.PORT || 8000;

app.listen(PORT, () => console.log(`listening on port ${PORT}`));

MySQL Database Server

MySQL is a free relational database server that is a good choice for a Linux installation.

MySQL Installation

Install MySQL by issuing the commands sudo apt-get update and sudo apt-get install mysql-server. If using "ufw" firewall, allow remote access to the MySQL server by issuing the command sudo ufw allow mysql.

Start the MySQL service by issuing the command systemctl start mysql and to make it launch at startup issue the command systemctl enable mysql. A password may need to be entered several times.

Start the MySQL shell by issuing the command sudo mysql -u root -p. This will bring up the mysql> prompt. Set the root password by executing the text UPDATE mysql.user SET authentication_string = PASSWORD('abc123') WHERE User = 'root'; then FLUSH PRIVILEGES;

Create Database

To create a database use CREATE DATABASE database_name;. To create a user for the new database: INSERT INTO mysql.user (User, Host, authentication_string, plugin, ssl_cipher, x509_issuer, x509_subject) VALUES ('database_user', 'localhost', PASSWORD('abc123'), 'mysql_native_password', '', '', ''); and FLUSH PRIVILEGES;

Grant permissions to the new user by issuing these commands: GRANT ALL PRIVILEGES ON database_name.* to database_user@localhost; and FLUSH PRIVILEGES;

Create Table

For this tutorial, create a new table named "Users": USE database_name; then CREATE TABLE Users (UID CHAR(36) NOT NULL, PRIMARY KEY(UID), Username VARCHAR(20) NOT NULL); then INSERT INTO Users (UID, Username) VALUES ('8a32ddc3-16de-4c5e-a06c-54ef2e166a48', 'admin');

At the mysql> prompt type exit.

Install MySQL driver for Node.js

Install the MySQL driver by issuing the command npm install mysql. Now, the previous EXPRESS server code rewritten to use MySQL.

const express = require("express");
const uuid = require("uuid");
const path = require("path");
const mysql = require("mysql");

const app = express();

// body parser middleware
app.use(express.json());
app.use(express.urlencoded( { extended: false } )); // this is to handle URL encoded data

// custom middleware to log data access
const log = function (request, response, next) {
    console.log(`${new Date()}: ${request.protocol}://${request.get('host')}${request.originalUrl}`);
    console.log(request.body);
    next();
}
app.use(log);
// end middleware


// enable static files pointing to the folder "public"
app.use(express.static(path.join(__dirname, "public")));


// the user "root" should not be used in production because it has greater privileges than is needed to simply create, read, update and delete data.
const conOptions = { host: "localhost", user: "database_user", password: "abc123", database: "database_name" };


// HTTP GET
// get all users
app.get("/api/users", function(request, response) {
    var connection = mysql.createConnection( conOptions );
    connection.connect(function(error) {
        if (error) throw error;
        connection.query(`SELECT UID, Username FROM Users;`, function(error, result) {
            connection.destroy();
            if (error) throw error;
            const ua = new Array();
            for(var i = 0; i < result.length; i++) {
                ua.push({ id: result[i].UID, username: result[i].Username });
            }
            response.json(ua);
        });
    });
});


// HTTP GET
// returns a user with the matching ID
app.get("/api/users/:id", function(request, response) {
    var connection = mysql.createConnection( conOptions );
    connection.connect(function(error) {
        if (error) throw error;
        connection.query(`SELECT UID, Username FROM Users WHERE UID='${request.params.id}';`, function(error, result) {
            connection.destroy();
            if (error) throw error;
            if(result.length === 0) // return 404 Object Not Found
                return response.status(404).json( { message: `id '${request.params.id}' not found`} );
            response.json( { id: result[0].UID, username: result[0].Username } );
        });
    });
});


// HTTP POST
// create a new user (this code does NOT check for duplicate usernames)
app.post("/api/users", function(request, response) {
    // TO DO: check that request.body.username is not too long
    const newUser = {
        id: uuid.v4(),
        username: request.body.username,
    };
    if(!newUser.username)
        return response.status(400).json( { message: "\"username\" required" } );
    var connection = mysql.createConnection( conOptions );
    connection.connect(function(error) {
        if (error) throw error;
        connection.query(`INSERT INTO Users (UID,Username) VALUES ('${newUser.id}','${newUser.username}');`, function(error, result) {
            connection.destroy();
            if (error) throw error;
            response.json( newUser ); // send back the new user
        });
    });
});


// HTTP PUT
// update user
app.put("/api/users/:id", function(request, response) {
    // TO DO: check that request.body.username is not too long
    var connection = mysql.createConnection( conOptions );
    connection.connect(function(error) {
        if (error) throw error;
        connection.query(`UPDATE Users SET Username='${request.body.username}' WHERE UID='${request.params.id}';`, function(error, result) {
            connection.destroy();
            if (error) throw error;
            if(result.affectedRows === 0)
                return response.status(404).json( { message: `id '${request.params.id}' not found`} );
            response.json( { id: request.params.id, username: request.body.username } ); // send back the updated user
        });
    });
});


// HTTP DELETE
// delete user
app.delete("/api/users/:id", function(request, response) {
    var connection = mysql.createConnection( conOptions );
    connection.connect(function(error) {
        if (error) throw error;
        connection.query(`DELETE FROM Users WHERE UID='${request.params.id}';`, function(error, result) {
            connection.destroy();
            if (error) throw error;
            if(result.affectedRows === 0)
                return response.status(404).json( { message: `id '${request.params.id}' not found`} );
                response.json( { message: `id '${request.params.id}' deleted` } );
        });
    });
});


// set port from environment variable, or 8000
const PORT = process.env.PORT || 8000;

app.listen(PORT, () => console.log(`listening on port ${PORT}`));

This is the end of this quick tutorial.

Feedback
close