Node.js and Express.js basics tutorial

Node.js and Express.js basics tutorial by webdevemonk

To get started with Node.js, it is recommended that you are familiar with JavaScript programming. If you haven't learned how to write code in JavaScript, it is recommended to learn that first. However, if you are already familiar with JavaScript, you can get started with Node.js right away.

JavaScript Tutorial: JavaScript

Concepts:

1. What is Node.js?

2. Installing Node.js

3. Hello World!

4. Node.js Core Modules

5. Global Objects

6. File system module

7. Path module

8. OS module

9. URL module

10. HTTP module

11. Events

12. Streams and Buffer

13. Introduction to NPM

14. Installing and Managing Packages

15. Package.json file

16. Event loop and Asynchronous Programming

17. Non-blocking I/O

18. Express.js

19. Routing

20. Handling HTTP requests (GET, POST, PUT, DELETE)

21. Middleware

22. Body parser

23. Static files

24. Template engines

25. Error Handling

26. Connetion to MySQL Database

27. Connetion to MongoDB Database

28. CRUD Operations using MySQL Database

29. CRUD Operations using MongoDB Database

1. What is Node.js?

Node.js, developed by Ryan Dahl in 2009, is a widely used open-source runtime environment that enables developers to create server-side applications using JavaScript.

It is built on top of the V8 JavaScript engine, the same engine that powers Google Chrome, making it exceptionally speedy and efficient.

Node.js stands out with its event-driven, non-blocking I/O model, which allows it to manage multiple requests simultaneously without blocking any threads. This results in superior performance and faster response times, making it an excellent choice for building high-performance applications that require handling a large number of concurrent connections.

2. Installing Node.js

Step 1: Download the Installer

To begin, visit the official Node.js website and download the appropriate installer for your operating system. Be sure to select the correct version that matches your system. You can find the download link at https://nodejs.org.

Step 2: Run the Installer

Once the installer has finished downloading, run it and follow the instructions provided to install Node.js on your computer. If you're using Windows, simply double-click the downloaded file and follow the prompts in the installation wizard. If you're on macOS, drag the downloaded file to your Applications folder and double-click it to open it.

Step 3: Verify the Installation

After the installation process is complete, open a command prompt or terminal window and type "node -v" to confirm that Node.js has been installed correctly. If everything has gone smoothly, you should see the version number of Node.js displayed.

That's it! With Node.js installed on your system, you're now ready to start building applications using this powerful framework.

3. Hello World!

In the text editor, create a new file and save it with a ".js" extension (e.g., "server.js").

In the file, write the following code


const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
     res.statusCode = 200;
     res.setHeader('Content-Type', 'text/plain');
     res.end('Hello World!');
});

server.listen(port, hostname, () => {
     console.log(`Server running at http://${hostname}:${port}/`);
});
     

This code uses the http module to create a server that listens for requests on port 3000. When a request is received, the server responds with a status code of 200 and the message "Hello World!".

Save the file and Open your command prompt or terminal, navigate to the directory where the "server.js" file is saved, and type the following command

node server.js

This will start the server and print the message "Server running at http://127.0.0.1:3000/" to the console.

Open your web browser and go to http://127.0.0.1:3000/. You should see the message "Hello World!" displayed on the page.

Congratulations, you've just written "Hello World!" in Node.js with a server!

4. Node.js Core Modules

Modules are collections of code that provide specific functionality to your Node.js applications.

Node.js Core Modules are built-in modules that come bundled with Node.js and provide fundamental functionality such as file system access, networking, and cryptography. Some examples of core modules are fs, http, https, crypto, path, os, and util.

To use these core modules, simply require them in your Node.js code, like so


const fs = require('fs');
const http = require('http');
const https = require('https');
const path = require('path');
const crypto = require('crypto');
const os = require('os');
const util = require('util');

Third-party Modules are modules that are created and maintained by the Node.js community and can be installed using the Node Package Manager (NPM). These modules provide additional functionality such as date parsing, database connectivity, and user authentication. Some popular third-party modules include Express, Mongoose, Socket.IO, Passport, and Moment.js.

5. Global Objects

Global objects in Node.js are objects that are available in all modules without the need for importing or requiring them. These objects provide built-in functionality that can be used to simplify your code and improve the efficiency of your applications.

Here are some commonly used global objects in Node.js

1. console: The console object provides a simple debugging tool for Node.js applications. It has several methods for outputting messages to the console, including log(), warn(), and error().

console.log("Hello, world!"); // Output: Hello, world!

2. process: The process object provides information about the Node.js process running your application, such as the command-line arguments used to start the process and the environment variables set for the process.

console.log(process.argv); // an array of command-line arguments passed to the Node.js process

3. setTimeout and setInterval: These global functions provide a way to execute a function after a certain amount of time has elapsed (for setTimeout) or at regular intervals (for setInterval).


setTimeout(() => {
console.log("Delayed Hello, world!"); // Output: Delayed Hello, world! (after 1 second)
}, 1000);

let counter = 1;
const intervalId = setInterval(() => {
console.log(`Interval ${counter++}`); // Output: Interval 1, Interval 2, Interval 3, ...
if (counter > 5) {
     clearInterval(intervalId);
}
}, 1000);
        

4. __dirname and __filename: These global variables provide the absolute path of the directory and file containing the currently executing module, respectively.

console.log(__dirname); // the absolute path of the directory containing the currently executing module
console.log(__filename); // the absolute path of the file containing the currently executing module
     

6. File system module

The file system module in Node.js is used to interact with the file system of your operating system. It provides functionality for reading, writing, updating, and deleting files and directories. To use this module, you need to require it at the beginning of your code like this

const fs = require('fs');

Once you have required the fs module, you can use its methods to perform various file system operations.

1. Reading a file

The "fs.readFile()" method is used to read the contents of a file. It takes two arguments: the path of the file to be read, and a callback function that will be called with the contents of the file as its argument.


fs.readFile('file.txt', (err, data) => {
     if (err) throw err;
     console.log(data.toString()); // the contents of file.txt
});
     

2. Writing to a file

The "fs.writeFile()" method is used to write data to a file. It takes three arguments: the path of the file to be written, the data to be written, and a callback function that will be called when the write operation is complete.


fs.writeFile('file.txt', 'Hello, World!', (err) => {
     if (err) throw err;
     console.log('Data written to file'); // Output: Data written to file
});
        

3. Updating a file

The "fs.appendFile()" method is used to append data to the end of a file. It takes two arguments: the path of the file to be updated, and the data to be appended.


fs.appendFile('file.txt', 'More data', (err) => {
     if (err) throw err;
     console.log('Data appended to file'); // Output: Data appended to file
});
        

4. Deleting a file

The fs.unlink() method is used to delete a file. It takes one argument: the path of the file to be deleted.


const fs = require('fs');

// Create a file named "example.txt"
fs.writeFile('example.txt', 'This is an example file', (err) => {
     if (err) throw err;
     console.log('File created');

     // Delete the file
     fs.unlink('example.txt', (err) => {
     if (err) throw err;
     console.log('File deleted');
     });
});
     

7. Path module

The path module in Node.js provides functionality for working with file and directory paths. It is included as a core module in Node.js, which means you can use it without installing any additional dependencies.

To use the path module in your Node.js application, you can require it at the beginning of your code like we did for 'fs' module

const path = require('path');

Once you have required the path module, you can use its methods to manipulate file and directory paths. Here are some of the most commonly used methods of the 'path' module

1. path.join(): this method is used to join one or more path segments together. It takes any number of arguments and returns a normalized path.

const pathSegments = ['dir1', 'dir2', 'file.txt'];
const filePath = path.join(...pathSegments);
console.log(filePath); // Output: dir1/dir2/file.txt

2. path.resolve(): This method is used to resolve a sequence of paths or path segments into an absolute path. It takes any number of arguments and returns an absolute path.

const absolutePath = path.resolve('dir1', 'dir2', 'file.txt');
console.log(absolutePath); // Output: /Users/username/dir1/dir2/file.txt

3. path.dirname(): This method is used to get the directory name of a path. It takes a path as its argument and returns the directory name.

const dirName = path.dirname('/Users/username/dir1/dir2/file.txt');
console.log(dirName); // Output: /Users/username/dir1/dir2

4. path.basename(): This method is used to get the basename of a path. It takes a path as its argument and returns the last portion of the path.

const baseName = path.basename('/Users/username/dir1/dir2/file.txt');
console.log(baseName); // Output: file.txt
     

5. path.extname(): This method is used to get the extension of a file. It takes a path as its argument and returns the extension (including the dot).

const extName = path.extname('/Users/username/dir1/dir2/file.txt');
console.log(extName); // Output: .txt

6. path.parse(): This method is used to parse a path into its components (root, dir, base, ext, and name). It takes a path as its argument and returns an object containing the components.


const pathObj = path.parse('/Users/username/dir1/dir2/file.txt');
console.log(pathObj);
// Output:
// {
//   root: '/',
//   dir: '/Users/username/dir1/dir2',
//   base: 'file.txt',
//   ext: '.txt',
//   name: 'file'
// }
     

8. OS module

The os module in Node.js provides a way to interact with the operating system on which Node.js is running. It is included as a core module in Node.js

To use the os module in your Node.js application, you can require it at the beginning of your code

const os = require('os');

Once you have required the os module, you can use its methods to get information about the operating system, such as the amount of free memory, the number of CPUs, the hostname, and more.

1. os.cpus(): This method returns an array of objects containing information about each CPU/core of the system.


const cpus = os.cpus();
console.log(cpus);
   
/* output: 
[
  {
    model: 'Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz',
    speed: 2592,
    times: { user: 2600635, nice: 0, sys: 1639343, idle: 48441171, irq: 0 }
  },
  {
    model: 'Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz',
    speed: 2592,
    times: { user: 170752, nice: 0, sys: 702847, idle: 53784550, irq: 0 }
  },
  {
    model: 'Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz',
    speed: 2592,
    times: { user: 298285, nice: 0, sys: 172573, idle: 53655062, irq: 0 }
  },
  {
    model: 'Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz',
    speed: 2592,
    times: { user: 243956, nice: 0, sys: 148228, idle: 53987286, irq: 0 }
  }
]
*/

2. os.freemem(): This method returns the amount of free system memory in bytes.


const freeMem = os.freemem();
console.log(freeMem); // Output: 1488206848

3. os.hostname(): This method will the hostname of the OS.


const hostName = os.hostname();
console.log(hostName); // Output: MacBook-Pro.local

4. os.platform(): This method returns the operating system platform.


const platform = os.platform();
console.log(platform); // Output: darwin

5. os.totalmem(): This method returns the total amount of system memory in bytes.


const totalMem = os.totalmem();
console.log(totalMem); // Output: 16777216000

6. os.type(): This method returns the operating system name.


const osType = os.type();
console.log(osType); // Output: Darwin

These are just a few examples of the os module's capabilities. The os module provides many more methods for interacting with the operating system, such as getting the load averages, the network interfaces, the system uptime, and more.

9. URL module

The url module in Node.js provides a way to parse, manipulate, and resolve URLs. It is also included as a core module in Node.js

You know that, to use the url module in your Node.js application, you can require it at the beginning of your code

const url = require('url');

Once you have required the url module, you can use its methods to parse URLs into their component parts, create URLs from their component parts, and resolve URLs against a base URL

1. url.parse(): This method parses a URL string and returns an object containing its component parts.


const parsedUrl = url.parse('https://www.example.com/path?foo=bar#fragment');
console.log(parsedUrl);

/* 
Output:

Url {
     protocol: 'https:',
     slashes: true,
     auth: null,
     host: 'www.example.com',
     port: null,
     hostname: 'www.example.com',
     hash: '#fragment',
     search: '?foo=bar',
     query: 'foo=bar',
     pathname: '/path',
     path: '/path?foo=bar',
     href: 'https://www.example.com/path?foo=bar#fragment'
   }
*/   

2. url.format(): This method takes an object containing URL components and returns a formatted URL string.


const formattedUrl = url.format({
     protocol: 'https:',
     hostname: 'www.example.com',
     pathname: '/path',
     search: '?foo=bar'
     });
console.log(formattedUrl); // Output: https://www.example.com/path?foo=bar

3. url.resolve(): This method resolves a URL against a base URL and returns the result.


const resolvedUrl = url.resolve('https://www.example.com', '/path');
console.log(resolvedUrl); // Output: https://www.example.com/path

10. HTTP module

The HTTP module in Node.js allows you to make HTTP requests and create HTTP servers. It provides a simple interface for sending and receiving data over HTTP, making it a powerful tool for building web applications and APIs.

1. http.createServer()This method creates an HTTP server that listens for incoming requests on a specified port.


const http = require('http');

const server = http.createServer((req, res) => {
     res.statusCode = 200;
     res.setHeader('Content-Type', 'text/plain');
     res.end('Hello, world!');
});

server.listen(3000, () => {
     console.log('Server running on http://localhost:3000/');
});
     

Here we're creating an HTTP server that listens on the port 3000. When a request is received, we set the status code to 200, set the Content-Type header to text/plain, and send the response body "Hello, world!" using the res.end() method.

2. http.request() This method sends an HTTP request to a specified URL and returns a http.ClientRequest object


const http = require('http');

const options = {
     hostname: 'www.google.com',
     port: 80,
     path: '/',
     method: 'GET'
};

const req = http.request(options, (res) => {
     console.log(`statusCode: ${res.statusCode}`);

     res.on('data', (data) => {
     console.log(data.toString());
     });
});

req.on('error', (error) => {
     console.error(error);
});

req.end();
     

In this example, we're sending an HTTP GET request to www.google.com. We define the request options, including the hostname, port, path, and method, and create an http.ClientRequest object using the http.request() method. We then handle the response using a callback function that logs the response status code and any data received from the server.

3. http.get()This method sends an HTTP GET request to a specified URL and returns a http.ClientRequest object.


const http = require('http');

http.get('http://www.google.com/', (res) => {
     console.log(`statusCode: ${res.statusCode}`);

     res.on('data', (data) => {
     console.log(data.toString());
     });
}).on('error', (error) => {
     console.error(error);
});
     

In this example, we're sending an HTTP GET request to www.google.com using the http.get() method. We handle the response using a callback function that logs the response status code and any data received from the server.

Sending JSON Data

To send JSON data in an HTTP request, you can use the JSON.stringify() method to convert an object to a JSON string, and then set the Content-Type header to application/json.


const http = require('http');

const data = JSON.stringify({
     name: 'John Doe',
     email: 'johndoe@example.com'
});

const options = {
     hostname: 'httpbin.org',
     port: 80,
     path: '/post',
     method: 'POST',
     headers: {
     'Content-Type': 'application/json',
     'Content-Length': data.length
     }
};

const req = http.request(options, (res) => {
     console.log(`statusCode: ${res.statusCode}`);

     res.on('data', (data) => {
     console.log(data.toString());
     });
});

req.on('error', (error) => {
     console.error(error);
});

req.write(data);
req.end();
     

In this example, we're sending a JSON object to the httpbin.org API using a POST request. We define the data as a JSON string using JSON.stringify(), and set the Content-Type header to application/json to indicate that we're sending JSON data.

Sending Form Data

To send form data in an HTTP request, you can use the querystring module to convert an object to a URL-encoded string, and then set the Content-Type header to application/x-www-form-urlencoded.


const http = require('http');
const querystring = require('querystring');

const data = querystring.stringify({
name: 'John Doe',
email: 'johndoe@example.com'
});

const options = {
hostname: 'httpbin.org',
port: 80,
path: '/post',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': data.length
}
};

const req = http.request(options, (res) => {
console.log(`statusCode: ${res.statusCode}`);

res.on('data', (data) => {
console.log(data.toString());
});
});

req.on('error', (error) => {
console.error(error);
});

req.write(data);
req.end();

Redirecting HTTP Requests

To redirect an HTTP request to another URL, you can set the Location header to the new URL and the 302 status code.


const http = require('http');

const server = http.createServer((req, res) => {
     res.statusCode = 302;
     res.setHeader('Location', 'https://www.google.com/');
     res.end();
});

server.listen(3000, () => {
     console.log('Server running on http://localhost:3000/');
});

In this example, we're creating an HTTP server that redirects all requests to Google's home page. We set the Location header to https://www.google.com/ and the status code to 302 to indicate that the request has been temporarily moved.

11. Events

In Node.js, events are a core part of the programming model. They allow developers to write code that can respond to specific actions or behaviors that occur in the application, such as a user clicking a button or a file finishing loading.

Events in Node.js are based on the Observer design pattern, where an object maintains a list of its dependents (observers) and notifies them automatically of any state changes. In Node.js, the object that emits the events is called an EventEmitter.

EventEmitter class

The EventEmitter class is a core module in Node.js that provides the ability to emit named events and attach listeners to those events. To use the EventEmitter class, we need to first create an instance of it, which can be done using the following code


const EventEmitter = require('events');
const myEmitter = new EventEmitter();

This creates an instance of the EventEmitter class and assigns it to the myEmitter variable. We can now use the methods of the myEmitter object to emit events and attach listeners to those events.

Emitting Events

To emit an event, we can use the emit() method of the EventEmitter class. The emit() method takes two arguments: the name of the event, and an optional data object that will be passed to the event handler function.

myEmitter.emit('hello');

This code emits an event named 'hello' using the myEmitter object.

Listening for Events

To listen for an event, we can use the on() method of the EventEmitter class. The on() method takes two arguments: the name of the event to listen for, and a callback function that will be executed when the event is emitted.


myEmitter.on('hello', () => {
     console.log('Hello, world!');
});        

This code listens for an event named 'hello' using the myEmitter object, and when the event is emitted, it logs the message 'Hello, world!' to the console.

Removing Event Listeners

To remove an event listener, we can use the removeListener() method of the EventEmitter class. The removeListener() method takes two arguments: the name of the event to remove the listener from, and the listener function to remove.


const helloListener = () => {
console.log('Hello, world!');
};

myEmitter.on('hello', helloListener);

// Remove the helloListener function from the 'hello' event
myEmitter.removeListener('hello', helloListener);

This code attaches an event listener to the 'hello' event using the myEmitter object, and then removes the event listener using the removeListener() method.

Once

The once() method of the EventEmitter class is similar to the on() method, but it only listens for the event to be emitted once. After the event is emitted and the listener function is executed, the listener is automatically removed.


myEmitter.once('hello', () => {
console.log('Hello, world!');
});

// Emit the 'hello' event
myEmitter.emit('hello');

// The following line won't log anything because the listener was removed after it was executed
myEmitter.emit('hello');
        

This code listens for the 'hello' event using the once() method, emits the event once using the emit() method, and then tries to emit the event again. The second emit() call won't log anything because the listener was removed after it.

example:


const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

// Register the listener using the 'prependListener' method
myEmitter.prependListener('greet', () => {
     console.log('Hello, world!');
});

// Register another listener using the 'on' method
myEmitter.on('greet', (name) => {
     console.log(`Hello, ${name}!`);
});

// Emit the 'greet' event twice, with different arguments
myEmitter.emit('greet', 'John');
myEmitter.emit('greet', 'Jane');

/*
Output:

Hello, world!
Hello, John!
Hello, world!
Hello, Jane!
*/

     

12. Streams and Buffer

Streams

Streams are a way to read and write data in small chunks, rather than loading entire files or data sets into memory at once. They are used in Node.js to read and write data to and from files, network sockets, and other sources of data.

Streams are implemented in Node.js as instances of the stream.Readable and stream.Writable classes. There are also other classes such as stream.Duplex and stream.Transform that combine both readable and writable streams.


const fs = require('fs');

const stream = fs.createReadStream('example.txt');

stream.on('data', (chunk) => {
     console.log(chunk);
});

stream.on('end', () => {
     console.log('Finished reading file');
});
     

In this example, we create a readable stream using the fs.createReadStream() method, which reads data from a file and returns a stream object. We then attach event listeners to the stream object for the 'data' and 'end' events. The 'data' event is triggered whenever new data is available to be read from the stream, and we log each chunk of data to the console. The 'end' event is triggered when the stream has finished reading all of the data.

Buffers

Buffers are used to represent and manipulate binary data in Node.js. They are essentially fixed-size chunks of memory that can be used to store and manipulate binary data. Buffers are used extensively in Node.js for handling network communication, file I/O, and other types of data processing.

Buffers can be created in several ways, such as from a string, an array, or by allocating a specific amount of memory.


const buffer = Buffer.alloc(8);

buffer.writeUInt32BE(0xfeedface, 0);
buffer.writeUInt32BE(0xdeadbeef, 4);

console.log(buffer.toString('hex')); // feedfacedeadbeef

In this example, we create a new buffer with a length of 8 bytes using the Buffer.alloc() method. We then use the writeUInt32BE() method to write two 32-bit unsigned integers to the buffer, starting at byte offset 0 and 4, respectively. Finally, we log the contents of the buffer as a hexadecimal string using the toString() method.

13. Introduction to NPM

NPM (short for Node Package Manager) is a package manager for Node.js that allows you to easily install, manage, and share third-party libraries and modules. It is the default package manager for Node.js, and it comes pre-installed with Node.js.

NPM Commands

NPM has a variety of commands that can be used to manage packages and modules.

npm init: This command initializes a new Node.js project and creates a package.json file that contains metadata about the project and its dependencies.

npm install: This command installs packages and modules from the NPM registry. You can specify the package name and version as arguments, or you can specify them in the package.json file and run npm install without any arguments to install all the dependencies listed in the package.json file.

npm uninstall: This command uninstalls packages and modules that are installed in the project. You can specify the package name as an argument, or you can remove the package from the package.json file and run npm install to remove it.

npm update: This command updates the installed packages and modules to their latest version.

npm search: This command searches the NPM registry for packages and modules that match a given keyword.

npm publish: This command publishes your own package or module to the NPM registry.

NPM Packages

NPM packages are collections of files and code that are designed to be installed and used in Node.js projects. They can include JavaScript files, configuration files, documentation, and other resources.

When you install an NPM package, its files and code are downloaded and installed in a 'node_modules' directory in your project. You can then import the package's code and use it in your own code.

NPM packages are published to the NPM registry, which is a public repository of packages and modules. You can search the registry for packages that meet your needs, and then install them in your projects using the npm install command (eg: npm install loadash, npm install express, etc.).

14. Installing and Managing Packages

If you want to install a specific version of a package, you can specify the version number using the '@' symbol. For example, to install version 2.1.1 of the 'axios' package, you can run

npm install axios@2.1.1

You can also install multiple packages at once by separating their names with spaces. For example, to install both 'express' and 'axios'

npm install express axios

Managing Packages

To view a list of all the packages installed in your project, you can use the 'npm ls' command. This will display a tree-like structure of all the installed packages and their dependencies.

If you want to update a package to its latest version, you can use the npm update command followed by the name of the package. For example, to update the express package to its latest version, you can run

npm update express

If you want to update all the packages in your project to their latest versions, you can use the 'npm update' command without specifying a package name.

npm update

To remove a package from your project, you can use the 'npm uninstall' command followed by the name of the package. For example, to uninstall the axios package, you can run

npm uninstall axios

15. Package.json file

The 'package.json' file is a configuration file that is used to specify various properties of an application or package. This file is used by the npm package manager to identify the project and manage its dependencies.


{
"name": "my-app",
"version": "1.0.0",
"description": "My awesome application",
"main": "index.js",
"scripts": {
     "start": "node index.js"
},
"dependencies": {
     "express": "^4.17.1",
     "body-parser": "^1.19.0"
}
}
        

In this example, we have defined the following properties

name: The name of the project or package.

version: The version number of the project or package.

description: A brief description of the project or package.

main: The entry point for the application or package.

scripts: A set of scripts that can be run using the npm run command.

dependencies: A list of dependencies required by the application or package.

16. Event loop and Asynchronous Programming

Asynchronus programming

Asynchronous programming is a programming paradigm that allows for the execution of multiple tasks concurrently. In Node.js, asynchronous programming is used extensively to allow for non-blocking I/O operations. Rather than waiting for a task to complete before moving on to the next one, Node.js can initiate multiple tasks and execute them concurrently. This allows for more efficient use of system resources and can improve the overall performance of an application.


const fs = require('fs');

fs.readFile('myfile.txt', 'utf8', function (err, data) {
     if (err) throw err;
     console.log(data);
});

console.log('Reading file...');
     

In this example, we use the fs.readFile() method to read the contents of a file asynchronously. When the file is read, the specified callback function is executed, and the contents of the file are printed to the console. In the meantime, the console.log('Reading file...') statement is executed immediately, rather than waiting for the file to be read.

Event Loops

The event loop is the mechanism by which Node.js handles I/O operations. The event loop continuously checks for events and executes any associated callback functions. When an I/O operation is initiated, it is added to a queue, and the event loop will continue to check the queue for any completed operations. Once an operation is completed, the associated callback function is added to the event loop for execution. This allows Node.js to handle a large number of I/O operations in a non-blocking way.


console.log('Starting...');

setTimeout(function() {
     console.log('1 second passed.');
}, 1000);

setTimeout(function() {
     console.log('2 seconds passed.');
}, 2000);

setTimeout(function() {
     console.log('3 seconds passed.');
}, 3000);

console.log('End.');
 
/*
Output: 
Starting...
End.
1 second passed.
2 seconds passed.
3 seconds passed.
*/

In this example, we use the setTimeout() function to delay the execution of some code. The first setTimeout() function waits for 1 second, the second waits for 2 seconds, and the third waits for 3 seconds. After each delay, a message is printed to the console.

As you can see, the console.log('Starting...') statement is executed immediately, followed by the setTimeout() functions. These functions are added to the event loop and are not executed immediately. Instead, they wait for the specified amount of time (in this case, 1, 2, and 3 seconds) before being added to the event loop for execution.

While the setTimeout() functions are waiting, the console.log('End.') statement is executed immediately, as it is not part of the event loop. Once the setTimeout() functions are added to the event loop, they are executed in the order that they were added. This results in the messages being printed to the console in the order of 1 second passed, 2 seconds passed, and 3 seconds passed.

17. Non-blocking I/O

Non-blocking I/O is a technique used in computer programming that allows multiple operations to be performed concurrently, without blocking each other. In Node.js, non-blocking I/O is achieved through the use of asynchronous functions and callbacks.

When a Node.js application makes an I/O request (such as reading from a file or making a network request), the application does not wait for the operation to complete. Instead, Node.js adds the request to a queue and continues executing the rest of the application code. When the I/O operation completes, Node.js retrieves the result and executes the callback function associated with the operation.


const fs = require('fs');

console.log('Starting...');

fs.readFile('example.txt', 'utf8', function(err, data) {
     if (err) {
     console.error(err);
     } else {
     console.log(data);
     }
});

console.log('End.');

/*
Starting...
End.
Hello, world!
*/

In this Node.js example, we use the fs.readFile() function to read the contents of a file called example.txt. We provide a callback function as the last argument to fs.readFile(), which is executed once the file has been read.

The console.log('Starting...') statement is executed first, followed by the fs.readFile() function. Instead of blocking the rest of the application, the fs.readFile() function returns immediately and Node.js continues executing the rest of the code.

The console.log('End.') statement is executed before the fs.readFile() function completes, because fs.readFile() is a non-blocking I/O operation. When the file has been read, Node.js executes the callback function provided to fs.readFile(). This function logs the contents of the file to the console.

example

Suppose we have a Node.js server that needs to handle multiple requests simultaneously. For each request, the server needs to make a database query to fetch some data. Without non-blocking I/O, the server would have to wait for each query to complete before it can move on to the next request. This would make the server slow and unresponsive.

const http = require('http');
const mysql = require('mysql');

const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'mydatabase'
});

const server = http.createServer((req, res) => {
connection.query('SELECT * FROM users', (err, results) => {
if (err) {
     console.error(err);
     res.statusCode = 500;
     res.end('Internal Server Error');
} else {
     res.setHeader('Content-Type', 'application/json');
     res.statusCode = 200;
     res.end(JSON.stringify(results));
}
});
});

server.listen(3000, () => {
console.log('Server listening on port 3000');
});

In this example, we create a Node.js server that listens on port 3000. Whenever a request is made to the server, it executes a database query to fetch all the users from a MySQL database. We use the mysql module to create a connection to the database and execute the query.

The connection.query() function is a non-blocking I/O operation, which means that it returns immediately and does not block the rest of the application. Once the query completes, Node.js executes the callback function provided to connection.query(), which sends the results back to the client.

18. Express

Express.js is a popular Node.js web application framework that allows developers to easily create robust and scalable web applications. It provides a simple and intuitive interface for handling HTTP requests, routing, middleware, and view rendering. Express.js is built on top of Node.js and leverages its non-blocking I/O model to provide fast and efficient performance.

To install Express.js, you need to have Node.js installed on your system. Once you have Node.js installed, you can use npm (Node Package Manager) to install Express.js.

npm install express

This will install the latest version of Express.js in your project. Once you have installed Express.js, lets create a basic "Hello World" web application using the following code


const express = require('express')
const app = express()

app.get('/', (req, res) => {
     res.send('Hello World!')
})

app.listen(5000, () => {
     console.log('app is listening on port 5000!')
})

1. First, we import the Express.js module using the 'require()' function and create a new Express.js application by calling 'express()'.

2. Next, we define a route for the root URL (/) using the app.get() method. When a GET request is made to this URL, we send the response "Hello World!" using the res.send() method.

3. Finally, we start the Express.js application by calling app.listen() and passing in the port number we want the application to listen on (in this case, 3000). We also log a message to the console to indicate that the application is running.

To run this code, save it to a file (e.g. app.js) and run the following command in your terminal or command prompt

node app.js

This will start the Express.js application and it will be accessible at http://localhost:5000. When you navigate to this URL in your web browser, you should see the message "Hello World!" displayed on the page.

19. Routing

In the context of an Express.js application, routing refers to determining how an application responds to a client request to a particular endpoint, which is associated with a specific HTTP method (GET, POST, PUT, DELETE, etc.).

In Express.js, routing is achieved using the 'express.Router()' method. This method returns an instance of a router that can be used to define application endpoints. The router instance can be associated with an Express.js application using the 'app.use()' method.


const express = require('express');
const app = express();

app.get('/', (req, res) => {
     res.send('Hello, World!');
});

app.listen(3000, () => {
     console.log('Server started on port 3000');
});
     

In this example, we're defining a basic route for the root URL (/) of our application using the app.get() method. The app.get() method takes two arguments: the route and a callback function that is executed when the route is requested. In this case, we're sending a response of Hello, World! to the client.

The app.listen() method is used to start the server and listen for incoming requests. Here, we're telling the server to listen on port 3000.

Once the server is started, we can test our route by visiting http://localhost:3000 in a web browser.

example


const express = require('express');
const app = express();

//  GET route for the root URL
app.get('/', function(req, res) {
     res.send('Hello, World!');
});

//  POST route for submitting a form
app.post('/submit-form', function(req, res) {
     const name = req.body.name;
     const email = req.body.email;
     // Process the form data
     res.send(`Thank you for submitting the form, ${name}! We will contact you at ${email}.`);
});

// PUT route for updating a user record
app.put('/users/:id', function(req, res) {
     const userId = req.params.id;
     // Update the user record in the database
     res.send(`User with ID ${userId} updated successfully.`);
});

// DELETE route for deleting a user record
app.delete('/users/:id', function(req, res) {
     const userId = req.params.id;
     // Delete the user record from the database
     res.send(`User with ID ${userId} deleted successfully.`);
});

// Start the server
app.listen(3000, function() {
     console.log('Server started on port 3000');
});
     

In this example, we have defined routes for the root URL (/), a POST route for submitting a form (/submit-form), a PUT route for updating a user record (/users/:id), and a DELETE route for deleting a user record (/users/:id).

The GET route for the root URL simply sends back a "Hello, World!" message as the response.

The POST route for submitting a form expects the form data to be sent in the request body and processes it accordingly. It then sends a response back to the client with a personalized message.

The PUT and DELETE routes for updating and deleting a user record, respectively, expect the user ID to be included in the URL as a parameter (e.g., /users/123) and perform the respective actions in the database before sending a response back to the client.

Note: This is just a basic example, and in a real-world application, you would likely have many more routes and more complex logic within each route handler function.

20. Handling HTTP requests (GET, POST, PUT, DELETE)

To handle HTTP requests, we can create routes in our Express application. Each route can handle a specific HTTP method such as GET, POST, PUT, or DELETE.

GET


const express = require('express');

// Create an instance of the Express application
const app = express();

// Handle GET request for the root route
app.get('/', function(req, res) {
     res.send('Hello World!');
});

// Start the server
app.listen(3000, function() {
     console.log('Server listening on port 3000');
});
     

In this example, we have created a route for the root route using the app.get() method. This method takes two parameters: the route and a callback function to handle the request.

The callback function takes two parameters: the request object (req) and the response object (res). In this example, we are sending the response "Hello World!" using the res.send() method.

POST


const express = require('express');

// Create an instance of the Express application
const app = express();

// Handle POST request for the root route
app.post('/', function(req, res) {
     res.send('Got a POST request');
});

// Start the server
app.listen(3000, function() {
     console.log('Server listening on port 3000');
});
     

In this example, we have created a route for the root route using the app.post() method. This method takes two parameters: the route and a callback function to handle the request.

The callback function takes two parameters: the request object (req) and the response object (res). In this example, we are sending the response "Got a POST request" using the res.send() method.

PUT


const express = require('express');

// Create an instance of the Express application
const app = express();

// Handle PUT request for the root route
app.put('/', function(req, res) {
     res.send('Got a PUT request');
});

// Start the server
app.listen(3000, function() {
     console.log('Server listening on port 3000');
});
     

In this example, we have created a route for the root route using the app.put() method. This method takes two parameters: the route and a callback function to handle the request.

The callback function takes two parameters: the request object (req) and the response object (res). In this example, we are sending the response "Got a PUT request" using the res.send() method.

DELETE


const express = require('express');

// Create an instance of the Express application
const app = express();

// Handle DELETE request for the root route
app.delete('/', function(req, res) {
     res.send('Got a DELETE request');
});

// Start the server
app.listen(3000, function() {
     console.log('Server listening on port 3000');
});

In this example, we have created a route for the root route using the app.delete() method. This method takes two parameters: the route and a callback function to handle the request.

The callback function takes two parameters: the request object (req) and the response object (res).

21. Middleware

Middleware in Express.js is a function that can modify the request and response objects, execute additional code, and pass the request to the next middleware function in the chain. Middleware functions can be used to perform tasks like parsing request bodies, handling authentication, logging, and much more.

const express = require('express');
const app = express();

// custom middleware
const logMiddleware = (req, res, next) => {
     console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
     next(); // pass the request to the next middleware function
};

app.use(logMiddleware);

app.get('/', (req, res) => {
     res.send('Hello, world!');
});

app.listen(5000, () => {
     console.log('Server listening on port 5000');
});

In the above example, we define a middleware function called logMiddleware that logs the current date and time, the HTTP method and the URL of the incoming request. Then, we use the app.use() method to register this middleware function for all routes.

When we send a GET request to the root route (/), the middleware function gets executed and logs the request details to the console before passing the request to the next middleware function (app.get() in this case). Finally, the route handler sends the response back to the client with the message "Hello, world!".

22. Body Parser

Body parser is a middleware module that is used to extract the entire body portion of an incoming request stream and exposes it on req.body. It can be used to parse various types of request data such as JSON, URL-encoded, text, raw and XML.


const express = require('express');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json()); // parse JSON-encoded request bodies

app.post('/users', (req, res) => {
     const { name, email } = req.body; // extract name and email from request body
     // save the user to the database...
     res.status(201).send('User created successfully');
});

app.listen(3000, () => {
     console.log('Server listening on port 3000');
});
     

In the above example, we first import and use the body-parser middleware by calling app.use(bodyParser.json()). This instructs the middleware to parse any incoming request with a JSON payload and store it in the req.body property.

We then define a POST route /users, where we extract the name and email fields from the request body using destructuring. Finally, we save the user to the database and send a response with status 201 and a success message.

Note that there are different types of request bodies that can be parsed by body-parser, such as 'urlencoded', 'text', and 'raw'. You can specify the type of the incoming request body by calling the appropriate body-parser method in app.use().

23. Static Files

Static files refer to files such as images, CSS, and JavaScript files that do not change dynamically and are served directly to the client. These files are typically stored in a public directory in the server and are accessible to the client through a URL.

In Express.js, serving static files is made easy using the express.static middleware function. T


const express = require('express');
const app = express();

// serve static files from the public directory
app.use(express.static('public'));

// start the server
app.listen(3000, () => {
     console.log('Server started on port 3000');
});

In this example, the express.static middleware function is used to serve static files from the public directory. The files in this directory can be accessed by clients by using the URL path relative to the public directory. For example, if there is a file named index.html in the public directory, it can be accessed by the client using the URL http://localhost:3000/index.html.

Note that the directory name passed to express.static is relative to the root directory of the application. So if the public directory is located in a subdirectory of the root directory, the directory name should include the path to the subdirectory. For example, if the public directory is located in the static subdirectory of the root directory, the middleware should be defined like this

app.use(express.static('static/public'));

This will serve the static files from the public directory inside the static directory.

24. Template engines

Template engines are used to generate dynamic HTML pages in web applications. They allow developers to define reusable HTML templates with placeholders that are filled with dynamic data at runtime. This makes it easier to build web applications that have consistent layouts and styling, while also being able to generate dynamic content on the server-side.

Express.js supports a number of popular template engines including EJS, Pug (formerly known as Jade), Handlebars, and Mustache. These engines provide a simple syntax for defining templates, and can be easily integrated into an Express.js application.

To use a template engine in an Express.js application, you need to install the engine using NPM, and then configure your application to use the engine.

1. Install EJS using NPM

npm install ejs --save

2. Configure your Express.js application to use EJS


const express = require('express');
const app = express();

app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');

app.get('/', (req, res) => {
     res.render('index', { title: 'Welcome to my website', message: 'This is my first website using Express.js and EJS' });
});

app.listen(3000, () => {
     console.log('Server listening on port 3000');
});
     

In this example, we set the view engine to ejs using app.set('view engine', 'ejs'). We also set the views directory using app.set('views', __dirname + '/views'). This tells Express.js where to find the template files.

The res.render method is used to render the index.ejs template and pass in the data for the placeholders. The data is passed in as an object with key-value pairs.

example of the index.ejs template


<!DOCTYPE html>
<html>
     <head>
          <title><%= title %></title>
     </head>
     <body>
          <h1><%= message %></h1>
     </body>
</html>
     

25. Error Handling

The basic idea is to define an error-handling middleware function with four arguments: 'err', 'req', 'res', and 'next'. The error-handling middleware function should be defined last in the middleware chain.


app.use((err, req, res, next) => {
     console.error(err.stack);
     res.status(500).send('Something went wrong!');
});

In this example, app.use() is used to define the middleware function. The function takes four arguments, with the first one being the error object err. The console.error() function is used to log the error stack to the console. The res.status() function is used to set the status code of the response to 500 (Internal Server Error). The res.send() function is used to send a generic error message to the client.

To use this error-handling middleware function, you can simply call the next() function with an error object in any middleware function.

example


app.get('/user/:id', (req, res, next) => {
const id = req.params.id;
if (id === '0') {
     next(new Error('Invalid user ID'));
} else {
     res.send(`User ID: ${id}`);
}
});

In this example, the next() function is called with an error object if the user ID is 0. When the error object is passed to the next() function, it will skip all the remaining middleware functions and jump directly to the error-handling middleware function.

26. Connetion to MySQL Database

Connecting to MySQL database from a Node.js application involves installing the MySQL driver for Node.js and then using it to create a connection to the database. Here is a step-by-step tutorial on how to connect to a MySQL database from a Node.js application:

Step 1: Install the MySQL driver for Node.js using NPM

Open your terminal and run the following command to install the mysql package from NPM

npm install mysql

Step 2: Create a connection to the MySQL database

In your Node.js application, require the mysql package and use the createConnection method to create a connection to the MySQL database.


const mysql = require('mysql');

const connection = mysql.createConnection({
     host: 'localhost',
     user: 'root',
     password: 'password',
     database: 'mydatabase'
});

connection.connect((error) => {
     if (error) {
     console.error('Error connecting to MySQL database: ' + error.stack);
     return;
     }
     console.log('Connected to MySQL database with connection id ' + connection.threadId);
});
     

In the above example, replace the host, user, password, and database properties with your MySQL server details.

Step 3: Execute queries on the MySQL database

Once you have a connection to the MySQL database, you can use it to execute queries on the database using the query method of the connection object.


const mysql = require('mysql');

const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'mydatabase'
});

connection.connect((error) => {
if (error) {
console.error('Error connecting to MySQL database: ' + error.stack);
return;
}
console.log('Connected to MySQL database with connection id ' + connection.threadId);

connection.query('SELECT * FROM users', (error, results, fields) => {
if (error) {
     console.error('Error executing query: ' + error.stack);
     return;
}
console.log('Results:', results);
});
});
     

In the above example, the query method is used to execute a SELECT statement on the users table in the mydatabase database. The callback function is called with the error (if any), the results of the query, and metadata about the fields in the results.

Step 4: Close the connection to the MySQL database

When you are done using the connection to the MySQL database, you should close the connection using the end method of the connection object.


const mysql = require('mysql');

const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'mydatabase'
});

connection.connect((error) => {
if (error) {
console.error('Error connecting to MySQL database: ' + error.stack);
return;
}
console.log('Connected to MySQL database with connection id ' + connection.threadId);

connection.end((error) => {
if (error) {
     console.error('Error closing connection to MySQL database: ' + error.stack);
     return;
}
console.log('Connection to MySQL database closed.');
});
});
     

In the above example, the 'end' method is used to close the connection to the MySQL database. The callback function is called with the error (if any) when the connection is closed.

That's it! Now you know how to connect to a MySQL database from a Node.js application and execute queries on the database.

27. Connetion to MongoDB Database

To connect to a MongoDB database in a Node.js application, we can use the mongodb package.

Step 1: Install the 'mongodb' package using npm

npm install mongodb

Step 2: Import the mongodb package and create a MongoClient object


const { MongoClient } = require('mongodb');
const uri = 'mongodb://localhost:27017/mydb';
const client = new MongoClient(uri);

Note: Replace localhost:27017 with the address of your MongoDB server and mydb with the name of your database.

Step 3: Connect to the MongoDB server


await client.connect();
console.log('Connected to MongoDB');

Step 4: Access a database and collection


const db = client.db('mydb');
const collection = db.collection('mycollection');

Note: Replace 'mydb' with the name of your database and mycollection with the name of your collection.

Step 5: Perform operations on the collection

For example, to insert a document


const document = { name: 'John Doe', age: 30 };
const result = await collection.insertOne(document);
console.log(`Inserted document with id ${result.insertedId}`);

Step 6: Close the connection to the server when finished


await client.close();
console.log('Connection to MongoDB closed');

28. CRUD Operations using MySQL Database

First, we need to install mysql2 module in our Node.js application using npm

npm install mysql2

To establish a connection to a MySQL database, we need to create a connection object using createConnection method provided by mysql2 module.


const mysql = require('mysql2');

const connection = mysql.createConnection({
     host: 'localhost',
     user: 'root',
     password: 'password',
     database: 'mydb'
});

connection.connect((err) => {
     if (err) throw err;
     console.log('Connected to MySQL database!');
});
     

Create

To create a new record in a MySQL database, we can use query method provided by mysql2 module.


const newRecord = { name: 'John Doe', age: 30, email: 'john.doe@example.com' };

connection.query('INSERT INTO mytable SET ?', newRecord, (err, result) => {
     if (err) throw err;
     console.log('New record added:', result.insertId);
});
     

In the above example, we created a new record with the required data fields using an object literal newRecord. Then, we used query method to insert the new record into mytable. We passed the INSERT SQL statement and the new record object as parameters to the query method. When the query method completes successfully, it will return a result object containing the insertId property, which represents the unique identifier of the new record.

Read

To retrieve data from a MySQL database, we can use query method with a SELECT SQL statement.


connection.query('SELECT * FROM mytable', (err, results) => {
     if (err) throw err;
     console.log('Retrieved records:', results);
});

In the above example, we used query method to retrieve all records from mytable. When the query method completes successfully, it will return a result object containing the retrieved records in the results property.

Update

To update an existing record in a MySQL database, we can use query method with an UPDATE SQL statement.


const idToUpdate = 1;
const updatedRecord = { age: 35 };

connection.query('UPDATE mytable SET ? WHERE id = ?', [updatedRecord, idToUpdate], (err, result) => {
     if (err) throw err;
     console.log('Updated record:', result.affectedRows);
});

Delete

To delete data from a MySQL database using Node.js, we can use the DELETE statement.


     const sql = "DELETE FROM customers WHERE id = 1";
  
     connection.query(sql, (err, result) => {
       if (err) throw err;
       
       console.log(result.affectedRows + " record(s) deleted");
     });

The DELETE statement to delete a record from the customers table where the id is equal to 1. Finally, we use the query method of the connection to execute the statement and log the number of affected rows.

29. CRUD Operations using MongoDB Database

Creating a new document


const MongoClient = require('mongodb').MongoClient;

const url = 'mongodb://localhost:27017';

const client = new MongoClient(url, { useUnifiedTopology: true });

client.connect((err) => {
if (err) throw err;

const db = client.db('mydb');
const collection = db.collection('mycollection');

const newDocument = { name: 'John Doe', age: 30, email: 'john.doe@example.com' };

collection.insertOne(newDocument, (err, result) => {
if (err) throw err;

console.log('New document added:', result.insertedId);

client.close();
});
});
     

Reading documents


const MongoClient = require('mongodb').MongoClient;

const url = 'mongodb://localhost:27017';

const client = new MongoClient(url, { useUnifiedTopology: true });

client.connect((err) => {
if (err) throw err;

const db = client.db('mydb');
const collection = db.collection('mycollection');

// find all documents in the collection
collection.find({}).toArray((err, documents) => {
if (err) throw err;

console.log('All documents:', documents);

client.close();
});

// find documents that match a specific condition
collection.find({ name: 'John Doe' }).toArray((err, documents) => {
if (err) throw err;

console.log('John Doe documents:', documents);

client.close();
});
});

Updating documents


const MongoClient = require('mongodb').MongoClient;

const url = 'mongodb://localhost:27017';

const client = new MongoClient(url, { useUnifiedTopology: true });

client.connect((err) => {
if (err) throw err;

const db = client.db('mydb');
const collection = db.collection('mycollection');

// update a document that matches a specific condition
const condition = { name: 'John Doe' };
const update = { $set: { age: 40 } };

collection.updateOne(condition, update, (err, result) => {
if (err) throw err;

console.log('Document updated:', result.modifiedCount);

client.close();
});
});
     

Deleting documents


const MongoClient = require('mongodb').MongoClient;

const url = 'mongodb://localhost:27017';

const client = new MongoClient(url, { useUnifiedTopology: true });

client.connect((err) => {
if (err) throw err;

const db = client.db('mydb');
const collection = db.collection('mycollection');

// delete a document that matches a specific condition
const condition = { name: 'John Doe' };

collection.deleteOne(condition, (err, result) => {
if (err) throw err;

console.log('Document deleted:', result.deletedCount);

client.close();
});
});