/**
* Hello World using Express
*/
const express = require('express');
const app = express();
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.listen(3000, function () {
console.log('App listening on port 3000!');
});
⚝ Try this example on CodeSandbox
The EventEmitter is a class that facilitates communication/interaction between objects in Node.js. The EventEmitter class can be used to create and handle custom events.
EventEmitter is at the core of Node asynchronous event-driven architecture. Many of Node's built-in modules inherit from EventEmitter including prominent frameworks like Express.js. An emitter object basically has two main features:
- Emitting name events.
- Registering and unregistering listener functions.
Example:
/**
* Callback Events with Parameters
*/
const events = require('events');
const eventEmitter = new events.EventEmitter();
function listener(code, msg) {
console.log(`status ${code} and ${msg}`);
}
eventEmitter.on('status', listener); // Register listener
eventEmitter.emit('status', 200, 'ok');
Output:
status 200 and ok
1. process.nextTick():
The process.nextTick() method adds the callback function to the start of the next event queue. It is to be noted that, at the start of the program process.nextTick() method is called for the first time before the event loop is processed.
2. setImmediate():
The setImmediate() method is used to execute a function right after the current event loop finishes. It is callback function is placed in the check phase of the next event queue.
Example:
/**
* setImmediate() and process.nextTick()
*/
console.log("Program Started");
setImmediate(() => {
console.log("setImmediate()");
});
process.nextTick(() => {
console.log("process.nextTick()");
});
setTimeout(()=> {
console.log('setTimeout()');
}, 0);
console.log("Program Ends");
Output:
Program Started
Program Ends
process.nextTick()
setTimeout()
setImmediate()
A callback is a function which is called when a task is completed, thus helps in preventing any kind of blocking and a callback function allows other code to run in the meantime.
Callback is called when task get completed and is asynchronous equivalent for a function. Using Callback concept, Node.js can process a large number of requests without waiting for any function to return the result which makes Node.js highly scalable.
Example:
/**
* Callback Function
*/
function myAsync(a, b, callback) {
setTimeout(function () {
callback(a + b);
}, 100);
}
console.log("Before Asynchronous Call");
myAsync(10, 20, function (result) {
console.log("Sum: " + result);
});
console.log("After Asynchronous Call");
Output:
Before Asynchronous Call
After Asynchronous Call
Sum: 30
The pattern used across all the asynchronous methods in Node.js is called Error-first Callback. Here is an example:
fs.readFile( "file.json", function ( err, data ) {
if ( err ) {
console.error( err );
}
console.log( data );
});
Any asynchronous method expects one of the arguments to be a callback. The full callback argument list depends on the caller method, but the first argument is always an error object or null. When we go for the asynchronous method, an exception thrown during function execution cannot be detected in a try/catch statement. The event happens after the JavaScript engine leaves the try block.
In the preceding example, if any exception is thrown during the reading of the file, it lands on the callback function as the first and mandatory parameter.
The "normal" way in Node.js is probably to read in the content of a file in a non-blocking, asynchronous way. That is, to tell Node to read in the file, and then to get a callback when the file-reading has been finished. That would allow us to hand several requests in parallel.
Common use for the File System module:
- Read files
- Create files
- Update files
- Delete files
- Rename files
Example: Read Files
<!-- index.html -->
<html>
<body>
<h1>File Header</h1>
<p>File Paragraph.</p>
</body>
</html>
/**
* read_file.js
*/
const http = require('http');
const fs = require('fs');
http.createServer(function (req, res) {
fs.readFile('index.html', function(err, data) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data);
res.end();
});
}).listen(3000);
Streams are objects that let you read data from a source or write data to a destination in continuous fashion. There are four types of streams
- Readable − Stream which is used for read operation.
- Writable − Stream which is used for write operation.
- Duplex − Stream which can be used for both read and write operation.
- Transform − A type of duplex stream where the output is computed based on input.
Each type of Stream is an EventEmitter instance and throws several events at different instance of times.
Example:
- data − This event is fired when there is data is available to read.
- end − This event is fired when there is no more data to read.
- error − This event is fired when there is any error receiving or writing data.
- finish − This event is fired when all the data has been flushed to underlying system.
1. Reading from a Stream:
const fs = require("fs");
const data = '';
// Create a readable stream
const readerStream = fs.createReadStream('input.txt');
// Set the encoding to be utf8.
readerStream.setEncoding('UTF8');
// Handle stream events --> data, end, and error
readerStream.on('data', function(chunk) {
data += chunk;
});
readerStream.on('end',function() {
console.log(data);
});
readerStream.on('error', function(err) {
console.log(err.stack);
});
console.log("Program Ended");
2. Writing to a Stream:
const fs = require("fs");
const data = 'Simply Easy Learning';
// Create a writable stream
const writerStream = fs.createWriteStream('output.txt');
// Write the data to stream with encoding to be utf8
writerStream.write(data,'UTF8');
// Mark the end of file
writerStream.end();
// Handle stream events --> finish, and error
writerStream.on('finish', function() {
console.log("Write completed.");
});
writerStream.on('error', function(err) {
console.log(err.stack);
});
console.log("Program Ended");
3. Piping the Streams:
Piping is a mechanism where we provide the output of one stream as the input to another stream. It is normally used to get data from one stream and to pass the output of that stream to another stream. There is no limit on piping operations.
const fs = require("fs");
// Create a readable stream
const readerStream = fs.createReadStream('input.txt');
// Create a writable stream
const writerStream = fs.createWriteStream('output.txt');
// Pipe the read and write operations
// read input.txt and write data to output.txt
readerStream.pipe(writerStream);
console.log("Program Ended");
4. Chaining the Streams:
Chaining is a mechanism to connect the output of one stream to another stream and create a chain of multiple stream operations. It is normally used with piping operations.
const fs = require("fs");
const zlib = require('zlib');
// Compress the file input.txt to input.txt.gz
fs.createReadStream('input.txt')
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('input.txt.gz'));
console.log("File Compressed.");
If a child process in Node.js spawn their own child processes, kill() method will not kill the child process's own child processes. For example, if I start a process that starts it's own child processes via child_process module, killing that child process will not make my program to quit.
const spawn = require('child_process').spawn;
const child = spawn('my-command');
child.kill();
The program above will not quit if my-command
spins up some more processes.
PID range hack:
We can start child processes with {detached: true} option so those processes will not be attached to main process but they will go to a new group of processes. Then using process.kill(-pid) method on main process we can kill all processes that are in the same group of a child process with the same pid group. In my case, I only have one processes in this group.
const spawn = require('child_process').spawn;
const child = spawn('my-command', {detached: true});
process.kill(-child.pid);
Please note - before pid. This converts a pid to a group of pids for process kill() method.
JSON Web Token (JWT) is an open standard that defines a compact and self-contained way of securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.
There are some advantages of using JWT for authorization:
- Purely stateless. No additional server or infra required to store session information.
- It can be easily shared among services.
Syntax:
jwt.sign(payload, secretOrPrivateKey, [options, callback])
- Header - Consists of two parts: the type of token (i.e., JWT) and the signing algorithm (i.e., HS512)
- Payload - Contains the claims that provide information about a user who has been authenticated along with other information such as token expiration time.
- Signature - Final part of a token that wraps in the encoded header and payload, along with the algorithm and a secret
Installation:
npm install jsonwebtoken bcryptjs --save
Example:
/**
* AuthController.js
*/
const express = require('express');
const router = express.Router();
const bodyParser = require('body-parser');
const User = require('../user/User');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const config = require('../config');
router.use(bodyParser.urlencoded({ extended: false }));
router.use(bodyParser.json());
router.post('/register', function(req, res) {
let hashedPassword = bcrypt.hashSync(req.body.password, 8);
User.create({
name : req.body.name,
email : req.body.email,
password : hashedPassword
},
function (err, user) {
if (err) return res.status(500).send("There was a problem registering the user.")
// create a token
let token = jwt.sign({ id: user._id }, config.secret, {
expiresIn: 86400 // expires in 24 hours
});
res.status(200).send({ auth: true, token: token });
});
});
config.js:
/**
* config.js
*/
module.exports = {
'secret': 'supersecret'
};
The jwt.sign()
method takes a payload and the secret key defined in config.js
as parameters. It creates a unique string of characters representing the payload. In our case, the payload is an object containing only the id of the user.
Reference:
Microservices are a style of Service Oriented Architecture (SOA) where the app is structured on an assembly of interconnected services. With microservices, the application architecture is built with lightweight protocols. The services are finely seeded in the architecture. Microservices disintegrate the app into smaller services and enable improved modularity.
There are few things worth emphasizing about the superiority of microservices, and distributed systems generally, over monolithic architecture:
- Modularity — responsibility for specific operations is assigned to separate pieces of the application
- Uniformity — microservices interfaces (API endpoints) consist of a base URI identifying a data object and standard HTTP methods (GET, POST, PUT, PATCH and DELETE) used to manipulate the object
- Robustness — component failures cause only the absence or reduction of a specific unit of functionality
- Maintainability — system components can be modified and deployed independently
- Scalability — instances of a service can be added or removed to respond to changes in demand.
- Availability — new features can be added to the system while maintaining 100% availability.
- Testability — new solutions can be tested directly in the production environment by implementing them for restricted segments of users to see how they behave in real life.
Example: Creating Microservices with Node.js
Step 01: Creating a Server to Accept Requests
This file is creating our server and assigns routes to process all requests.
// server.js
const express = require('express')
const app = express();
const port = process.env.PORT || 3000;
const routes = require('./api/routes');
routes(app);
app.listen(port, function() {
console.log('Server started on port: ' + port);
});
Step 02: Defining the routes
The next step is to define the routes for the microservices and then assign each to a target in the controller. We have two endpoints. One endpoint called "about" that returns information about the application. And a "distance" endpoint that includes two path parameters, both Zip Codes of the Lego store. This endpoint returns the distance, in miles, between these two Zip Codes.
const controller = require('./controller');
module.exports = function(app) {
app.route('/about')
.get(controller.about);
app.route('/distance/:zipcode1/:zipcode2')
.get(controller.getDistance);
};
Step 03: Adding Controller Logic
Within the controller file, we are going to create a controller object with two properties. Those properties are the functions to handle the requests we defined in the routes module.
const properties = require('../package.json')
const distance = require('../service/distance');
const controllers = {
about: function(req, res) {
let aboutInfo = {
name: properties.name,
version: properties.version
}
res.json(aboutInfo);
},
getDistance: function(req, res) {
distance.find(req, res, function(err, dist) {
if (err)
res.send(err);
res.json(dist);
});
},
};
module.exports = controllers;
The next is a function in the Express router which executes the middleware succeeding the current middleware.
Example:
To load the middleware function, call app.use()
, specifying the middleware function. For example, the following code loads the myLogger middleware function before the route to the root path (/).
/**
* myLogger
*/
const express = require("express");
const app = express();
const myLogger = function (req, res, next) {
console.log("LOGGED");
next();
};
app.use(myLogger);
app.get("/", (req, res) => {
res.send("Hello World!");
});
app.listen(3000);
⚝ Try this example on CodeSandbox
Note: The next()
function is not a part of the Node.js or Express API, but is the third argument that is passed to the middleware function. The next()
function could be named anything, but by convention it is always named “next”. To avoid confusion, always use this convention.
1. Helmet module:
Helmet helps to secure your Express applications by setting various HTTP headers, like:
- X-Frame-Options to mitigates clickjacking attacks,
- Strict-Transport-Security to keep your users on HTTPS,
- X-XSS-Protection to prevent reflected XSS attacks,
- X-DNS-Prefetch-Control to disable browsers DNS prefetching.
/**
* Helmet
*/
const express = require('express')
const helmet = require('helmet')
const app = express()
app.use(helmet())
2. JOI module:
Validating user input is one of the most important things to do when it comes to the security of your application. Failing to do it correctly can open up your application and users to a wide range of attacks, including command injection, SQL injection or stored cross-site scripting.
To validate user input, one of the best libraries you can pick is joi. Joi is an object schema description language and validator for JavaScript objects.
/**
* Joi
*/
const Joi = require('joi');
const schema = Joi.object().keys({
username: Joi.string().alphanum().min(3).max(30).required(),
password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
access_token: [Joi.string(), Joi.number()],
birthyear: Joi.number().integer().min(1900).max(2013),
email: Joi.string().email()
}).with('username', 'birthyear').without('password', 'access_token')
// Return result
const result = Joi.validate({
username: 'abc',
birthyear: 1994
}, schema)
// result.error === null -> valid
3. Regular Expressions:
Regular Expressions are a great way to manipulate texts and get the parts that you need from them. However, there is an attack vector called Regular Expression Denial of Service attack, which exposes the fact that most Regular Expression implementations may reach extreme situations for specially crafted input, that cause them to work extremely slowly.
The Regular Expressions that can do such a thing are commonly referred as Evil Regexes. These expressions contain: *grouping with repetition, *inside the repeated group: *repetition, or *alternation with overlapping
Examples of Evil Regular Expressions patterns:
(a+)+
([a-zA-Z]+)*
(a|aa)+
4. Security.txt:
Security.txt defines a standard to help organizations define the process for security researchers to securely disclose security vulnerabilities.
const express = require('express')
const securityTxt = require('express-security.txt')
const app = express()
app.get('/security.txt', securityTxt({
// your security address
contact: '[email protected]',
// your pgp key
encryption: 'encryption',
// if you have a hall of fame for securty resourcers, include the link here
acknowledgements: 'http://acknowledgements.example.com'
}))
File can be uploaded to the server using Multer module. Multer is a Node.js middleware which is used for handling multipart/form-data, which is mostly used library for uploading files.
1. Installing the dependencies:
npm install express body-parser multer --save
2. server.js:
/**
* File Upload in Node.js
*/
const express = require("express");
const bodyParser = require("body-parser");
const multer = require("multer");
const app = express();
// for text/number data transfer between clientg and server
app.use(bodyParser());
const storage = multer.diskStorage({
destination: function (req, file, callback) {
callback(null, "./uploads");
},
filename: function (req, file, callback) {
callback(null, file.fieldname + "-" + Date.now());
},
});
const upload = multer({ storage: storage }).single("userPhoto");
app.get("/", function (req, res) {
res.sendFile(__dirname + "/index.html");
});
// POST: upload for single file upload
app.post("/api/photo", function (req, res) {
upload(req, res, function (err) {
if (err) {
return res.end("Error uploading file.");
}
res.end("File is uploaded");
});
});
app.listen(3000, function () {
console.log("Listening on port 3000");
});
3. index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Multer-File-Upload</title>
</head>
<body>
<h1>MULTER File Upload | Single File Upload</h1>
<form id = "uploadForm"
enctype = "multipart/form-data"
action = "/api/photo"
method = "post"
>
<input type="file" name="userPhoto" />
<input type="submit" value="Upload Image" name="submit">
</form>
</body>
</html>
Q. Explain the terms body-parser, cookie-parser, morgan, nodemon, pm2, serve-favicon, cors, dotenv, fs-extra, moment in Express.js?
1. body-parser:
body-parser
extract the entire body portion of an incoming request stream and exposes it on req.body
. The body-parser module parses the JSON, buffer, string and URL encoded data submitted using HTTP POST request.
Example:
npm install body-parser
/**
* body-parser
*/
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
// create application/json parser
const jsonParser = bodyParser.json();
// create application/x-www-form-urlencoded parser
const urlencodedParser = bodyParser.urlencoded({ extended: false });
// POST /login gets urlencoded bodies
app.post("/login", urlencodedParser, function (req, res) {
res.send("welcome, " + req.body.username);
});
// POST /api/users gets JSON bodies
app.post("/api/users", jsonParser, function (req, res) {
// create user in req.body
});
2. cookie-parser:
A cookie is a piece of data that is sent to the client-side with a request and is stored on the client-side itself by the Web Browser the user is currently using.
The cookie-parser
middleware's cookieParser function takes a secret
string or array of strings as the first argument and an options
object as the second argument.
Installation:
npm install cookie-parser
Example:
/**
* cookie-parser
*/
const express = require('express')
const cookieParser = require('cookie-parser')
const app = express()
app.use(cookieParser())
app.get('/', function (req, res) {
// Cookies that have not been signed
console.log('Cookies: ', req.cookies)
// Cookies that have been signed
console.log('Signed Cookies: ', req.signedCookies)
})
app.listen(3000)
3. morgan:
HTTP request logger middleware for node.js.
Installation:
npm install morgan
Example:
/**
* Writing logs to a file
*/
const express = require('express')
const fs = require('fs')
const morgan = require('morgan')
const path = require('path')
const app = express()
// create a write stream (in append mode)
const accessLogStream = fs.createWriteStream(path.join(__dirname, 'access.log'), { flags: 'a' })
// setup the logger
app.use(morgan('combined', { stream: accessLogStream }))
app.get('/', function (req, res) {
res.send('hello, world!')
})
4. nodemon:
Nodemon is a utility that will monitor for any changes in source and automatically restart your server.
Installation:
npm install -g nodemon
Example:
{
// ...
"scripts": {
"start": "nodemon server.js"
},
// ...
}
5. pm2:
P(rocess) M(anager) 2 (pm2) is a production process manager for Node.js applications with a built-in load balancer. It allows to keep applications alive forever, to reload them without downtime and to facilitate common system admin tasks.
Installation:
npm install pm2 -g
Start an application:
pm2 start app.js
Reference:
6. serve-favicon:
Node.js middleware for serving a favicon. It create new middleware to serve a favicon from the given path to a favicon file. path may also be a Buffer of the icon to serve.
Installation:
npm install serve-favicon
Example:
/**
* serve-favicon
*/
const express = require('express')
const favicon = require('serve-favicon')
const path = require('path')
const app = express()
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')))
// Add your routes here, etc.
app.listen(3000)
7. cors:
Cross-Origin Resource Sharing (CORS) headers allow apps running in the browser to make requests to servers on different domains (also known as origins). CORS headers are set on the server side - the HTTP server is responsible for indicating that a given HTTP request can be cross-origin.
Installation:
npm install cors
Example:
/**
* Enable CORS for a Single Route
*/
const express = require('express')
const cors = require('cors')
const app = express()
app.get('/products/:id', cors(), function (req, res, next) {
res.json({msg: 'This is CORS-enabled for a Single Route'})
})
app.listen(8080, function () {
console.log('CORS-enabled web server listening on port 80')
})
8. dotenv:
When a NodeJs application runs, it injects a global variable called process.env
which contains information about the state of environment in which the application is running. The dotenv
loads environment variables stored in the .env
file into process.env
.
Installation:
npm install dotenv
Usage:
// .env
DB_HOST=localhost
DB_USER=admin
DB_PASS=root
/**
* config.js
*/
const db = require('db')
db.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS
})
9. fs-extra:
fs-extra
contains methods that aren't included in the vanilla Node.js fs package. Such as recursive mkdir
, copy
, and remove
. It also uses graceful-fs to prevent EMFILE
errors.
Installation:
npm install fs-extra
Usage:
/**
* fs-extra
*/
const fs = require('fs-extra')
// Async with callbacks:
fs.copy('/tmp/myfile', '/tmp/mynewfile', err => {
if (err) return console.error(err)
console.log('success!')
})
10. moment:
A JavaScript date library for parsing, validating, manipulating, and formatting dates.
Installation:
npm install moment --save
Usage:
- Format Dates
const moment = require('moment');
moment().format('MMMM Do YYYY, h:mm:ss a'); // October 24th 2022, 3:15:22 pm
moment().format('dddd'); // Saturday
moment().format("MMM Do YY"); // Oct 24th 22
- Relative Time
const moment = require('moment');
moment("20111031", "YYYYMMDD").fromNow(); // 9 years ago
moment("20120620", "YYYYMMDD").fromNow(); // 8 years ago
moment().startOf('day').fromNow(); // 15 hours ago
- Calendar Time
const moment = require('moment');
moment().subtract(10, 'days').calendar(); // 10/14/2022
moment().subtract(6, 'days').calendar(); // Last Sunday at 3:18 PM
moment().subtract(3, 'days').calendar(); // Last Wednesday at 3:18 PM
REST stands for REpresentational State Transfer. REST is web standards based architecture and uses HTTP Protocol. It is an architectural style as well as an approach for communications purposes that is often used in various web services development. A REST Server simply provides access to resources and REST client accesses and modifies the resources using HTTP protocol.
HTTP methods:
GET
− Provides read-only access to a resource.PUT
− Updates an existing resource or creates a new resource.DELETE
− Removes a resource.POST
− Creates a new resource.PATCH
− Update/modify a resource
Example:
{
"user1" : {
"id": 1,
"name" : "Ehsan Philip",
"age" : 24
},
"user2" : {
"id": 2,
"name" : "Karim Jimenez",
"age" : 22
},
"user3" : {
"id": 3,
"name" : "Giacomo Weir",
"age" : 18
}
}
List Users ( GET
method)
Let's implement our first RESTful API listUsers using the following code in a server.js file −
const express = require('express');
const app = express();
const fs = require("fs");
app.get('/listUsers', function (req, res) {
fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {
console.log( data );
res.end( data );
});
})
const server = app.listen(3000, function () {
const host = server.address().address
const port = server.address().port
console.log("App listening at http://%s:%s", host, port)
});
Add User ( POST
method )
Following API will show you how to add new user in the list.
const express = require('express');
const app = express();
const fs = require("fs");
const user = {
"user4" : {
"id": 4,
"name" : "Spencer Amos",
"age" : 28
}
}
app.post('/addUser', function (req, res) {
// First read existing users.
fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {
data = JSON.parse( data );
data["user4"] = user["user4"];
console.log( data );
res.end( JSON.stringify(data));
});
})
const server = app.listen(3000, function () {
const host = server.address().address
const port = server.address().port
console.log("App listening at http://%s:%s", host, port)
})
Delete User:
const express = require('express');
const app = express();
const fs = require("fs");
const id = 2;
app.delete('/deleteUser', function (req, res) {
// First read existing users.
fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {
data = JSON.parse( data );
delete data["user" + 2];
console.log( data );
res.end( JSON.stringify(data));
});
})
const server = app.listen(3000, function () {
const host = server.address().address
const port = server.address().port
console.log("App listening at http://%s:%s", host, port)
})
The req.params are a part of a path in URL and they're also known as URL variables. for example, if you have the route /books/:id, then the id property will be available as req.params.id. req.params default value is an empty object {}.
A req.query is a part of a URL that assigns values to specified parameters. A query string commonly includes fields added to a base URL by a Web browser or other client application, for example as part of an HTML form. A query is the last part of URL
Example 01: req.params
/**
* req.params
*/
// GET http://localhost:3000/employees/10
app.get('/employees/:id', (req, res, next) => {
console.log(req.params.id); // 10
})
Example 02: req.query
/**
* req.query
*/
// GET http://localhost:3000/employees?page=20
app.get('/employees', (req, res, next) => {
console.log(req.query.page) // 20
})
Following code snippet can be used to make a Post Request in Node.js.
/**
* POST Request
*/
const request = require("request");
request.post("http://localhost:3000/action", { form: { key: "value" } },
function (error, response, body) {
if (!error && response.statusCode === 200) {
console.log(body);
}
}
);
It allows to associate handlers to an asynchronous action's eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of the final value, the asynchronous method returns a promise for the value at some point in the future.
Promises in node.js promised to do some work and then had separate callbacks that would be executed for success and failure as well as handling timeouts. Another way to think of promises in node.js was that they were emitters that could emit only two events: success and error.The cool thing about promises is you can combine them into dependency chains (do Promise C only when Promise A and Promise B complete).
The core idea behind promises is that a promise represents the result of an asynchronous operation. A promise is in one of three different states:
- pending - The initial state of a promise.
- fulfilled - The state of a promise representing a successful operation.
- rejected - The state of a promise representing a failed operation. Once a promise is fulfilled or rejected, it is immutable (i.e. it can never change again).
Example:
/**
* Promise
*/
function getSum(num1, num2) {
const myPromise = new Promise((resolve, reject) => {
if (!isNaN(num1) && !isNaN(num2)) {
resolve(num1 + num2);
} else {
reject(new Error("Not a valid number"));
}
});
return myPromise;
}
console.log(getSum(10, 20)); // Promise { 30 }
1. When the web server sets cookies, it can provide some additional attributes to make sure the cookies won't be accessible by using malicious JavaScript. One such attribute is HttpOnly.
Set-Cookie: [name]=[value]; HttpOnly
HttpOnly makes sure the cookies will be submitted only to the domain they originated from.
2. The "Secure" attribute can make sure the cookies are sent over secured channel only.
Set-Cookie: [name]=[value]; Secure
3. The web server can use X-XSS-Protection response header to make sure pages do not load when they detect reflected cross-site scripting (XSS) attacks.
X-XSS-Protection: 1; mode=block
4. The web server can use HTTP Content-Security-Policy response header to control what resources a user agent is allowed to load for a certain page. It can help to prevent various types of attacks like Cross Site Scripting (XSS) and data injection attacks.
Content-Security-Policy: default-src 'self' *.http://sometrustedwebsite.com
/**
* POST Request using Axios
*/
const express = require("express");
const app = express();
const axios = require("axios");
app.post("/user", async (req, res) => {
try {
const payload = { name: "Aashita Iyer", email: "[email protected]" };
const response = await axios.post("http://httpbin.org/post", payload);
console.log(response.data);
res.status(200).json(response.data);
} catch (err) {
res.status(500).json({ message: err });
}
});
app.listen(3000, function () {
console.log(`App listening at http://localhost:3000/`);
});
Output:
{
args: {},
data: '{"name":"Aashita Iyer","email":"[email protected]"}',
files: {},
form: {},
headers: {
Accept: 'application/json, text/plain, */*',
'Accept-Encoding': 'gzip, deflate, br',
'Content-Length': '56',
'Content-Type': 'application/json',
Host: 'httpbin.org',
'User-Agent': 'axios/1.1.3',
'X-Amzn-Trace-Id': 'Root=1-635cd3d3-1f13ea981467e6371ce3a740'
},
json: { email: '[email protected]', name: 'Aashita Iyer' },
origin: 'xx.xx.xx.xx',
url: 'http://httpbin.org/post'
}
PUT and PATCH are HTTP verbs and they both relate to updating a resource. The main difference between PUT and PATCH requests are in the way the server processes the enclosed entity to modify the resource identified by the Request-URI.
In a PUT request, the enclosed entity is considered to be a modified version of the resource stored on the origin server, and the client is requesting that the stored version be replaced.
With PATCH, however, the enclosed entity contains a set of instructions describing how a resource currently residing on the origin server should be modified to produce a new version.
Also, another difference is that when you want to update a resource with PUT request, you have to send the full payload as the request whereas with PATCH, you only send the parameters which you want to update.
The most commonly used HTTP verbs POST, GET, PUT, DELETE are similar to CRUD (Create, Read, Update and Delete) operations in database. We specify these HTTP verbs in the capital case. So, the below is the comparison between them.
POST
- createGET
- readPUT
- updateDELETE
- delete
PATCH: Submits a partial modification to a resource. If you only need to update one field for the resource, you may want to use the PATCH method.
The HTTP core module is a key module to Node.js networking.
const http = require('http')
http.METHODS
require('http').METHODS
[ 'ACL',
'BIND',
'CHECKOUT',
'CONNECT',
'COPY',
'DELETE',
'GET',
'HEAD',
'LINK',
'LOCK',
'M-SEARCH',
'MERGE',
'MKACTIVITY',
'MKCALENDAR',
'MKCOL',
'MOVE',
'NOTIFY',
'OPTIONS',
'PATCH',
'POST',
'PROPFIND',
'PROPPATCH',
'PURGE',
'PUT',
'REBIND',
'REPORT',
'SEARCH',
'SUBSCRIBE',
'TRACE',
'UNBIND',
'UNLINK',
'UNLOCK',
'UNSUBSCRIBE' ]
http.STATUS_CODES:
require('http').STATUS_CODES
{ '100': 'Continue',
'101': 'Switching Protocols',
'102': 'Processing',
'200': 'OK',
'201': 'Created',
'202': 'Accepted',
'203': 'Non-Authoritative Information',
'204': 'No Content',
'205': 'Reset Content',
'206': 'Partial Content',
'207': 'Multi-Status',
'208': 'Already Reported',
'226': 'IM Used',
'300': 'Multiple Choices',
'301': 'Moved Permanently',
'302': 'Found',
'303': 'See Other',
'304': 'Not Modified',
'305': 'Use Proxy',
'307': 'Temporary Redirect',
'308': 'Permanent Redirect',
'400': 'Bad Request',
'401': 'Unauthorized',
'402': 'Payment Required',
'403': 'Forbidden',
'404': 'Not Found',
'405': 'Method Not Allowed',
'406': 'Not Acceptable',
'407': 'Proxy Authentication Required',
'408': 'Request Timeout',
'409': 'Conflict',
'410': 'Gone',
'411': 'Length Required',
'412': 'Precondition Failed',
'413': 'Payload Too Large',
'414': 'URI Too Long',
'415': 'Unsupported Media Type',
'416': 'Range Not Satisfiable',
'417': 'Expectation Failed',
'418': 'I\'m a teapot',
'421': 'Misdirected Request',
'422': 'Unprocessable Entity',
'423': 'Locked',
'424': 'Failed Dependency',
'425': 'Unordered Collection',
'426': 'Upgrade Required',
'428': 'Precondition Required',
'429': 'Too Many Requests',
'431': 'Request Header Fields Too Large',
'451': 'Unavailable For Legal Reasons',
'500': 'Internal Server Error',
'501': 'Not Implemented',
'502': 'Bad Gateway',
'503': 'Service Unavailable',
'504': 'Gateway Timeout',
'505': 'HTTP Version Not Supported',
'506': 'Variant Also Negotiates',
'507': 'Insufficient Storage',
'508': 'Loop Detected',
'509': 'Bandwidth Limit Exceeded',
'510': 'Not Extended',
'511': 'Network Authentication Required' }
Making HTTP Requests:
const request = require('request');
request('https://nodejs.org/', function(err, res, body) {
console.log(body);
});
The first argument to request can either be a URL string, or an object of options. Here are some of the more common options you'll encounter in your applications:
- url: The destination URL of the HTTP request
- method: The HTTP method to be used (GET, POST, DELETE, etc)
- headers: An object of HTTP headers (key-value) to be set in the request
- form: An object containing key-value form data
const request = require('request');
const options = {
url: 'https://nodejs.org/file.json',
method: 'GET',
headers: {
'Accept': 'application/json',
'Accept-Charset': 'utf-8',
'User-Agent': 'my-reddit-client'
}
};
request(options, function(err, res, body) {
let json = JSON.parse(body);
console.log(json);
});
Using the options object, this request uses the GET method to retrieve JSON data directly from Reddit, which is returned as a string in the body field. From here, you can use JSON.parse
and use the data as a normal JavaScript object.
1. Promises:
A promise is used to handle the asynchronous result of an operation. JavaScript is designed to not wait for an asynchronous block of code to completely execute before other synchronous parts of the code can run. With Promises, we can defer the execution of a code block until an async request is completed. This way, other operations can keep running without interruption.
States of Promises:
Pending
: Initial State, before the Promise succeeds or fails.Resolved
: Completed PromiseRejected
: Failed Promise, throw an error
Example:
function logFetch(url) {
return fetch(url)
.then(response => {
console.log(response);
})
.catch(err => {
console.error('fetch failed', err);
});
}
2. Async-Await:
Await
is basically syntactic sugar for Promises. It makes asynchronous code look more like synchronous/procedural code, which is easier for humans to understand.
Putting the keyword async
before a function tells the function to return a Promise. If the code returns something that is not a Promise
, then JavaScript automatically wraps it into a resolved promise with that value. The await
keyword simply makes JavaScript wait until that Promise
settles and then returns its result.
Example:
function doubleAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x * 2);
}, 2000);
});
}
async function addAsync(x) {
const a = await doubleAfter2Seconds(10);
const b = await doubleAfter2Seconds(20);
const c = await doubleAfter2Seconds(30);
return x + a + b + c;
}
addAsync(10).then((sum) => {
console.log(sum);
});
/**
* Get Request using Axios
*/
const express = require("express");
const app = express();
const axios = require("axios");
app.get("/async", async (req, res) => {
try {
const response = await axios.get("https://jsonplaceholder.typicode.com/todos/1");
res.status(200).json(response.data);
} catch (err) {
res.status(500).json({ message: err });
}
});
app.listen(3000, function () {
console.log(`App listening at http://localhost:3000/`);
});
Routing defines the way in which the client requests are handled by the application endpoints. We define routing using methods of the Express app object that correspond to HTTP methods; for example, app.get()
to handle GET
requests and app.post
to handle POST
requests, app.all()
to handle all HTTP methods and app.use()
to specify middleware as the callback function.
These routing methods "listens" for requests that match the specified route(s) and method(s), and when it detects a match, it calls the specified callback function.
Syntax:
app.METHOD(PATH, HANDLER)
Where:
- app is an instance of express.
- METHOD is an
HTTP request method
. - PATH is a path on the server.
- HANDLER is the function executed when the route is matched.
1. Route methods:
// GET method route
app.get('/', function (req, res) {
res.send('GET request')
})
// POST method route
app.post('/login', function (req, res) {
res.send('POST request')
})
// ALL method route
app.all('/secret', function (req, res, next) {
console.log('Accessing the secret section ...')
next() // pass control to the next handler
})
2. Route paths:
Route paths, in combination with a request method, define the endpoints at which requests can be made. Route paths can be strings, string patterns, or regular expressions.
The characters ?
, +
, *
, and ()
are subsets of their regular expression counterparts. The hyphen (-)
and the dot (.)
are interpreted literally by string-based paths.
Example:
// This route path will match requests to /about.
app.get('/about', function (req, res) {
res.send('about')
})
// This route path will match acd and abcd.
app.get('/ab?cd', function (req, res) {
res.send('ab?cd')
})
// This route path will match butterfly and dragonfly
app.get(/.*fly$/, function (req, res) {
res.send('/.*fly$/')
})
3. Route parameters:
Route parameters are named URL segments that are used to capture the values specified at their position in the URL. The captured values are populated in the req.params
object, with the name of the route parameter specified in the path as their respective keys.
Example:
app.get('/users/:userId', function (req, res) {
res.send(req.params)
})
Response methods:
Method | Description |
---|---|
res.download() |
Prompt a file to be downloaded. |
res.end() |
End the response process. |
res.json() |
Send a JSON response. |
res.jsonp() |
Send a JSON response with JSONP support. |
res.redirect() |
Redirect a request. |
res.render() |
Render a view template. |
res.send() |
Send a response of various types. |
res.sendFile() |
Send a file as an octet stream. |
res.sendStatus() |
Set the response status code and send its string representation as the response body. |
4. Router method:
const express = require('express')
const router = express.Router()
// middleware that is specific to this router
router.use(function timeLog (req, res, next) {
console.log('Time: ', Date.now())
next()
})
// define the home page route
router.get('/', function (req, res) {
res.send('Birds home page')
})
// define the about route
router.get('/about', function (req, res) {
res.send('About birds')
})
module.exports = router
Joi module is a popular module for data validation. This module validates the data based on schemas. There are various functions like optional(), required(), min(), max(), etc which make it easy to use and a user-friendly module for validating the data.
Example:
const Joi = require("joi");
// User-defined function to validate the user
function validateUser(user) {
const JoiSchema = Joi.object({
username: Joi.string().min(5).max(30).required(),
email: Joi.string().email().min(5).max(50).optional(),
date_of_birth: Joi.date().optional(),
account_status: Joi.string()
.valid("activated")
.valid("unactivated")
.optional(),
}).options({ abortEarly: false });
return JoiSchema.validate(user);
}
const user = {
username: "Deepak Lucky",
email: "[email protected]",
date_of_birth: "2000-07-07",
account_status: "activated",
};
let response = validateUser(user);
if (response.error) {
console.log(response.error.details);
} else {
console.log("Validated Data");
}
⚝ Try this example on CodeSandbox
The Node.js Crypto module supports cryptography. It provides cryptographic functionality that includes a set of wrappers for open SSL's hash HMAC, cipher, decipher, sign and verify functions.
-
Hash: A hash is a fixed-length string of bits i.e. procedurally and deterministically generated from some arbitrary block of source data.
-
HMAC: HMAC stands for Hash-based Message Authentication Code. It is a process for applying a hash algorithm to both data and a secret key that results in a single final hash.
-
Encryption Example using Hash and HMAC
const crypto = require('crypto');
const secret = 'abcdefg';
const hash = crypto.createHmac('sha256', secret)
.update('Welcome to Node.js')
.digest('hex');
console.log(hash);
- Encryption example using Cipher
const crypto = require('crypto');
const cipher = crypto.createCipher('aes192', 'a password');
const encrypted = cipher.update('Hello Node.js', 'utf8', 'hex');
encrypted += cipher.final('hex');
console.log(encrypted);
- Decryption example using Decipher
const crypto = require('crypto');
const decipher = crypto.createDecipher('aes192', 'a password');
const encrypted = '4ce3b761d58398aed30d5af898a0656a3174d9c7d7502e781e83cf6b9fb836d5';
const decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
console.log(decrypted);
const { exec } = require('child_process');
exec('"/path/to/test file/test.sh" arg1 arg2');
exec('echo "The \\$HOME variable is $HOME"');
The checksum (aka hash sum) calculation is a one-way process of mapping an extensive data set of variable length (e.g., message, file), to a smaller data set of a fixed length (hash). The length depends on a hashing algorithm.
For the checksum generation, we can use node crypto()
module. The module uses createHash(algorithm)
to create a checksum (hash) generator. The algorithm is dependent on the available algorithms supported by the version of OpenSSL on the platform.
Example:
const crypto = require('crypto');
// To get a list of all available hash algorithms
crypto.getHashes() // [ 'md5', 'sha1', 'sha3-256', ... ]
// Create hash of SHA1 type
const key = "MY_SECRET_KEY";
// 'digest' is the output of hash function containing
// only hexadecimal digits
hashPwd = crypto.createHash('sha1').update(key).digest('hex');
console.log(hashPwd); //ef5225a03e4f9cc953ab3c4dd41f5c4db7dc2e5b
ToDo