JavaScript+Servers+-+Module+2+-+Noroff+Guide+-+PDF
JavaScript+Servers+-+Module+2+-+Noroff+Guide+-+PDF
Noroff Guide
Table of Contents
Introduction
Welcome to Module 2 of JavaScript Servers.
Learning outcomes
In this lesson, we are covering the following knowledge learning
outcomes:
The candidate has knowledge of concepts, processes, and tools used
in node-based JavaScript projects and solutions.
Introduction
We have already learnt about the basics of NodeJS and NPM. Moreover, we learnt
how to set up a simple server using the NodeJS HTTP module and added simple
routing, which handles HTTP GET and POST requests.
However, to create a server, web frameworks are usually used instead. ExpressJS
is a very popular web framework hosted within a NodeJS environment. It provides
multiple tools to make features such as routing or setting application settings much
easier.
In this lesson, we’ll learn about the basics of ExpressJS – how to install it and use it
to create a simple server. Moreover, we’ll look at how it helps us with routing and
adding ‘middleware’ request processing.
After we have discussed the basics of ExpressJS – how to create simple
applications, implement routing and use middleware, we’ll learn how to display our
data to the users.
Learning outcomes
In this lesson, we are covering the following knowledge learning
outcome:
The candidate has knowledge of concepts, processes, and tools of
server-based JavaScript solutions.
Introduction to ExpressJS
ExpressJS is one of the most popular web frameworks for NodeJS. It is used to
ease building websites, create web applications, and REST APIs with JavaScript.
READ
1. Website: https://expressjs.com/
2. Page: 5.x API by ExpressJS.
WATCH
We can also install the application generator, which will help us later with, for
example, creating a skeleton of the web application or adding template engines to
our project. To do this, let’s use the following command:
npm install express-generator -g
The -g flag will tell npm to install the package globally, so we’ll be able to use it in
all our future projects. After installation, we should ensure that our dependencies
were added correctly to our package.json file. It should look as follows:
{
"dependencies": {
"express": "^4.18.1",
}
}
Let’s create a very simple app with this generator. Let’s use the command:
express myapp
where “myapp” is the name of our application. There are many other options for
generating the application. We can see them after typing the command:
express -h
You might be shown a permission error that Express is not a command. This error
is encountered mainly in Windows 10 when VS Code terminal uses Power Shell as
If everything was done correctly, open the link http://localhost:3000/in the browser.
You should get the following result:
This is the home page of our Express application. It is a default template for each
application generated with the express-generator package. It is displayed because
http://localhost:3000/ is the home address of our local application.
WATCH
Project overview
Let’s check the generated structure of our project:
Variable process.env.PORT isn’t set yet (we’ll learn about this in the next modules),
so we need to provide port as the string (current value – ‘3000’)
We already know that the node_modules folder contains files from our
dependencies, so we won’t edit anything there.
We’ll place our resources (such as images), JavaScript programs, or CSS
stylesheets in a public folder.
The routes folder contains files responsible for our request. By default, we can
handle two GET requests to http://localhost:3000/ and http://localhost:3000/users
We already saw the result of the first one, so let’s check the result of the second by
opening the link in a browser while the application is running:
module.exports = router;
In Express web apps, each endpoint is handled in a separate JavaScript file, unlike
in solution, with only a standard NodeJS HTTP module. This way, we can easily
implement new endpoints while keeping the application structure clean.
The views folder contains our template engine files, generating HTML files to
display to the user. By default, Jade is used as the template engine. We won’t edit
these files in this lesson.
The “app.js” file creates the instance of the Express application with the command:
var app = express();
and exports it as the “app” module. We can use all Express functionalities just by
requiring this module.
WATCH
Routing
To handle an HTTP request with Express, all we need to do is use the
following:
app.get()
app.post()
app.put()
app.delete()
As the first parameter, we must provide a path to the endpoint (for example ‘/’ or
‘/user’).
As the second, we need to provide a pair of parameters: request and response. We
can use the response parameter to respond to the client. We can chain different
methods to the same path – the code below does the same thing as the code
above:
app.route('/').get((req, res) => {
res.send('Hello World!')
}).post((req, res) => {
res.send('Got a POST request')
})
module.exports = router;
For our application to see that code, we need to include this file in an “app.js” file.
Let’s create a helloRouter, similar to other routers:
var helloRouter = require('./routes/hello');
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/hello', helloRouter);
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ?
err : {};
module.exports = app;
The client can pass parameters in the path. We can treat the element of the path as
a parameter by adding a “:” sign before it in the path. These values are stored in
the req.params object.
Let’s add new code to the “users.js” file, which handles
the/users/:userId/books/:bookId path and returns the req.params object:
router.get('/:userId/books/:bookId', (req, res) => {
res.send(req.params)
})
To understand what is happening there, look at the URL and path provided as the
first parameter of the get() method:
http://localhost:3000/users part is the default one for each handler in users.js file.
The rest is /34/books/8989:
34 is passed as userId (there is “:” before userId in the path)
books text is the same in both paths
8989 is passed as the bookId variable.
The entire req.params object (responsible for storing route parameters) is displayed
as the result.
We can also provide a callback function (often named a route handler). We’ll pass it
as the function’s third parameter (after res) and name it “next”.
READ
WATCH
Middleware
Middleware functions have access to the request object, response object, and
next() function. We can use them to execute some actions before processing the
request:
Let’s see how to create a simple middleware function logging into the console.
First, temporarily add the following function into “app.js”:
const myLogger = function (req, res, next) {
console.log('LOGGED')
next()
}
inside the mentioned function in the app.js file, restart the application, and access a
non-existing endpoint (to trigger an error).
We can also use third-party middleware to add new functionalities to Express. For
example, we can install a cookie parser with the command:
npm install cookie-parser
WATCH
Static HTML
In this chapter, we’ll try to present an HTML page to the user instead of the strings
or JSON we have been presenting so far. Download any image and save it in the
images folder in your project (this picture will be used in the example:
https://www.freeimages.com/photo/green-frog-1361810).
Create an “index.html” file in the same place where the “app.js” file is located. This
file should then contain the following HTML code:
<!DOCTYPE html>
<html>
<head>
<title>App</title>
It’s a straightforward HTML page with one text paragraph and a downloaded image.
(Change the picture name, “/frog.jpg”, in the HTML code to match the name of your
downloaded image).
We want to see this website on the starting endpoint (http://localhost:3000/). To do
this, open the “index.js” file in the router folder. We can use the router.sendFile
command to return the file (for example, an HTML page) in response.
However, as our HTML file is located in a different place, we need to use the path
module to access it. The path module is built-in into NodeJS, providing utilities for
working with file and directory paths. Let’s change the file so it looks as follows:
var express = require('express');
var path = require('path');
var router = express.Router();
module.exports = router;
module.exports = router;
Static resources aren’t only pictures. It can also be CSS styles, for example.
Let’s add a new stylesheet in which we’ll do the following:
make the text green
enlarge the fonts
set the size of the image
centre both the paragraph and the image
img {
width: 200px;
margin: auto;
display: block;
}
This CSS file format should be familiar. Now link it in the “index.html” file as a
stylesheet:
index.html:
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<link rel="stylesheet"
href="../public/stylesheets/mainPage.css">
</head>
<body>
<p>Hello!</p>
<img src="../public/images/frog.jpg">
</body>
</html>
WATCH
Once the button is clicked, you should get the following results:
See the full list of template engines linked in the READ section.
All these engines are easy to install with NPM or Express Generator.
READ
Installation of EJS
We can install EJS by calling the command:
npm install ejs
It uses the “title” variable in three places. This variable is set in the “index.js” router
file:
var express = require('express');
var router = express.Router();
module.exports = router;
READ
Website: https://ejs.co/
Templating - Layout
In this chapter, we’ll learn how to divide one big page into multiple smaller ones
(called partials). Partials contain only code about a certain component, for example,
the navbar. We can compare them to classes in Object Oriented Programming.
Instead of having huge HTML files, we should export independent parts to the
partials and include them in the main file.
To demonstrate this, we need a more complex HTML page. Let’s add Bootstrap
and jQuery to our application. Place “jquery-3.6.0.js” and “bootstrap.bundle.min.js”
in the public/javascripts folder and “bootstrap.min.css” in the public/stylesheets
folder (we downloaded these files multiple times in previous chapters. If you don’t
have them yet, you can download them from: https://getbootstrap.com/ and
https://jquery.com/download/).
Let’s replace our current “index.ejs” file with the following code:
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,
initial-scale=1">
<!-- Bootstrap CSS -->
<link href="../public/stylesheets/bootstrap.min.css"
rel="stylesheet">
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-
icons@1.9.1/font/bootstrap-icons.css">
We also need to tell our router where to look for files by adding
router.use(express.static(path.join(__dirname, '..')));
carousel.ejs
button-group.ejs
<div class="text-center">
<div class="btn-group" role="group" aria-label="Basic
mixed styles example">
<button type="button" class="btn btn-
danger">Left</button>
<button type="button" class="btn btn-
warning">Middle</button>
<button type="button" class="btn btn-
success">Right</button>
</div>
</div>
toast.ejs
scripts.ejs
<script
src="../public/javascripts/bootstrap.bundle.min.js"></script>
<script src="../public/javascripts/jquery-3.6.0.js"></script>
<script>
$(document).ready(function(){
$("#liveToastBtn").click(function(){
$("#liveToast").toast("show");
});
});
</script>
<!doctype html>
<html lang="en">
<head>
<title>Hello, world!</title>
</head>
<body>
<%- include('./partials/navbar.ejs') %>
<%- include('./partials/carousel.ejs') %>
<%- include('./partials/button-group.ejs') %>
With the <%- %>, we tell the template engine we’ll use the external values there.
With the include function, we add other templates to our HTML file.
Restart the application and make sure that your html elements are loaded (The
styling will not be correct. That will be fixed in the Activity below).
Thanks to these changes, adding a picture in the carousel means we don’t have to
scroll through the entire page’s code. Instead, we need to find the “carousel.ejs” file
and edit it. Moreover, we can reuse this file in different files just by including it in the
same way we did here.
Therefore, by changing only one file, we can affect all places its code is used, just
like in Object Oriented Programming.
WATCH
First, create a new partial that will contain the previous <head>
code from the index.ejs (before we split it into partials). Add it
in the <head> section of the current “index.ejs” file.
We want to list users. As input, we should get the user’s array. We can use the
forEach method on this array to generate code for each user.
We also want to add a Hello message to the list. Notice the difference in tags. The
“<%” tag doesn’t output values to the HTML template, while “<%-“ does. In other
words, code inside the “<%” tag will still execute, but its result won’t be visible in the
browser. For example, writing “<% user %>” would not display the user name.
In EJS, we can use the following tags:
<% - allows us to use ‘scriptlets’ – small programs containing logic
similar to JavaScript. For example, we use them to create a start if
statement or loop inside the view (such as the forEach loop used above).
It has no output.
<%_ - the same as <% tag, but strips all whitespace before execution.
<%= - outputs value into the template HTML escaped-characters, which
have a special function in HTML and are represented as HTML code,
useful when we provide data directly to the user.
<%- - outputs unescaped value into the template HTML, useful to include
other files (for example, partials). It can also be used to display data if
we know that data doesn’t contain special characters.
We can either set the user’s array here with static data (as the second parameter of
the include function) or get dynamic data from the router.
Let’s see the result:
READ
Update the code so that the final list only displays users with an
odd index in the user’s array:
References
OpenJS Foundation (2017). Express - Node.js web application framework. [online]
Expressjs.com. Available at: https://expressjs.com/.
LinkedIn. (n.d.). Express Essential Training Online Class | LinkedIn Learning,
formerly Lynda.com. [online] Available at:
https://www.linkedin.com/learning/express-essential-training-14539342.
Stack Overflow. (n.d.). angular - why am I suddenly getting: ng : File
C:\Users\d\AppData\Roaming\npm\ng.ps1 cannot be loaded. [online] Available at:
https://stackoverflow.com/questions/72863930/why-am-i-suddenly-getting-ng-file-c-
users-d-appdata-roaming-npm-ng-ps1-canno.
www.freeimages.com. (n.d.). Green frog Free Photo Download. [online] Available
at: https://www.freeimages.com/photo/green-frog-1361810.
LinkedIn. (n.d.). Building a Website with Node.js and Express.js Online Class |
LinkedIn Learning, formerly Lynda.com. [online] Available at:
https://www.linkedin.com/learning/building-a-website-with-node-js-and-express-js-3.
ejs.co. (n.d.). EJS -- Embedded JavaScript templates. [online] Available at:
https://ejs.co/#docs.
Otto, M. (n.d.). Bootstrap. [online] Getbootstrap.com. Available at:
https://getbootstrap.com/.
JS Foundation - js.foundation (2019). Download jQuery | jQuery. [online]
Jquery.com. Available at: https://jquery.com/download/.
strongloop.com. (n.d.). StrongLoop - Comparing JavaScript Templating Engines:
Jade, Mustache, Dust and More. [online] Available at:
https://strongloop.com/strongblog/compare-javascript-templates-jade-mustache-
dust/.
The task
Part 1
In this lesson, we learnt about ExpressJS. In this part of the task, you must create
an “/add” endpoint, which takes two numbers (as parameters) and returns their
sum.
For example, on path http://localhost:3000/add/1/5, we should get 6 as a result.
Source code: Click here to reveal add.js.
Source code: Click here to reveal app.js.
Part 2
We also learnt about template engines. In this part of the task, you need to
create an application that has three endpoints:
/ - main empty page
/number – generates a random number from 1 to 10 and returns to the
client
/tellme/:routeParamString – returns the string provided in route
parameters, assuming that there will always be a route parameter
provided.
For example, endpoint “/tellme/wizard” should return “wizard” in
response.
These endpoints must have a “Hello” text at the top. Please use partial to add this.
Source code: Click here to reveal app.js.
Source code: Click here to reveal index.js.
Source code: Click here to reveal index.ejs.
Source code: Click here to reveal number.js.
Introduction
We already know how to create applications with ExpressJS and how to create a
dynamic front-end for them. This lesson will focused on the backend and
connection side of the application.
We’ll learn how to use web sockets to create a real-time chat application. For this,
we’ll use the Socket.IO library, one of the most popular libraries to manage web
sockets for ExpressJS.
The following chapters will show us why and how to use web sockets, how to install
Socket.io and create a real-time chat application for many users with many rooms.
Learning outcomes
In this lesson, we are covering the following knowledge learning
outcome:
The candidate has knowledge of concepts, processes, and tools of
server-based JavaScript solutions.
Source: https://www.geeksforgeeks.org/what-is-web-socket-and-how-it-is-
different-from-the-http/
Source: https://www.geeksforgeeks.org/what-is-web-socket-and-how-it-is-
different-from-the-http/
Other web technologies have their web socket packages as well. For example,
while working with ASP.NET apps, we would use Microsoft’s SignalR.
In this course, we’ll use Socket.io, as it is well-known in the NodeJS community for
being performant and reliable.
READ
Installing Socket.IO
Applications generated by Express Generator by default use an HTTP connection.
While it is possible to change this, it is complex and doesn’t help much with
understanding web sockets.
Because of that, we’ll create a project without Express Generator in this lesson.
Let’s create the folder “socketapp”, enter this folder, and type the following
commands in the terminal:
npm init
and use the default settings. This command should create a “package.json” file.
Then update this file with new dependencies:
npm install express socket.io
index.js
const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
const { Server } = require("socket.io");
const io = new Server(server);
server.listen(3000, () => {
console.log('listening on *:3000');
});
We’ll analyse these files in the next chapter. For now, all we need to do is check if
the http://localhost:3000/ page loads correctly (when we see the input field at the
bottom) and if we see “a user connected” log in the console:
READ
WATCH
We use the socket.emit() function to emit the new event and socket.on() function to
listen for them. Right now, the server sends the message “any messages for me”,
and the client displays this in the web console:
Communication from the server to the client is working correctly. Now let’s try to
implement the other side. We want to send the message provided by the user to
Our server’s socket is listening for a “chat message” event and, after getting one,
displays a message in the console.
On the client side, it is different from before.
We need to get data from the input first and then, after the button click, send it to
the server. We’ll use the following code to do that:
<script>
var socket = io();
form.addEventListener('submit', function(e) {
e.preventDefault();
if (input.value) {
socket.emit('chat message', input.value);
input.value = '';
}
});
</script>
Therefore, our basic communication between the client and server is working
correctly.
READ
WATCH
Events
We already know how to use simple events to implement communication between
the client and server. However, there are usually multiple clients in our chatroom,
and we want many users to be able to chat. We also want users to see messages
from others. As clients aren’t connected, the server needs to implement this
functionality.
There are two ways to solve this problem:
1. We can use the io.emit() method, which sends a message to everyone.
2. We can use broadcast – the server can send a message to every
connected client besides the one who sent it. All we need to do is add a
broadcast flag before the emit() function – socket.broadcast.emit().
We want our users to see their own messages as well, so we’ll use the first option:
index.js:
The server is sending messages correctly. Now we only need to handle them on
the client side:
<script>
var socket = io();
form.addEventListener('submit', function(e) {
e.preventDefault();
if (input.value) {
socket.emit('chat message', input.value);
input.value = '';
}
});
socket.on('chat message', function(msg) {
var item = document.createElement('li');
item.textContent = msg;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});
</script>
We use DOM methods to update the list after the event. In the end, the window is
scrolled to the most recent message.
READ
WATCH
To create a new namespace, we need to use the io.of() method and then replace
all uses of “io” with provided as the parameter name. Let’s see this in an example:
Changes in index.js:
const admin = io.of("/admin");
Changes in index.html:
var socket = io('/admin');
After the application of these changes, the client and server are communicating by
admin namespace.
Room is an arbitrary channel that sockets can join or leave with methods:
socket.join("some room")
And
Then namespace calls only some rooms using the to() method:
admin.to("some room").emit("some text");
Let’s apply this knowledge to our application. We want to have two rooms: room1
and room2. Let’s create “room1.html” and “room2.html” files containing the current
code from “index.html”.
Let’s replace “index.html” with this code:
<!DOCTYPE html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
</style>
</head>
<body>
<p>Select room</p>
<ul>
<a href="/room1">Room 1</a>
<a href="/room2">Room 2</a>
</ul>
We already have the front end ready to work, but messages from all rooms are
visible in each room.
We’ll add the functionality that after a user enters or leaves a room, the message is
displayed.
Let’s start with the “index.js” server file. Use this connection code:
admin.on('connection', (socket) => {
socket.on('join', (data) => {
socket.join(data.room);
admin.in(data.room).emit('chat message', `New user
joined ${data.room} room!`);
})
socket.on('disconnect', () => {
admin.emit('chat message', 'user disconnected');
})
});
Whenever a client joins the room, a message reading something similar to, "New
user joined this room!" is sent to all clients in that room. Whenever the client sends
form.addEventListener('submit', function(e) {
e.preventDefault();
if (input.value) {
var msg = input.value;
socket.emit('chat message', { msg, room });
input.value = '';
}
});
socket.on('chat message', function(msg) {
var item = document.createElement('li');
item.textContent = msg;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});
socket.on('connect', () => {
socket.emit('join', { room: room });
})
</script>
In the first line, we set the room to room1. Whenever we emit a message, we add
room as the parameter in the object. When we enter the room, we send a join
To check how our app works, we select a room on the main page:
We see that users in different rooms don’t see each other’s messages:
READ
Let’s add the path module and EJS view engine in our “index.js” server file:
const path = require('path')
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
We see that the website works exactly the same; however, it now makes use of the
EJS template engine.
ACTIVITY
Using route parameters, you must enable the user to use any
room, not only room1 and room2. For example, after opening
http://localhost:3000/anythingTheyType in the browser, they
will join anythingTheyType room.
After finishing the exercise, revert the changes (in the next
chapters, we don’t want the user to be able to use any room).
We need to get the room parameter from the body, initialise the handling of the
GET request on the new endpoint and return JSON in the response:
app.post('/newroom', jsonParser, (req, res) => {
const room = req.body.room;
app.get('/' + room, (req, res) => {
res.render(__dirname + '/room.ejs', {room:
room});
});
res.send({
'room': room
});
})
Let’s run the application and send a POST request with Postman:
While creating a new room, enable the client to set this colour.
The client should provide it as the “color” string property in the
request’s body. This property isn’t mandatory. Your code
should still handle requests with no “color” property provided
(i.e., the colour parameter shouldn’t be required).
You can assume that it is correct if the user provides data (you
don’t need to focus on data validation here). Fix that by making
changes only in the /newroom POST handler and in the view file
“room.ejs”. Ensure that room1 and room2 are working properly
while not making changes in their GET handlers - they should
stay this way:
The room array will be provided as a parameter. For each room in the array, we
dynamically create a link.
Now let’s move to “index.js”.
While creating a new room, we add the new name to the array. We do this by
pushing new room to the rooms array (6th line in the code below):
app.post('/newroom', jsonParser, (req, res) => {
const room = req.body.room;
app.get('/' + room, (req, res) => {
res.render(__dirname + '/room.ejs', {room:
room});
});
rooms.push(room);
res.send({
'room': room
});
})
Now we can check if the application is working correctly. Let’s restart the
application, create a new room called “newRoom” (by sending a POST request with
Postman) and open the link: http://localhost:3000/.
While creating a new room, let the client set the room’s link
colour. It should be the same as in the activity in the previous
chapter:
Now move to the “index.js” server file. We need to attach the “fs” module to work
with files:
var fs = require('fs');
We pass path to the JSON file as the first parameter and encoding format as the
second parameter.
Now we need to edit the code by adding new rooms. If the room of the sent name
already exists, we should return an error in response. Otherwise, if “save” is true,
we should add this to our JSON file. Regardless of the “save” value, we need to
add this to the array of the current room:
app.post('/newroom', jsonParser, (req, res) => {
const room = req.body.room;
app.get('/' + room, (req, res) => {
res.render(__dirname + '/room.ejs', {room:
room});
});
if(!rooms.includes(req.body.room)) {
References
LinkedIn. (n.d.). Node.js: Real-Time Web with Socket.IO Online Class | LinkedIn
Learning, formerly Lynda.com. [online] Available at:
https://www.linkedin.com/learning/node-js-real-time-web-with-socket-io.
socket.io. (n.d.). Introduction | Socket.IO. [online] Available at:
https://socket.io/docs/v4/.
socket.io. (n.d.). Get started | Socket.IO. [online] Available at: https://socket.io/get-
started/.
Stack Overflow. (n.d.). javascript - Using socket.io in Express 4 and express-
generator’s /bin/www. [online] Available at:
https://stackoverflow.com/questions/24609991/using-socket-io-in-express-4-and-
express-generators-bin-www.
MDN Web Docs. (2019). The WebSocket API (WebSockets). [online] Available at:
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API.
ejs.co. (n.d.). EJS -- Embedded JavaScript templates. [online] Available at:
https://ejs.co/#docs.
expressjs.com. (n.d.). Express 4.x - API Reference. [online] Available at:
https://expressjs.com/en/4x/api.html.
The task
In this lesson, we learnt how to combine features to improve the application. In this
part of the task, you need to:
Store all messages from room1 (the ones sent from the clients, not the ones about
joining/disconnecting) in the JSON file (name it messages.json) and send them to
the client whenever it joins the room.
Source code: Click here to reveal index.js.
Source code: Click here to reveal room.ejs.
Source code: Click here to reveal created message.json file (at the same level as
index.js file).
Introduction
We learnt how to download and use external packages from Node Package
Manager. With these packages, we can use various functionalities without
implementing them by ourselves. In this lesson, we’ll also learn how to create and
publish our packages to contribute to the community.
To use NPM, you don’t need an account, but you’ll need one to publish your own
packages. We’ll create a package with simple functionality in this course. Later,
we’ll learn about publishing details such as versions and tags.
Finally, we’ll publish the package and learn how to update it. We can then delete it
from the registry if we want to.
This lesson will also discuss a very important issue - authentication.
Learning outcomes
In this lesson, we are covering the following knowledge learning
outcome:
The candidate has knowledge of concepts, processes, and tools of
server-based JavaScript solutions.
After that, check your email for the code and paste it into the next window:
In the Packages menu, we can see the packages we’ll publish in the next chapter.
READ
You’ll be able to log in with our username and password. We also need to provide
an email. Next, check your email. You should receive an email with a confirmation
code. Enter this code in the VS Code console.
After that, we can check if the login attempt succeeded by checking the current
user with the command:
npm whoami
If everything went correctly, we should get results similar to these presented below
(but with your unique username):
to create a new project. After that, you’ll see a form in the console. Fill it with the
unique name and description of the project, and put your username as the author.
console.log("Tests completed!");
In “test.js”, we test our function using the “assert” module. The assert.scrictEqual()
method checks whether two values are identical. We can run this test file with the
command.
node test
If we change the second argument in assert from 6 to 5, the result will be:
## Install
```
npm install add-four-numbers
```
## USAGE
**`addFourNumbers(a, b, c, d)`**
```
// Load library
var addFourNumbers = require('add-four-numbers');
// Calculate 0 + 1 + 2 + 3
console.log(addFourNumbers(0, 1, 2, 3)); // => 6
```
## Test
```
npm test
```
License ISC
WATCH
READ
WATCH
Make sure that your package name is unique. If it isn’t, you should see an error. To
fix it, you need to change that package name in the package.json file.
If everything goes according to plan, we should get an output similar to this:
READ
WATCH
Adding a new dependency is a tiny update and can be classified as either a minor
release or a patch. Let’s open the “package.json” file and change the version to
1.0.1. (classify this change as a patch):
{
"name": "add-four-numbers",
"version": "1.0.1",
"description": "Function adding four numbers",
"main": "index.js",
"scripts": {
"test": "node test.js"
},
"author": "learnwithme",
"license": "ISC",
"dependencies": {
"socketio": "^1.0.0"
}
}
Let’s open the VSCode console, ensuring we are in the folder where we installed
the package in the previous chapter and run the command:
npm install
It should have no result, as, by default, our module was installed as version 1.0.0
without any characters before. Let’s change the version to: “~1.0.0” and rerun the
command:
READ
WATCH
All of these tests have their disadvantages. Weak passwords can be guessed, and
phones can be stolen. Because of that, in many applications, two-factor
authentication is used. It combines more than one authentication method, usually
knowledge and ownership tests.
In this course, we’ll use PassportJS – authentication middleware for NodeJS. It
provides us with an authenticate method. Then we’ll choose one from over 500+
authentication strategies, using username and password, Twitter, Facebook, or
more. In this section, we’ll learn about username and password authentication.
Once the user is authenticated, we store their data in the session, so they are still
logged in even after refreshing the website or moving to another endpoint. The
entire authentication process is presented in the picture below:
Passport provides an authenticate function. This function uses one of over 500
strategies to authenticate the user. Strategies are pluggable authentication
modules installed separately. Once the user is authenticated, their data is stored in
the session and used for every request. The Serialize function determines which
READ
WATCH
Project setup
In this lesson, we’ll create authentication for a simple TODO list project. We’ll go
through the official PassportJS tutorial. We can clone a starting project from the
repository. It uses a database (SQLite). As we haven’t learned about databases
yet, we’ll treat this functionality as the BlackBox – we won’t focus on understanding
database code but its effects.
The project uses Embedded JavaScript (EJS) as the template engine. It has
already implemented the TODO list logic. Whenever we add an item, it is added to
the database. However, the user needs to be signed in first to use it. We’ll also use
this logic as the BlackBox; the primary process we’ll focus on is creating user
authentication.
The final version of the app is available at: https://github.com/passport/todos-
express-password, so we can see how it should work.
The first user needs to sign in:
Let’s start the project setup. Create a folder for the project and open it in the
VSCode terminal. Type the command:
git clone https://github.com/passport/todos-express-
starter.git username-password-tutorial
Notice that all views and styles we will be used in further chapters are already
implemented:
We can see that the project uses NPM – there is a “package.json” file. There is a
start command in this file, which starts the app. Let’s install all dependencies and
run the application:
npm install
npm start
READ
module.exports = router;
It’s a standard ExpressJS endpoint, which renders “login.ejs” view on /login path.
Let’s add this router file in “app.js”:
/* code before */
var indexRouter = require('./routes/index');
var authRouter = require('./routes/auth');
app.locals.pluralize = require('pluralize');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
We can see the sign-in view, but the user can’t give us any data yet. Let’s create a
form for that and add it to the “login.ejs” file:
login.ejs:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,
initial-scale=1">
<title>Express • TodoMVC</title>
<link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/index.css">
<link rel="stylesheet" href="/css/login.css">
</head>
<body>
<section class="prompt">
<h3>todos</h3>
<h1>Sign in</h1>
<form action="/login/password" method="post">
<section>
<label for="username">Username</label>
<input id="username" name="username"
type="text" autocomplete="username" required autofocus>
</section>
<section>
<label for="current-
We added a simple form with labels and inputs and a button to submit it.
After submission, a POST request to /login/password path is sent. We’ll create this
endpoint in the next chapter.
Verify password
In this chapter, we’ll finally use PassportJS and its local strategy. Let’s install them
first with NPM:
npm install passport
npm install passport-local
and include them in “auth.js”. We’ll need our database and built-in encryption
module as well:
var express = require('express');
var passport = require('passport');
var LocalStrategy = require('passport-local');
var crypto = require('crypto');
var db = require('../db');
We’ll use a passport.use() method to choose the authentication strategy. The local
strategy takes a verification function as the parameter. This function takes a
username, password, and callback function. Let’s paste this code after all
requirements:
The verify() function first uses the query to find all records of the provided
username in the database. We’ll learn more about databases and queries in the
next course. For now, all we need to know is that it finds all rows where the
username is the same as provided.
While we can send a username and keep it in the database as plain text, we
shouldn’t do this with a password. We encrypt the password with the sha256
algorithm (one of the most popular algorithms for authentication and data
encryption ) and a built-in encryption module.
Encryption makes the knowledge test much safer. If an encrypted password is the
same as in the database, for now, we move to the next step by returning the row in
the callback. Otherwise, we send an error in callback instead.
After the Passport setup is done, let’s use it in the endpoint. Let’s create a
/login/password handler in the auth.js router file:
Let’s start the application and use these credentials to try to sign into the login view
(http://localhost:3000/login) :
READ
Session
Sessions are a way to store users’ data temporarily on the server side. After users
log in, we give them an access token that the browser remembers. A session
usually gets deleted when the user exits the browser. This way, after each refresh
or attempt to access another endpoint, the user doesn’t need to reauthenticate
each time.
Let’s add session support to our application. Start by installing an express session
module and a module for session management for our database with NPM
commands:
npm install express-session
npm install connect-sqlite3
We use the connect-sqlite3 package to create a place to keep data about our
current active sessions. This package stores this information using the database.
Using just created SQLiteStore, this code creates a new temporary database to
keep sessions. This database will be named “sessions.db” and placed in the
_current_path/var/db directory. As the secret, we can pass any string or array of
strings. It is used to sign a session cookie.
Now we need to implement serialization and deserialization of the user to the
session. PassportJS has serializeUser() and deserializeUser() methods to do that.
The Serialization method saves users’ data to the session, while the Deserialization
method retrieves this data using the user id.
Let’s use them in the “auth.js” file:
passport.serializeUser(function(user, callback) {
process.nextTick(function() {
callback(null, { id: user.id, username: user.username });
});
});
passport.deserializeUser(function(user, callback) {
process.nextTick(function() {
return callback(null, user);
});
});
Right now, we should have a working application. However, we still miss the
functionalities of switching users and signing up new ones.
We can implement signing out from the app easily with the Express
request.logout() method. It finishes the session and takes the callback function as
the parameter. Let’s redirect the user to the home page in that callback:
auth.js:
router.post('/logout', function(req, res, next) {
req.logout(function(err) {
if (err) { return next(err); }
res.redirect('/');
});
});
We see that after adding this endpoint, we can sign out (it was already linked in an
“index.ejs” file).
Adding new users is more complex.
Let’s create /signup endpoint in “auth.js”:
router.get('/signup', function(req, res, next) {
res.render('signup');
});
Now we have the front end ready. We still need to implement the logic of saving
new users to the database. As this code is database related, we’ll go through it and
explain each part without analysing the database query part:
auth.js:
router.post('/signup', function(req, res, next) {
var salt = crypto.randomBytes(16);
crypto.pbkdf2(req.body.password, salt, 310000, 32,
'sha256', function(err, hashedPassword) {
if (err) { return next(err); }
db.run('INSERT INTO users (username, hashed_password,
salt) VALUES (?, ?, ?)', [
req.body.username,
hashedPassword,
salt
], function(err) {
if (err) { return next(err); }
var user = {
id: this.lastID,
username: req.body.username
};
req.login(user, function(err) {
if (err) { return next(err); }
As we want to encrypt the credentials before saving, we generate the salt first and
pass it to encrypting function pbkdf2(). Salt is a randomly generated byte sequence
used in encryption because people tend to choose the same passwords, and not at
all randomly. Many used passwords are actual, short words to make them easy to
remember, but this could also enable an attack. We apply salt to the password to
make the password hash output unique.
Next, we insert the user’s username, encrypted password, and salt into the
database. If everything goes correctly, we log in to the new user and redirect them
to the home page.
We can now run the application and check that all functionalities are complete.
If we try to add an already existing user, we should see the error, as the database
is preventing us from doing that:
READ
References
docs.npmjs.com. (n.d.). npm Documentation. [online] Available at:
https://docs.npmjs.com/.
LinkedIn. (n.d.). Advanced npm Online Class | LinkedIn Learning, formerly
Lynda.com. [online] Available at: https://www.linkedin.com/learning/advanced-npm.
LinkedIn. (n.d.). Web Security: User Authentication and Access Control Online
Class | LinkedIn Learning, formerly Lynda.com. [online] Available at:
https://www.linkedin.com/learning/web-security-user-authentication-and-access-
control.
The task
Part 1
After testing the solution and ensuring it works correctly, unpublish the package.
Part 2
In this lesson, we also learnt about authentication with PassportJS. We went over
the tutorial about authentication with username and password. In this task, you
need to discover other ways to authenticate by finishing the guide on any other
PassportJS authentication strategy, for example, authentication with a Google
account.
INFO
Introduction
Learning outcomes
In this lesson, we are covering the following knowledge learning
outcome:
The candidate has knowledge of concepts, processes, and tools of
server-based JavaScript solutions.
console.log('end')
is synchronous. We wait until each operation finishes and then move to the next
lines of our code. It should give the following results:
To make it asynchronous, we need to process the done() method in the next tick:
function addOne(number, done) {
process.nextTick(() => {
done(number + 1);
});
}
console.log('end')
delay(1)
.then(console.log)
.then(() => 42)
.then((number) => console.log(`Hello world: ${number}`))
We created the delay() method, which takes several seconds as the argument. The
method uses promises to return the text after the delay.
We can pass the function to execute after the promise resolves with the then()
method. We can pass the value returned from the function to pass it further in the
next then() methods. We do this in this code section:
delay(1)
.then(console.log)
.then(() => 42)
.then((number) => console.log(`Hello world: ${number}`))
First, then() doesn’t return anything. Second, then() returns a number, and we use
it in the third then().
We get the expected results:
if (seconds > 3) {
rejects(new Error(`${seconds} is too long!`))
}
setTimeout(() => {
resolves('the long delay has ended')
}, seconds);
});
delay(1)
.then(console.log)
.then(() => 42)
.then((number) => console.log(`Hello world: ${number}`))
.catch((error) => console.log(`error: ${error.message}`));
When we run the delay function with 5 as the parameter, we get the error:
The file system writeFile() method is based on callbacks. With the promisify()
method, we can use this method as a promise.
WATCH
Here is an example:
var delay = (seconds, number) => new Promise((resolves) => {
setTimeout(() => {
resolves(number)
console.log(number)
}, seconds * 1000);
});
Promise.all([
delay(10, 1),
delay(6, 2),
delay(5, 3),
delay(6, 4)
]).then(console.log);
We create an array of four tasks and simulate their execution with a delay function.
Running this program will give the following results:
The then() method is executed right after getting the first result (from the third task)
as the Promise.race method waits only for the first task to be completed.
Executing tasks sequentially or in parallel has pros and cons. While executing in
parallel, we can get tasks completed faster. However, when some of the tasks are
large, we might not have enough resources to complete them.
We can combine both of these options and create a concurrent task queue. It runs
tasks in parallel, but only a specified number can run simultaneously.
Let’s see an example of a class implementation.
class PromiseQueue {
constructor(promises=[], concurrentCount=1) {
this.concurrent = concurrentCount;
this.total = promises.length;
this.todo = promises;
this.running = [];
this.complete = [];
}
get runAnother() {
return (this.running.length < this.concurrent) &&
this.todo.length;
}
run() {
while (this.runAnother) {
var promise = this.todo.shift();
At first, we initialise our to-do array with all promises in a promises[] array. The
runAnother() method checks if there are still tasks to complete and if we have free
spots in the “running” array. If we do, we take the first promise from the to-do array
and place it in the running array. After the promise resolves, we push it to the
complete array and, as we have a new spot left, check whether some tasks are still
waiting for execution.
WATCH
With streams, instead of sending all the data at the same time, we estimate how
much we can process at the time and send data part by part. We can create
streams with a standard file system module:
res.writeHeader(200, { 'Content-Type': 'video/mp4' });
fs.createReadStream(file)
.pipe(res)
There are many types of streams. In this chapter, we will focus on readable and
writable ones.
We need to include a stream module and create a class extending Readable to
implement a readable stream. Let’s create a class for reading arrays:
const { Readable } = require('stream');
const peaks = [
"Tallac",
"Ralston",
"Rubicon",
"Twin Peaks",
"Castle Peak",
"Rose",
"Freel Peak"
];
_read() {
if (this.index <= this.array.length) {
const chunk = {
data: this.array[this.index],
index: this.index
};
this.push(chunk);
this.index += 1;
} else {
this.push(null);
}
}
readStream.on('end', () => {
writeStream.end();
});
writeStream.on('close', () => {
process.stdout.write('file copied\n');
})
Advanced streams
In this chapter, we will overview advanced knowledge of NodeJS streams.
Backpressure is when a readable stream sends data too fast for the writable
stream to handle. We can solve this by simply stopping the readable stream until
the writable stream finishes its work. This is illustrated below.
We shouldn’t keep sending new data when the destination cannot process more
data. It would cause data loss (image on the left). We should wait until the data is
processed and then continue sending our data (image on the right).
readStream.on('end', () => {
writeStream.end();
});
writeStream.on('drain', () => {
console.log('drained');
readStream.resume();
})
writeStream.on('close', () => {
process.stdout.write('file copied\n');
})
We can set a high watermark for the write stream. It specifies the maximum amount
of data potentially processed. If it’s bigger, more memory is used, but backpressure
happens less often.
The code we have used so far takes many lines. Fortunately, there is a pipe()
method. This method connects a writable stream to the readable stream while
handling all anomalies, including backpressure, in its implementation. We can use it
to pipe a write to the read stream in only one line.
Let’s look at the previous example but using the pipe() method instead:
const { createReadStream, createWriteStream } =
require('fs');
readStream.pipe(writeStream).on('error', console.error)
var total = 0;
report.on('data', (chunk) => {
total += chunk.length;
console.log('bytes: ', total);
})
readStream
.pipe(report)
.pipe(writeStream);
We can see the result before the data is saved. Several bytes are already sent to
the write stream in the console:
constructor(char) {
super();
this.replaceChar = char;
}
_flush(callback) {
this.push('more stuff is being passed...');
callback();
}
readStream
.pipe(xStream)
.pipe(process.stdout);
We created a stream replacing all letters and numbers with a specified character.
Let’s create a “sample.txt” file in the current directory:
Hello world!
It’s me!
We can see that all letters got changed beside one line, which was added by the
_flush() method at the very end.
Security overview
Open Web Application Security Project (OWASP) is the leading non-profit
organisation working on security standards. Once every few years, they release
reports of the Top 10 most popular threats, containing information about them and
how to prevent them.
On their official website, we can see a list of attacks, their code examples, and
details on how to deny them.
Although their work is created for all languages, we are mainly interested in
JavaScript/NodeJS security. Fortunately, there is a website consolidating
information from the OWASP page using NodeJS technology. Fortunately, there is
a project that teaches how OWASP Top 10 security risks apply to web applications
developed using Node.js, and how to effectively address these issues:
https://github.com/OWASP/NodeGoat.
The most common attack types are:
Cross-site scripting – inserting JavaScript code as the form data,
executed without the developer’s expectation. We can prevent this with
data validation on both the client and the server.
Denial of service – trying to overload the server with large data, an
unexpected number of requests, or creating an infinite loop. We can
READ
WATCH
Packages security
In NodeJS applications, we usually use a lot of external dependencies. We should
remember to keep them up-to-date. Outdated versions can have vulnerabilities that
can expose us to attacks.
Or we can check all our current dependencies for vulnerabilities with this command:
npm audit
Another important part of working with packages is to protect our NPM account. We
should enable two-factor authentication, so users must complete knowledge and
ownership tests while accessing our account. We can change this at
https://www.npmjs.com/settings/your_username/profile.
WATCH
Data security
To ensure data security, we should validate data provided by the end users. There
are many libraries to do this. We’ll use ValidatorJSand install it with npm:
npm install validator
While saving code to the database, we should use template statements. If we use
MongoDB, we can use Error! Hyperlink reference not valid.. If SQL-based one –
sequelize. As we haven’t learnt about databases yet, we will revisit this topic in the
next lessons.
We can increase the security of our data by installing HelmetJS – tool setting HTTP
security headers – HTTP headers that are exchanged between client and server to
specify security details. We can install it with the command:
npm install helmet --save
app.use(helmet());
READ
WATCH
Server security
We should use HTTPS protocol instead of pure HTTP to ensure server security.
HTTPS encrypts our connection using Secure Socket Layer (SSL)’s public key
cryptography. Some browsers even refuse to load websites using pure HTTP.
To use the HTTPS protocol, we need to ensure that the domain we’re using to host
our server has a Secure Socket Layer (SSL) certificate. This digital certificate
authenticates a website’s identity and enables an encrypted connection.
We can prevent one way of Denial of Service attack, the one overloading us with
requests, by using the middleware Express Rate Limit. It limits the maximum
References
Node.js Foundation (2019). Docs | Node.js. [online] Node.js. Available at:
https://nodejs.org/en/docs/.
LinkedIn. (n.d.). Advanced Node.js Online Class | LinkedIn Learning, formerly
Lynda.com. [online] Available at: https://www.linkedin.com/learning/advanced-
node-js.
Videezy. (n.d.). Circle Cut Animation Video. [online] Available at:
https://www.videezy.com/abstract/44059-circle-cut-animation-video.
The task
In this lesson, we learnt about asynchronous patterns and streams. In this part of
the task, you need to implement a program using transform streams, which takes
input from the console, changes all letters to upper case, and saves it as a
upper.txt file.
After starting the program, we should be able to provide as much data as we want
and end this by clicking CTRL+C. This shortcut sends a SIGKILL signal, which
terminates the process of reading the data. NodeJS program behaves this way by
default. You don’t need to implement any code to provide this functionality.
Usage example:
Console:
upper.txt:
Introduction
This is a self-study lesson that consolidates knowledge of the second module. We’ll
attempt to improve the chat application built in the previous chapters. We’ll also
revisit using ExpressJS, EJS, and Socket.IO.
READ
The task
Part 1
In this part of the task, you need to improve the application we developed in the
web socket lessons by adding a name field to the message. The client should be
able to send their username whenever it sends a new message (this can be done
via prompt):
The username should be displayed next to it. A chat should look like this:
In this part of the task, you need to create a module that exports the
function filling in the following criteria:
Takes two string parameters, names of input and output files
Reads an input file, using a transform stream to transform the data, so
every character is doubled and saved in the output file.
If the file doesn’t exist, it shows an error message and stops (default
behaviour).