NodeJS Learning Path
What is Node.js?
Node.js is an open-source and cross-platform JavaScript runtime environment. It is a popular tool for almost any kind of project! Node.js runs the V8 JavaScript engine, Google Chrome's core, outside the browser. This allows Node.js to be very performant. A Node.js app runs in a single process, without creating a new thread for every request. Node.js provides a set of asynchronous I/O primitives in its standard library that prevent JavaScript code from blocking and generally, libraries in Node.js are written using non-blocking paradigms, making blocking behavior the exception rather than the norm. You can read more about NodeJs and how it works on the official NodeJS website and their documentation.
What is event-loop in Node.js and how it works?
The Event Loop is one of the most critical aspects of Node.js. Why is this so important? Because it explains how Node.js can be asynchronous and have non-blocking I/O, it explains the "killer feature" of Node.js, which made it this successful.
Here are some resources for learning more about Event Loop:
How does single-threaded architecture work in NodeJs?
Node JS Processing model is mainly based on Javascript Event based model with Javascript callback mechanism. You should have some good knowledge about how Javascript events and callback mechanism works. If you are not familiar with the above mentioned concepts go through the following articles before continuing.
Now that you understand the architecture a little better, we can continue with the single threaded approach followed in NodeJS.Using the event-based model with JavaScript callback mechanism Nodejs can handle concurrent client requests with ease. The event loop is the heart of the Node.js processing model. A visual representation of event loop is provided in the previous section.
What are promises, callbacks and async/await calls in NodeJS?
These three are the fundamental concepts that NodeJS relies on to handle its asynchronous operations. A detailed comparison for all three is provided in this article.
Javascript | Promises
Promises are used to handle asynchronous operations in JavaScript. They are easy to manage when dealing with multiple asynchronous operations where callbacks can create callback hell leading to unmanageable code.
Promises are the ideal choice for handling asynchronous operations in the simplest manner. They can handle multiple asynchronous operations easily and provide better error handling than callbacks and events. In other words also, we may say that, promises are the ideal choice for handling multiple callbacks at the same time, thus avoiding the undesired callback hell situation. Promises do provide a better chance to a user to read the code in a more effective and efficient manner especially it that particular code is used for implementing multiple asynchronous operations.
Read more about promises here
Javascript | Callbacks
Callback is an asynchronous equivalent for a function. A callback function is called at the completion of a given task. Node makes heavy use of callbacks. All the APIs of Node are written in such a way that they support callbacks.
For example, a function to read a file may start reading file and return the control to the execution environment immediately so that the next instruction can be executed. Once file I/O is complete, it will call the callback function while passing the callback function, the content of the file as a parameter. So there is no blocking or wait for File I/O. This makes Node.js highly scalable, as it can process a high number of requests without waiting for any function to return results.
Read more about callback functions here
Javascript | Async/Await
In javascript an async function is declared with the async keyword and the await keyword is permitted within it. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains.
Async functions can contain zero or more await expressions. Await expressions make promise-returning functions behave as though they're synchronous by suspending execution until the returned promise is fulfilled or rejected. The resolved value of the promise is treated as the return value of the await expression. Use of async and await enables the use of ordinary try / catch blocks around asynchronous code.
Read more about javascript async/await here.
NodeJS Frameworks
A framework is a set of tools in programming on which to build well-structured, reliable software and systems. In this guide, we will explain what is a framework in programming in simple terms, explain why a framework is necessary, and detail various framework examples.
ExpressJS
ExpressJS is one of the most popular frameworks used in Nodejs. It is a minimal and flexible Node.js web application framework that provides a robust set of features to develop web and mobile applications. It facilitates the rapid development of Node based Web applications. Following are some of the core features of Express framework. It's a layer built on the top of the Node js that helps manage servers and routes.
Learn more about expressJS from their official documentation
NestJS
Nest.JS is a framework that helps build Node.JS server-side applications. The Nest framework is built using TypeScript which allows developers to build highly scalable and testable applications.
Built on top of Express.JS, Nest.JS offers multiple functionalities and out-of-the-box APIs. Developers can leverage the features of Nest.JS to build applications with less code. Nest.JS depends heavily on Angular architecture. A bunch of files is nested under a structured environment. Nest.JS divides the files into multiple modules helping developers focus on a single feature at a time.
Read more about how NestJS works and how its different from other nodejs frameworks here.
Go through through the official documentation for NestJS to get familiarized with the framework.
NodeJS project setup and structuring
Structuring your nodeJS application is a crucial task in order to make your code more readable and understandable for other people in your team. It's necessary to structure your project in such a way that your team has no issues understanding what each file in the project directory does.
A common way of structuring a NodeJs application is to use the MVC (Model-View-Controller) pattern which bundles everything into its own folder/package. Although this is a very common and useful way to structure your application, but if not maintained properly while scaling your application it can become confusing and jumbled up pretty quickly. It's better to document the flows of your application as well to allow your team and yourself as well to understand every aspect of project when it starts to grow.
An Example of this kind of project structure would be:
Another way would be to divide your application into sub-modules and bundle everything related to a single module in its own package/folder. This is the pattern followed by default in NestJS when you utilize its CLI. An example of how this may look like would be:
Additional Resources
In order to read more about project structuring best practices, you can checkout the articles listed below:
- Structure of a NodeJS API Project
- A better project structure with Express and Node.Js
- Project structure for an Express REST API
Getting started with ExpressJs
In order to get started with ExpressJS you must have NodeJS installed on you system. We're going to assume that you already have NodeJS installed but in case you don't have it, you can install by following the instructions here for Windows, Mac or Linux.
Once you're done with NodeJS installation, create a new folder anywhere on your system and open a command terminal within that folder and type the command npm init
or npm init -y
to accept all defaults for the package.json file. Once you have the package.json file in your folder, enter the command in terminal npm install express
to install the ExpressJS. No that we're done with installing all dependencies to run a simple express app, create a Example.js file so that we may move on to writing some code for a basic Hello World application:
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log("Example app listening on port: ",port)
})
After adding the code to your file, save it and write node Example.js
in your terminal to run the file. You will see the output:
Example app listening on port:3000
Congratulations on writing your first application in ExpressJS. In order to expand on this application and to include more functionality, we recommend you to go through the express docs to try some of the stuff mentioned there to learn more about how different aspects of an Express application work.
Routing in Express Applications
As per the expressJS documentation "Routing refers to how an application’s endpoints (URIs) respond to client requests".
We define routing using HTTP methods (GET, POST, PUT etc). These routing methods specify a callback function that executes when the HTTP method it is associated with is called either by browser or by using a tool like Postman. You can checkout the ExpressJS guide for understanding routing better.
Express Body-Parser
Express body-parser parses the incoming request bodies in a middleware before it reaches the handlers. It provides four express middleware for parsing JSON, Text, URL-encoded, and raw data sets over an HTTP request body. Before the target controller receives an incoming request, these middleware routines handle it.
You can install it using npm install body-parser
. To learn more about its functionality, you can checkout the official express docs.
Middleware in NodeJS:
The middleware in node.js is a function that will have all the access for requesting an object, responding to an object, and moving to the next middleware function in the application request-response cycle. This function can be used for modifying the req and res objects for tasks like adding response headers, parsing requesting bodies, and so on.
Different types of middlewares in Nodejs:
- Application-level middleware
- Router-level middleware
- Error-handling middleware
- Built-in middleware
- Third-party middleware
What is the Next() function?
If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging.
To learn more about internal workings of a middleware function, you can checkout this article
Enabling CORS in ExpressJS
CORS stands for cross-origin-resource-sharing. It is a HTTP header based mechanism that allows server to indicate origins that are different than its own from which a browser. This is all controlled through preflight requests that exchange a set of HTTP request headers and corresponding response headers collectively referred to as "CORS Headers", each of these headers modifies a different element of the Same-Origin policy to loosen the limitations it imposes.
In order to enable CORS in an Express application we just need to use the cors middleware. First install the package using npm install cors
. Then in your server code include the middleware using app.use(cors())
. This enables all cors i.e. request from any origin will be able to access resources on your server/api. In order to allow selected origins we can pas options in the cors() inside the middleware. E.g.
let corsOption = {origin : "http://dummyurl.com"}
This tells the middleware to only entertain requests originating from this url. Besides application level cors, we can specify cors for a single route by passing the middleware inside the route.
To learn more about cors and how express handles cors, you can checkout the official docs for cors package here.
Error handling in ExpressJS
Error handling functions in an application detect and capture multiple error conditions and take appropriate remedial actions to either recover from those errors or fail gracefully. Common examples of remedial actions are providing a helpful message as output, logging a message in an error log that can be used for diagnosis, or retrying the failed operation. In order to learn more about error handling, we would encourage you to check the official guide for Express error handling.
Working with Databases
One of the core functionalities in a backend application is the manipulation of data with different databases. Each database has its own driver which allows NodeJS applications to connect to that database.
There are a number of different databases but for our example we'll be using mysql. We start off with installing its driver by using npm install mysql
. Then we use the installed driver to establish connection with our database by using the mysql.createConnection()
function. For Example:
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'someuser',
password : 'somepassword',
database : 'somedatabase'
});
connection.connect();
The last line establishes connection with the database provided in the createConnection method options. In order to learn more about the mysql driver and how to run queries on our database using this driver, we would encourage you to read the official documentation for the mysql driver.
P.S We will be using an ORM (more on that later) for our DB related operations but its a good thing to know how to setup everything without an ORM too
Using ORMs
ORMs enable us to interact with databases using object-oriented paradigm rather than writing Sql queries. You can checkout this article to know more about ORMs and why we use them.
For the sake of this guide, we'll be using the Sequelize ORM which is one of the mosty widely used ORM with SQL type databases. There are other ORMs as well like TypeORM or Prisma but having good grasp over one of them would enable you to pick up the others without any problems whatsover since the core concept behind each is quite similar.
Seqeuelize ORM
As per the official documentation
"Sequelize is a modern TypeScript and Node.js ORM for Oracle, Postgres, MySQL, MariaDB, SQLite and SQL Server, and more. Featuring solid transaction support, relations, eager and lazy loading, read replication and more."
Sequelize has a vast array of topics which unfortunately cannot be cover within this guide. In order to learn more about Sequelize, you can visit their official documentation or watch this tutorial on how to setup and use sequelize with NodeJS and Postgres.
What are models?
Models refer to tables in databases. The model tells Sequelize several things about the entity it represents, such as the name of the table in the database and which columns it has (and their data types). IUsing sequelize we can define models as a function or as a class by extending the Model
class from Sequelize. To learn more about models in Sequelize, checkout the official documentation for Sequelize.
What are migrations?
Sequelize provides you with a migration feature to keep track of the changes to your database in a JavaScript file. By using migrations, you will be able to save instructions that change your database structures and definitions. Sequlieze migration has two functions, up()
to perform changes to database and down()
to revert/undo changes. To learn more about migrations, checkout the official documentation for Sequelize Migrations.
NodeJS contains a vast number of topics, tools and technologies that cannot be covered completely in this guid. We've provided you with the basics and fundamentals to get on track with NodeJS, In order to further enhance your grip on Node and its related tools, we would recommend you to go through this NodeJS roadmap and explore the resources suggested there.