We are going to build this simple ToDo List app, which allows us to create a list of tasks, mark them as completed, and delete tasks from the list.
Let's get started by using the starter code of Chapter 1 in the book's code files. The starter code will contain three files: index.html
, scripts.js
, and styles.css
. Open the index.html
file in a web browser to see the basic design of the ToDo List app, as shown in the preceding screenshot.
The JavaScript file will be empty, in which we are going to write scripts to create the application. Let's take a look at the HTML file. In the <head>
section, a reference to the styles.css
file and BootstrapCDN are included, and at the end of the <body>
tag, jQuery and Bootstrap's JS files are included along with our scripts.js
file:
In our HTML file, the styles in the CSS file included last will overwrite the styles in the previous file. Hence, it's a good practice to include our own CSS files after the default framework's CSS files (Bootstrap in our case) if we plan to rewrite any of the framework's default CSS properties. We don't have to worry about CSS in this chapter, since we are not going to edit default styles of Bootstrap in this chapter. We only need to concentrate on our JS files. JavaScript files must be included in the given order as in the starter code:
We are including the jQuery code first after which Bootstrap JS files are included. This is because Bootstrap's JS files require jQuery to run. If we include Bootstrap JS first, it will print an error in the console, saying Bootstrap requires jQuery to run. Try moving the Bootstrap code above the jQuery code and open up your browser's console. For Google Chrome, it's Ctrl+Shift+J on Windows or Linux and command+option+J on Mac. You will receive an error similar to this:
Hence, we are currently managing dependencies by including the JS files in the right order. However, in larger projects, this could be really difficult. We'll look at a better way to manage our JS files in the next chapter. For now, let's continue on to build our application.
We usually use the navigation bar to add links to the different sections of our web app. Since we are only dealing with a single page in this app, we will only include the page title in the navigation bar.
Now that you have a good idea about the developer tools, let's start the coding part. You should already be familiar with the JavaScript ES5 syntax. So, let's explore JavaScript with the ES6 syntax in this chapter. ES6 (ECMAScript 2015) is the sixth major release of ECMAScript language specification. JavaScript is an implementation of ECMAScript language specification.
Note
At the time of writing this book, ES8 is the latest release of JavaScript language. However, for simplicity and ease of understanding, this book only focuses on ES6. You can always learn about the latest features introduced in ES7 and beyond on the Internet easily once you grasp the knowledge of ES6.
At the time of writing this book, all the modern browsers support most of the ES6 features. However, older browsers don't know about the new JavaScript syntax and, hence, they will throw errors. To resolve such backward compatibility issues, we will have to transpile our ES6 code to ES5 before deploying the app. Let's look into that at the end of the chapter. The latest version of Chrome supports ES6; so, for now, we'll directly create our ToDo List with the ES6 syntax.
I'll explain in detail about the new ES6 syntax. If you find difficulties understanding normal JavaScript syntax and data types, do refer to the respective section in the following w3schools page: https://www.w3schools.com/js/default.asp.
Open up the scripts.js
file in your text editor. First of all, we will create a class that contains the methods of our ToDo List app, and yeah! Classes are a new addition to JavaScript in ES6. It's simple to create objects using classes in JavaScript. It lets us organize our code as modules. Create a class named ToDoClass
with the following code in the scripts file and refresh the browser:
class ToDoClass {
constructor() {
alert('Hello World!');
}
}
window.addEventListener("load", function() {
var toDo = new ToDoClass();
});
Your browser will now throw an alert saying "Hello World!
". So here's what the code is doing. First, window.addEventListener
will attach an event listener to the window and wait for the window to finish loading all the needed resources. Once it is loaded, the load
event is fired, which calls the callback function of our event listener that initializes ToDoClass
and assigns it to a variable toDo
. While ToDoClass
is initialized, it automatically calls the constructor, which creates an alert saying "Hello World!
". We can further modify our code to take advantage of ES6. In the window.addEventListener
part, you can rewrite it as:
let toDo;
window.addEventListener("load", () => {
toDo = new ToDoClass();
});
First, we replace the anonymous callback function function () {}
with the new arrow function () => {}
. Second, we define the variable with let
instead of var
.
Arrow functions are a cleaner and shorter way to define functions in JavaScript and they simply inherit the this
object of its parent instead of binding its own. We'll see more about the this
binding soon. Let's just look into using the new syntax. Consider the following functions:
let a = function(x) {
}
let b = function(x, y) {
}
The equivalent arrow functions can be written as:
let a = x => {}
let b = (x,y) => {}
You can see that ()
are optional, when we have to pass the only single argument to the function.
Sometimes, we just return a value in a single line in our functions, such as:
let sum = function(x, y) {
return x + y;
}
If we want to directly return a value in our arrow function in a single line, we can directly ignore the return
keyword and {}
curly braces and write it as:
let sum = (x, y) => x+y;
That's it! It will automatically return the sum of x
and y
. However, this can be used only when you want to return the value immediately in a single line.
Next, we have the let
keyword. ES6 has two new keywords for declaring variables, let
and const
. let
and var
differ by the scope of the variables declared using them. The scope of variables declared using var
is within the function it is defined and global if it is not defined inside any function, while the scope of let
is restricted to within the enclosing block it was declared in and global if it is not defined inside any enclosing block. Look at the following code:
var toDo;
window.addEventListener("load", () => {
var toDo = new ToDoClass();
});
If you were to accidentally re-declare toDo
somewhere along the code, as follows, your class object gets overwritten:
var toDo = "some value";
This behavior is confusing and quite difficult to maintain variables for large applications. Hence, let
was introduced in ES6. It restricts the scope of variables only within the enclosing in which it was declared. In ES6, it is encouraged to use let
instead of var
for declaring variables. Look at the following code:
let toDo;
window.addEventListener("load", () => {
toDo = new ToDoClass();
});
Now, even if you accidentally re-declare toDo
somewhere else in the code, JavaScript will throw an error, saving you from a runtime exception. An enclosing block is a block of code between two curly braces {}
and the curly braces may or may not belong to a function.
We need a toDo
variable to be accessible throughout the application. So, we declare toDo
above the event listener and assign it to the class object inside the callback function. This way, the toDo
variable will be accessible throughout the page.
Note
let
is very useful for defining variables in for
loops. You can create a for
loop such that for(let i=0; i<3; i++) {}
and the scope of the variable i
will only be within the for
loop. You can easily use the same variable name in other places of your code.
Let's take a look at the other keyword const
. The working of const
is the same as that of let
, except that variables declared using const
cannot be changed (reassigned). Hence, const
is used for constants. However, an entire constant cannot be reassigned but their properties can be changed. For example:
const a = 5;
a = 7; // this will not work
const b = {
a: 1,
b: 2
};
b = { a: 2, b: 2 }; // this will not work
b.a = 2; // this will work since only a property of b is changed
Note
While writing code in ES6, always use const
to declare your variables. Use let
only when you need to perform any changes (reassignments) to the variable and completely avoid using var
.
The toDo
object contains the class variables and functions as properties and methods of the object. If you need a clear picture of how the object is structured in JavaScript, see: https://www.w3schools.com/js/js_objects.asp.
Loading the tasks from data
The first thing we want to do in our application is to load the tasks dynamically from a set of data. Let's declare a class variable that contains the data for tasks along with methods needed to pre-populate the tasks. ES6 does not provide a direct way to declare class variables. We need to declare variables using the constructor. We also need a function to load tasks into the HTML elements. So, we'll create a loadTasks()
method:
class ToDoClass {
constructor() {
this.tasks = [
{task: 'Go to Dentist', isComplete: false},
{task: 'Do Gardening', isComplete: true},
{task: 'Renew Library Account', isComplete: false},
];
this.loadTasks();
}
loadTasks() {
}
}
The tasks variable is declared inside the constructor as this.tasks
, which means the tasks variable belongs to this
(ToDoClass
). The variable is an array of objects that contain the task details and its completion status. The second task is set to be completed. Now, we need to generate an HTML code for the data. We'll reuse the code of the <li>
element from the HTML to generate a task dynamically:
<li class="list-group-item checkbox">
<div class="row">
<div class="col-md-1 col-xs-1 col-lg-1 col-sm-1 checkbox">
<label><input type="checkbox" value="" class="" checked></label>
</div>
<div class="col-md-10 col-xs-10 col-lg-10 col-sm-10 task-text complete">
First item
</div>
<div class="col-md-1 col-xs-1 col-lg-1 col-sm-1 delete-icon-area">
<a class="" href="/"><i class="delete-icon glyphicon glyphicon-trash"></i></a>
</div>
</div>
</li>
Note
In JavaScript, an instance of a class is called the class object or simply object. The class objects are structured similarly to JSON objects in key-value pairs. The functions associated with a class object are called its methods and the variables/values associated with a class object are called its properties.
Traditionally, in JavaScript, we concatenate strings using the +
operator. However, if we want to concatenate multi-line strings, then we have to use the escape code \
to escape new lines, such as:
let a = '<div> \
<li>' + myVariable+ '</li> \
</div>'
This can be very confusing when we have to write a string that contains a large amount of HTML. In this case, we can use ES6 template strings. Template strings are strings surrounded by backticks ` `
instead of single quotation marks ' '
. By using this, we can create multi-line strings in an easier way:
let a = `
<div>
<li> ${myVariable} </li>
</div>
`
As you can see, we can create DOM elements in a similar way; we type them in HTML without worrying about spaces or multi-lines. Because whatever formatting, such as tabs or new lines, present inside the template strings is directly recorded in the variable. And we can declare variables inside the strings using ${}
. So, in our case, we need to generate a list of items for each task. First, we will create a function to loop through the array and generate the HTML. In our loadTasks()
method, write the following code:
loadTasks() {
let tasksHtml = this.tasks.reduce((html, task, index) => html +=
this.generateTaskHtml(task, index), '');
document.getElementById('taskList').innerHTML = tasksHtml;
}
After that, create a generateTaskHtml()
function inside ToDoClass
, with the code:
generateTaskHtml(task, index) {
return `
<li class="list-group-item checkbox">
<div class="row">
<div class="col-md-1 col-xs-1 col-lg-1 col-sm-1 checkbox">
<label><input id="toggleTaskStatus" type="checkbox"
onchange="toDo.toggleTaskStatus(${index})" value="" class=""
${task.isComplete?'checked':''}></label>
</div>
<div class="col-md-10 col-xs-10 col-lg-10 col-sm-10 task-text ${task.isComplete?'complete':''}">
${task.task}
</div>
<div class="col-md-1 col-xs-1 col-lg-1 col-sm-1 delete-icon-area">
<a class="" href="/" onClick="toDo.deleteTask(event, ${index})"><i
id="deleteTask" data-id="${index}" class="delete-icon glyphicon
glyphicon-trash"></i></a>
</div>
</div>
</li>
`;
}
Now, refresh the page, and wow! Our application is loaded with tasks from our tasks
variable. That should look like a lot of code at first, but let's look into it line by line.
Note
In case the changes aren't reflected when you refresh the page, it's because Chrome has cached the JavaScript files and is not retrieving the latest one. To make it retrieve the latest code, you will have to do a hard reload by pressing Ctrl+Shift+R on Windows or Linux and command+Shift+R on Mac.
In the loadTasks()
function, we declare a variable tasksHtml
with a value that is returned by the callback function of the array reduce()
method of the tasks
variable. Each array object in JavaScript has some methods associated with it. reduce
is one such method of JS array that applies a function to each element of the array from left to right and applies the values to an accumulator so that the array gets reduced to a single value and then it returns that final value. The reduce
method accepts two parameters; first is the callback function, which is applied to each element of the array, and the second one is the initial value of the accumulator. Let's look at our function in normal ES5 syntax:
let tasksHtml = this.tasks.reduce(function(html, task, index, tasks) {
return html += this.generateTaskHtml(task, index)
}.bind(this), '');
- The first parameter is the callback function, whose four parameters are
html
, which is our accumulator, task
, which is an element from the tasks array, index, which gives the current index of the array element in the iteration, and tasks
, which contains the entire array on which the reduce method is applied on (we don't need the entire array inside the callback function for our use case, so the fourth parameter is ignored in our code). - The second parameter is optional, which contains the initial value of the accumulator. In our case, the initial HTML string is an empty string
''
. - Also, note that we have to
bind
the callback function with this
(which is our class) object so that the methods of ToDoClass
and the variables are accessible within the callback function. This is because, otherwise, every function will define its own this
object and the parent's this
object will be inaccessible within that function.
What the callback function does is it takes the empty html
string (accumulator) first and concatenates it with the value returned by the generateTaskHtml()
method of ToDoClass
, whose parameters are the first element of the array and its index. The returned value, of course, should be a string, otherwise, it will throw an error. Then, it repeats the operation for each element of the array with an updated value of the accumulator, which is finally returned at the end of the iteration. The final reduced value contains the entire HTML code for populating our tasks as a string.
By applying ES6 arrow functions, the entire operation can be achieved in a single line as:
let tasksHtml = this.tasks.reduce((html, task, index) => html += this.generateTaskHtml(task, index), '');
Isn't that simple! Since we are just returning the value in a single line, we can ignore both the {}
curly braces and return
keyword. Also, arrow functions do not define their own this
object; they simply inherit the this
object of their parents. So we can also ignore the .bind(this)
method. Now, we have made our code cleaner and much simpler to understand using arrow functions.
Before we move on to the next line of the loadTasks()
method, let's look at the working of the generateTaskHtml()
method. This function takes two arguments--an array element task in the tasks data and its index and returns a string that contains the HTML code for populating our tasks. Note that we have included variables in the code for the checkbox:
<input id="toggleTaskStatus" type="checkbox" onchange="toDo.toggleTaskStatus(${index})" value="" class="" ${task.isComplete?'checked':''}>
It says that "on change of checkbox's status", call toggleTaskStatus()
method of the toDo
object with the index of the task that was changed. We haven't defined the toggleTaskStatus()
method yet, so when you click the checkbox on the website now, it will throw an error in Chrome's console and nothing special happens in the browser window. Also, we have added a conditional operator ()?:
to return a checked attribute for the input tag if the task status is complete. This is useful to render the list with a prechecked check box if the task is already complete.
Similarly, we have included ${task.isComplete?'complete':''}
in the div
that contains the task text so that an additional class gets added to the task if the task is complete, and CSS has been written in the styles.css
file for that class to render a strike-through line over the text.
Finally, in the anchor tag, we have included onClick="toDo.deleteTask(event, ${index})"
to call the deleteTask()
method of the toDo
object with parameters--the click event itself and the index of the task. We haven't defined the deleteTask()
method yet, so clicking on the delete icon is going to take you to the root of your file system!
Note
onclick
and onchange
are some of HTML attributes that are used to call JavaScript functions when the specified event occurs on the parent element on which the attributes are defined. Since these attributes belong to HTML, they are case insensitive.
Now, let's look at the second line of the loadTasks()
method:
document.getElementById('taskList').innerHTML = tasksHtml;
We just replaced the HTML code of the DOM element with the ID taskList
with our newly generated string tasksHTML
. Now, the ToDo List is populated. Time to define the two new methods of the toDo
object, which we included in our generated HTML code.
Inside ToDoClass
, include the two new methods:
toggleTaskStatus(index) {
this.tasks[index].isComplete = !this.tasks[index].isComplete;
this.loadTasks();
}
deleteTask(event, taskIndex) {
event.preventDefault();
this.tasks.splice(taskIndex, 1);
this.loadTasks();
}
The first method, toggleTaskStatus()
, is used to mark a task as completed or incomplete. It is called when a checkbox is clicked (onChange
) with the index of the task, which was clicked as the parameter:
- Using the task's index, we assign the task's
isComplete
status as the negation of its current status not using the (!)
operator. Hence, the completion status of the tasks can be toggled in this function. - Once the
tasks
variable is updated with new data, this.loadTasks()
is called to re-render all the tasks with the updated value.
The second method, deleteTask()
, is used to delete a task from the list. Currently, clicking the delete icon will take you to the root of the file system. However, before navigating you to the root of the file system, a call to toDo.deleteTask()
is made with the click event
and task's index
as the parameters:
- The first parameter
event
contains the entire event object that contains various properties and methods about the click event that just happened (try console.log(event)
inside the deleteTask()
function to see all the details in Chrome's console). - To prevent any default action (opening a URL) from happening once, we click the delete icon (the
<a>
tag). Initially, we need to specify event.preventDefault()
. - Then, we need to remove the task element of the array that was deleted from the
tasks
variable. For that, we use the splice()
method, which deletes a specified number of elements from an array from a specified index. In our case, from the index of the task, which needs to be deleted, delete only a single element. This removes the task to be deleted from the tasks
variable. this.loadTasks()
is called to re-render all the tasks with the updated value.
Refresh the page (hard reload if needed) to see how our current application works with the new code. You can now mark a task as completed and can delete a task from the list.
Adding new tasks to the list
We now have the options to toggle a task status and to delete a task. But we need to add more tasks to the list. For that, we need to use the text box provided in the HTML file to allow users to type in new tasks. The first step will be adding the onclick
attribute to the add task <button>
:
<button class="btn btn-primary" onclick="toDo.addTaskClick()">Add</button>
Now, every button click will call the addTaskClick()
method of the toDo
object, which is not yet defined. So, let's define it inside our ToDoClass
:
addTaskClick() {
let target = document.getElementById('addTask');
this.addTask(target.value);
target.value = ""
}
addTask(task) {
let newTask = {
task,
isComplete: false,
};
let parentDiv = document.getElementById('addTask').parentElement;
if(task === '') {
parentDiv.classList.add('has-error');
} else {
parentDiv.classList.remove('has-error');
this.tasks.push(newTask);
this.loadTasks();
}
}
Reload Chrome and try adding a new task by clicking the Add
button. If everything's fine, you should see a new task get appended to the list. Also, when you click the Add
button without typing anything in the input field, then it will highlight the input field with a red border, indicating the user should input text in the input field.
Note
See how I have divided our add task operation across two functions? I did a similar thing for the loadTask()
function. In programming, it is a best practice to organize all the tasks into smaller, more generic functions, which will allow you to reuse those functions in the future.
Let's see how the addTaskClick()
method works:
addTaskClick()
function doesn't have any request parameters. First, to read the new task's text, we get the <input>
element with the ID addTask
, which contains the text needed for the task. using document.getElementById('addTask')
, and assign it to target
variable. Now, the target
variable contains all the properties and methods of the <input>
element, which can be read and modified (try console.log(target)
to see all the details contained in the variable).- The
value
property contains the required text. So, we pass target.value
to the addTask()
function, which handles adding a new task to the list. - Finally, we reset the input field to an empty state by setting
target.value
to an empty string ''
.
That's the event handling part for the click event. Let's see how the task gets appended to the list in the addTask()
method. The task
variable contains the text for the new task:
- Ideally, the first step in this function is to construct the JSON data that defines our task:
let newTask = {
task: task,
isComplete: false
}
- Here's another ES6 feature object literal property value shorthand; instead of writing
{task: task}
in our JSON object, we can simply write {task}
. The variable name will become the key and the value stored in the variable becomes the value. This will throw an error if the variable is undefined. - We also need to create another variable
parentDiv
to store the object of the parent <div>
element of our target <input>
element. It's useful because, when the task is an empty string, we can add the has-error
class to the parent element parentDiv.classList.add('has-error')
, which by Bootstrap's CSS, renders a red border to our <input>
element. This is how we can indicate to the user that they need to enter a text before clicking the Add
button. - However, if the input text is not empty, we should remove the
has-error
class from our parent element to ensure the red border is not shown to the user and then simply push our newTask
variable to the tasks
variable of our class. Also, we need to call loadTasks()
again so that the new task gets rendered.
Adding tasks by hitting Enter button
Well, this is one way of adding tasks, but some users prefer adding tasks directly by hitting the Enter button. For that, let's use event listeners to detect the Enter key press in the <input>
element. We can also use the onchange
attribute of our <input>
element, but let's give event listeners a try. The best way to add event listeners to a class is to call them in the constructor so that the event listeners are set up when the class is initialized.
So, in our class, create a new function addEventListeners()
and call it in our constructor. We are going to add event listeners inside this function:
constructor() {
...
this.addEventListeners();
}
addEventListeners() {
document.getElementById('addTask').addEventListener('keypress', event => {
if(event.keyCode === 13) {
this.addTask(event.target.value);
event.target.value = '';
}
});
}
And that's it! Reload Chrome, type in the text, and hit Enter. This should add tasks to our list just like how the add button works. Let's go through our new event listener:
- For every keypress happening in the
<input>
element with the ID addTask
, we run the callback function with the event
object as the parameter. - This event object contains the keycode of the key that was pressed. For the Enter key, the keycode is 13. If the key code is equal to 13, we simply call the
this.addTask()
function with the task's text event.target.value
as its parameter. - Now, the
addTask()
function handles adding the task to the list. We can simply reset <input>
back to an empty string. This is a great advantage of organizing every operation into functions. We can simply reuse the functions wherever they're needed.
Persisting data in the browser
Now, functionality-wise, our ToDo List is ready. However, on refreshing the page, the data will be gone. Let's see how to persist data in the browser. Usually, web apps connect with APIs from the server-side to load data dynamically. Here, we are not looking into server-side implementation. So, we need to look for an alternate way to store data in the browser. There are three ways to store data in the browser. They are as follows:
cookie
: A cookie
is a small information that is stored on the client-side (browser) by the server with an expiry date. It is useful for reading information from the client, such as login tokens, user preferences, and so on. Cookies are primarily used on the server-side and the amount of data that can be stored in the cookie is limited to 4093 bytes. In JavaScript, cookies can be managed using the document.cookie
object.localStorage
: HTML5's localStorage
stores information with no expiry date and the data will persist even after closing and opening the web page. It provides a storage space of 5 MB per domain.
sessionStorage
: sessionStorage
is equivalent to that of localStorage
, except that the data is only valid per session (the current tab that the user is working on). The data expires when the website is closed.
For our use case, localStorage
is the best choice for persisting task data. localStorage
stores data as key-value pairs, while the value needs to be a string. Let's look at the implementation part. Inside the constructor, instead of assigning the value to this.tasks
directly, change it to the following:
constructor() {
this.tasks = JSON.parse(localStorage.getItem('TASKS'));
if(!this.tasks) {
this.tasks = [
{task: 'Go to Dentist', isComplete: false},
{task: 'Do Gardening', isComplete: true},
{task: 'Renew Library Account', isComplete: false},
];
}
...
}
We are going to save our tasks in localStorage
as a string with 'TASKS'
as its key. So when the user opens the website for the first time, we need to check whether any data is present in localStorage
with the key 'TASKS'
. If no data is present, it will return null
, which means this is the first time a user is visiting the website. We need to use JSON.parse()
to convert the data retrieved from localStorage
from a string to an object:
- If no data is present in
localStorage
(user visiting the site for the first time), we shall prepopulate some data for them using the tasks
variable. The best place to add the code to persist task data in our application will be the loadTasks()
function because it is called every time a change in tasks
is made. In the loadTasks()
function, add an additional line:
localStorage.setItem('TASKS', JSON.stringify(this.tasks));
- This will convert our
tasks
variable to string and store it in localStorage
. Now, you can add tasks and refresh the page, and the data will be persisted in your browser. - If you want to empty
localStorage
for development purposes, you can use localStorage.removeItem('TASKS')
to delete the key or you can use localStorage.clear()
to completely remove all the data stored in localStorage
.
Note
Everything in JavaScript has an inherent Boolean value, which can be called truthy or falsy. The following values are always falsy - null
, ""
(empty string), false
, 0
(zero), NaN
(not a number), and undefined
. Other values are considered truthy. Hence, they can be directly used in conditional statements like how we used if(!this.tasks) {}
in our code.
Now that our application is complete, you can remove the contents of the <ul>
element in the index.html
file. The contents will now be directly populated from our JavaScript code. Otherwise, you will see the default HTML code flash in the page when the page is loaded or refreshed. This is because our JavaScript code executes only after all the resources are finished loading due to the following code:
window.addEventListener("load", function() {
toDo = new ToDoClass();
});
If everything works fine, then congratulations! You have successfully built your first JavaScript application and you have learned about the new ES6 features of JavaScript. Oh wait! Looks like we forgot something important!
Note
All the storage options discussed here are unencrypted and, hence, should not be used for storing sensitive information, such as password, API keys, authentication tokens, and so on.
Compatibility with older browsers
While ES6 works with almost all modern browsers, there are still many users who use older versions of Internet Explorer or Firefox. So, how are we going to make our application work for them? Well, the good thing about ES6 is that all it's new features can be implemented using the ES5 specification. This means that we can easily transpile our code to ES5, which will work on all modern browsers. For this purpose, we are going to use Babel: https://babeljs.io/, as the compiler for converting ES6 to ES5.
Remember how, in the beginning of our chapter, we installed Node.js in our system? Well, it's finally time to use it. Before we start compiling our code to ES5, we need to learn about Node and the npm.
Node.js is a JavaScript runtime built on Chrome's V8 engine. It lets developers run JavaScript outside of the browser. Due to the non-blocking I/O model of Node.js, it is widely used for building data-intensive, real-time applications. You can use it to build backend for your web application in JavaScript, just like PHP, Ruby, or other server-side languages.
One great advantage of Node.js is that it lets you organize your code into modules. A module is a set of code used to perform a specific function. So far, we have included the JavaScript code one after another inside the <script>
tag in the browser. But in Node.js, we can simply call the dependency inside the code by creating the reference to the module. For example, if we need to jQuery, we can simply write the following:
const $ = require('jquery');
Or, we can write the following:
import $ from 'jquery';
The jQuery module will be included in our code. All the properties and methods of jQuery will be accessible inside the $
object. The scope of $
will be only within the file it is called. So, in each file, we can specify the dependencies individually and all of them will be bundled together during compilation.
But wait! For including jquery
, we need to download the jquery
package that contains the required module and save it in a folder. Then, we need to assign $
the reference of the file in the folder containing the module. And as the project grows, we will be adding a lot of packages and refer ring the modules in our code. So, how are we going to manage all the packages. Well, we have a nice little tool that gets installed along with Node.js called the Node Package Manager (npm):
- For Linux and Mac users, npm is similar to one of these:
apt-get
, yum
, dnf
, and Homebrew
. - For Windows users, you might not be familiar with the concept of package management yet. So, let's say you need jQuery. But you don't know what dependencies are needed for jQuery to run. That's where package managers come into play. You can simply run a command to install a package (
npm install jquery
). The package manager will read all the dependencies of the target package and install the target along with its dependencies. It also manages a file to keep track of installed packages. This is used for easily uninstalling the package in the future.
Note
Even though Node.js allows require/import of modules directly into the code, browsers do not support require or import functionality to directly import a module. But there are many tools available that can easily mimic this functionality so that we can use import/require inside our browsers. We'll use them for our project in the next chapter.
npm maintains a package.json
file to store information regarding a package, such as its name, scripts, dependencies, dev dependencies, repository, author, license, and so on. A package is a folder containing one or more folder or files with a package.json
file in its root folder. There are thousands of open source packages available in npm. Visit https://www.npmjs.com/ to explore the available packages. The packages can be modules that are used on the server-side or browser-side and command-line tools that are useful for performing various operations.
npm packages can be installed locally (per project) or globally (entire system). We can specify how we want to install it using different flags, as follows:
- If we want to install a package globally, we should use the
--global
or -g
flag. - If the package should be installed locally for a specific project, use the
--save
or -S
flag. - If the package should be installed locally and it is only used for development purposes, use the
--save-dev
or -D
flag. - If you run
npm install <package-name>
without any flags, it will install the package locally but will not update the package.json
file. It is not recommended to install packages without the -S
or -D
flags.
Let's install a command-line tool using npm called http-server
: https://www.npmjs.com/package/http-server. It is a simple tool that can be used to serve static files over an http-server
just like how files are served in Apache or Nginx. This is useful for testing and developing our web applications, since we can see how our application behaves when it's served through a web server.
Command-line tools are mostly recommended to install globally if they are going to be used only by ourselves and not by any other developer. In our case, we are only going to be using the http-server
package. So, let's install it globally. Open your Terminal/command prompt and run the following command:
npm install -g http-server
Note
If you are using Linux, some times you might face errors such as permission denied or unable to access file, and so on. Try running the same command as administrator (prefixed with sudo
) for installing the package globally.
Once the installation is complete, navigate to the root folder of our ToDo List app in your terminal and run the following command:
http-server
You will receive two URLs and the server will start running, as follows:
- To view the ToDo List app on your local device, open the URL starting with
127
in your browser - To view the ToDo List app on a different device connected to your local network, open the URL starting with
192
on the device's browser
Every time you open the application, http-server
will print the served files in the terminal. There are various options available with http-server
, such as -p
flag, which can be used to change the default port number 8080
(try http-server -p 8085
). Visit the http-server: https://www.npmjs.com/package/http-server, npm page for documentation on all available options. Now that we have a general idea of the npm
packages, let's install Babel to transpile our ES6 code to ES5.
Note
We will be using Terminals a lot in our upcoming chapters. If you are using VSCode, it has an inbuilt terminal, which can be opened by pressing Ctrl+` on Mac, Linux, and Windows. It also supports opening multiple terminal sessions at the same time. This can save you lot time on switching between windows.