const name = "Hello World";
Creates variable that cannot be reassigned.
let name = "Hello World";
Creates variable that can be reassigned.
var name = "Hello World";
Creates reassignable global scope variable
Primitives are known as being immutable data types because there is no way to change a primitive value once it gets created. They are stored in memory by value.
const number = 18;
JavaScript numbers are always stored in double-precision 64-bit binary format IEEE 754.
const string = "text";
JavaScript Strings are made up of a list of characters, essentially an array of characters.
const boolean = true;
Represents a logical entity and can have two values: true or false.
const null = null;
This type has only one value: null.
const undefined = undefined;
A variable that has not been assigned a value is undefined.
const symbol = Symbol("Something");
Symbols return unique identifiers that can be used as property keys in objects without colliding with other keys.
const bigInt = BigInt("0b1010101001010101001111111111111111");
BigInt is a built-in object providing a way to represent whole numbers larger than 253-1.
Non-Primitives are known as mutable data types because we can change the value after creation. They are stored in memory by reference.
const object = {number: 18, string: "text"};
JavaScript objects are fundamental data structures used to store collections of data. They consist of key-value pairs and can be created using curly braces {} or the new keyword. Understanding objects is crucial, as everything in JavaScript is essentially an object.
const array = [18, "text"];
Arrays are also objects.
const myFunction = function(param1, param2) { console.log("This is a function expression") };
Functions are also objects.
const pi = 3.141;
pi.toFixed(0);
Returns "3"
pi.toFixed(2);
Returns "3.14"
pi.toFixed(10);
Returns "3.141000000"
pi.valueOf();
Returns 3.141
Number("3.141");
Returns 3.141
Number(new Date());
Returns number of milliseconds since 1970.01.01 00:00:00
parseInt("2 apples");
Returns first number, 2
Number("2 apples"); would return NaN
parseFloat("2.1 apples");
Returns 2.1
Math.max(1, 3, 2)
Gets the biggest item, returns 3
Math.min(1, 3, 2)
Gets the smallest item, returns 1
Math.round(0.9);
Returns 1
Math.floor(5.95);
Rounds down, returns 5
Math.ceil(0.95);
Rounds up, returns 1
Math.abs(-1)
Converts negative number to positive, returns 1
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
console.log(getRandomInt(3));
Returns 0, 1 or 2
function getRandomIntInclusive(min, max) {
const minCeiled = Math.ceil(min);
const maxFloored = Math.floor(max);
// The maximum is inclusive and the minimum is inclusive
return Math.floor(Math.random() * (maxFloored - minCeiled + 1) + minCeiled);
}
Returns a random value between min and max
const number = 1234;
number.toString(2);
Converts integer to its binary representation. Returns "10011010010"
let string = "I'm a string";
string + " I'm string too";
Returns "I'm a string I'm string too"
`${string} I'm string too`;
Returns "I'm a string I'm string too"
`${string} \n I'm string too`;
Adds new line between the two strings
string.length;
Returns 12
string.indexOf(" ");
Finds the first positon of a substring. Returns 3
string.lastIndexOf(" ");
Finds the last positon of a substring. Returns 5
string.slice(6, 9);
Returns "str", negative values start from behind
string.slice(6);
Returns "string"
string.replace(" ", " | ");
Returns "I'm | a string"
// Method 1
string.replaceAll(" ", " | ");
// Method 2
const regex = new RegExp(" ", "g");
string.replace(regex, " | ");
Returns "I'm | a | string"
string.toUpperCase();
Returns "I'M A STRING"
string.toLowerCase();
Returns "i'm a string"
string[4];
Returns "a"
string.split(" ");
Returns [ "I'm", "a", "string" ]
string.split("");
Returns [ "I", "'", "m", " ", "a", " ", "s", "t", "r", "i", "n", "g" ]
string = string.split(" ");
string[1] = string[1] + " good";
string = string.join(" ");
Returns "I'm a good string"
string.startsWith("I'm");
Returns true
string.endsWith("something");
Returns false
string.includes(" a ");
Returns true
Array.from(string).reverse().join('')
Returns "gnirts a m'I"
const product = {
name: "apple",
type: "fruit",
color: "green",
stock: 25,
getTitle: function () {
return `${this.stock} ${this.color}
${this.name}`;
}
}
console.log(product.name);
Returns "apple"
const propertyToLookFor = "name";
console.log(product[propertyToLookFor]);
Returns "apple"
product.stock = 29;
product["stock"]++;
console.log(product.getTitle());
Returns "30 green apple"
product.size = "large";
Adds "size" property to the object.
product.hasOwnProperty("color");
Checks if the object has the given property. Returns false if not found or inherited.
if ("color" in product)
Same as hasOwnProperty()
product.getColorAndType = function() {
return this.color + " " + this.type;
};
product.getColorAndType();
Returns "green fruit"
function Product(name, type, color, stock) {
this.name = name;
this.type = type;
this.color = color;
this.stock = stock;
this.getTitle = function() {
return `${this.stock} ${this.color}
${this.name}`;
}
}
const carrot = new Product("carrot", "vegetable", "orange", 42);
Object constructors provide an easy way to create multiple objects.
Product.prototype.getColorAndType = function() {
return this.color + " " + this.type;
}
carrot.getColorAndType();
Adding methods to object constructor functions can be done by adding them to their prototypes.
for (let [key, value] of Object.entries(carrot)) {
console.log(key + ": " + value);
}
Object.entries() can be used to iterate through objects.
for (const property in carrot) {
console.log(`${property}: ${carrot[property]}`);
}
For...in loops can do the same.
for (const property of Object.keys(carrot)) {
console.log(`${property}: ${carrot[property]}`);
}
Object.keys returns the keys of an object as an array that can be used for loops.
// Method 1
const carrotCopy = Array.from(carrot);
// Method 2
const carrotCopy = carrot;
Shallow copies share the same references as the original object.
// Method 1
const carrotClone = JSON.parse(JSON.stringify(carrot));
// Method 2
const carrotClone = structuredClone(carrot);
Deep copies do not share the same references as the original object.
Object.freeze(product);
Makes the object non-writable and non-configurable.
Object.keys(product).length;
Returns the number of keys of the object.
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target); // Expected output: Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget === target); // Expected output: true
Merging objects can be done by using Object.assign()
// Method 1
const fruits = ["orange", "banana", "apple"];
// Method 2
const fruits = new Array("orange", "banana", "apple");
fruits[0];
Returns orange
fruits[0] = "grape";
Replaces "orange" with "grape".
const fruitsClone = fruits.with(1, "watermelon");
Creates deep copy with these elements [ "orange", "watermelon", "apple" ]
fruits.toString();
Returns "orange,banana,apple"
fruits.join("; ");
Returns "orange; banana; apple"
fruits.shift();
Removes first element.
fruits.unshift("grape");
Adds "grape" to the beginning.
fruits.pop();
Removes last element.
fruits.push("grape");
Adds "grape" to the end.
delete fruits[0];
Changes element to undefined. Not recommended to use.
// Method 1
fruits.splice(1, 2);
// Method 2
const fruitsClone = fruits.toSpliced(1, 2); // Creates deep copy
At position 1, removes 2 items, so fruits array becomes [ "orange" ]
for (let i = fruits.length - 1; i >= 0; i--) { fruits.splice(i, 1); }
Removing elements of the array one by one.
fruits.splice(1, 0, "watermelon", "pear");
Adds items at position 1, doesn't remove any, so fruits array becomes [ "orange", "watermelon", "pear", "banana", "apple" ]
fruits.concat([ "cherry", "lemon" ], [ "peach" ])
Adds array(s) to an existing array, so fruits array becomes [ "orange", "banana", "apple", "cherry", "lemon", "peach" ]
fruits.slice(1, 3);
Returns [ "banana", "apple" ]
fruits.slice(-2, -1);
Returns [ "banana" ]
negative numbers select from the end of the array.
const number = 123;
const intArrayFromNumber = number.toString().split('').map(Number);
Returns an integer array from the number variable, such as [ 1, 2, 3 ]
fruits.indexOf("banana");
Finds the first positon of an item. Returns 1
fruit.lastIndexOf("banana");
Finds the last positon of an item. Returns 1
// Method 1
fruits.sort();
// Method 2
const fruitsClone = fruits.toSorted(); // Creates deep copy
Sorts alphabetically, fruits array becomes [ "apple", "banana", "orange" ]
// Method 1
fruits.reverse();
// Method 2
const fruitsClone = fruits.toReversed(); // Creates deep copy
Changes the order of the items in the array, fruits array becomes [ "apple", "banana", "orange" ]
const numbers = [7, 23, 5, 46, 11];
numbers.sort(
function(a, b) {
return a - b;
}
)
Sorts array numerically, numbers array becomes: [ 5, 7, 11, 23, 46 ]
const numbers = [7, 23, 5, 46, 11];
numbers.sort(
function(a, b) {
return b - a;
}
)
Sorts array numerically in descending order, numbers array becomes: [ 46, 23, 11, 7, 5 ]
const numbers = [7, 23, 5, 46, 11];
numbers.sort(
function() {
return 0.5 - Math.random();
}
)
Sorts array randomly and modifies it.
// Method 1
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
// Method 2
fruits.forEach(function(item) { console.log(item) });
Loops through an array.
// Method 1
const fruitsCopy = fruits;
// Method 2
const fruitsCopy = [...fruits];
Only creates a shallow copy.
// Method 1
const fruitsClone = JSON.parse(JSON.stringify(carrot));
// Method 2
const fruitsClone = structuredClone(carrot);
Creates a deep copy.
fruits.some( (element) => element.startsWith("ba") );
Returns true if at least one of the elements of the array passes the test.
fruits.every( (element) => element.includes("a") );
Returns true if all the elements of the array passes the test.
const fruitsCopy = fruits.filter( (element) => element.includes("an") );
Creates shallow copy of elements of the array that pass the test.
fruits.find( (element) => element.includes("ban") );
Returns the first element of the array that pass the test. If non found then returns undefined.
fruits.includes("apple");
Returns true if the element in the array is found.
const fruitsClone = fruits.map( (element) => element + " fruit" );
Returns a deepy copy of an array with the modified elements.
[ "orange fruit", "banana fruit", "apple fruit" ]
const numbers = [1, 2, 3, 4];
numbers.reduce(
(accumulator, currentValue) => accumulator + currentValue
);
Returns 10
Math.random().toString(16).slice(2)
Creates a random string like: 767b9ca93b034
Although the possiblity to generate the same string twice is very low,
it's not impossible.
function add(a, b) {
return a + b;
}
Function declaration.
const add = function(a, b) {
return a + b;
}
Function expression.
// Method 1
const add = (a, b) => a + b;
// Method 2
const add = (a, b) => { return a + b; }
Arrow function.
// Method 1
(function () {
return 1 + 1;
})();
// Method 2
(() => {
return 1 + 1;
})();
Immediately invoked function expression (IIFE) runs as soon as it is defined.
Lexical Scope in programming, particularly in JavaScript, refers to the context in which variables and functions are accessible or visible. Lexical Scopes can be broadly classified into two categories: Global Scope and Local Scope.
const globalVar = "I am a global variable";
function accessGlobalVar() {
// Accessing the global variable within a function
console.log(globalVar);
}
When a variable is declared outside any function, it belongs to the Global Scope. This means it can be accessed and modified from any other part of the code, regardless of the context. Global variables are accessible throughout the lifespan of the application, making them readily available but also posing a risk of unintended modifications, which can lead to bugs.
Local Scope, on the other hand, refers to variables declared within a function or a block (in case of let and const in ES6). These variables are only accessible within that function or block, making them hidden from the rest of the program. There are two main types of Local Scopes: Function Scope and Block Scope.
function localFunctionScope() {
const functionScopedVar = "I am a local variable";
console.log(functionScopedVar); // Output: I am a local variable
}
localFunctionScope(); // Output: I am a local variable
console.log(functionScopedVar); // Uncaught ReferenceError: functionScopedVar is not defined
Function Scope variables declared within a function using var, let, or const are scoped to the function.
if (true) {
let blockScopedVar = "I am block-scoped";
const anotherBlockScopedVar = "So am I";
var globalVar = "I am global!!!";
console.log(blockScopedVar); // Accessible here
console.log(anotherBlockScopedVar); // Accessible here
}
console.log(blockScopedVar); // Uncaught ReferenceError: blockScopedVar is not defined
console.log(anotherBlockScopedVar); // Uncaught ReferenceError: anotherBlockScopedVar is not defined
console.log(globalVar); // Output: I am global!!!
Block Scope, introduced in ES6 with let and const, variables declared inside a block {} are only accessible within that block.
Lexical Environment is a structure that stores variable and function declarations in a specific scope, along with a reference to its parent environment. It is created during code execution for the global scope, functions, and blocks (with let and const). JavaScript uses Lexical Environments to resolve variables by checking the current environment and its parent recursively. This mechanism is also the foundation of closures, where a function retains access to variables from its outer environment even after that environment has exited.
Execution Context is an environment where code is executed. It consists of three main parts: a Variable Environment (stores variables and function declarations), a Lexical Environment (for scope and closures), and the this binding. There are three types: Global Execution Context, Function Execution Context, and Eval Execution Context. JavaScript manages these contexts using a stack, with the global context at the bottom and new contexts added/removed as functions are called or return.
The this keyword refers to the object that is currently executing the function. Its value depends on how the function is called:
In the global scope, this refers to the global object (window in browsers, global in Node.js).
In an object method, this refers to the object the method belongs to.
In a regular function, this is undefined in strict mode or the global object otherwise.
In an arrow function, this is inherited from the surrounding lexical scope.
It dynamically changes based on the calling context.
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives a function access to its outer scope. In JavaScript, closures are created every time a function is created, at function creation time.
const counter = (function() {
let counter = 0;
return function() {
return counter++;
}
})();
console.log(counter());
console.log(counter());
console.log(counter());
Returns
0
1
2
const delay = (function() {
let timer = 0;
return function(callback, ms) {
clearTimeout(timer);
timer = setTimeout(callback, ms);
};
})();
delay(function() {
console.log("Time off");
}, 1000);
Debouncing can be done with closures. The script above logs "Time off" if delay function is not called again within 1000 milliseconds.
if (condition1) {
// executes if condition1 is true
} else if (condition2) {
// executes if the condition1 is false and condition2 is
true
} else {
// executes if the condition1 is false and condition2 is
false
}
switch(expression) {
case x:
// executes if expression is x
break;
case y:
// executes if expression is y
break;
default:
// executes if expression is neither x nor x
}
const fruitsArray = ["orange", "banana", "apple"];
const fruitsObject = {orange: "orange", banana: "yellow", apple: "red"};
// Method 1
for (let i = 0; i < fruitsArray.length; i++) {
console.log(fruitsArray[i]);
}
// Method 2
for (const i of fruitsArray) {
console.log(fruitsArray[i]);
}
Loops through an array.
for (const i in fruitsObject) {
console.log(fruitsObject[i]);
}
Loops through an object.
let i = 0;
while (i < 10) {
console.log(i);
i++;
}
Keeps iterating as long as the condition is true.
let i = 0;
do {
console.log(i);
i++;
} while (i < 10);
Loops as long as the condition is true, but first runs the code block and then checks the condition.
for (let i = 0; i < fruitsArray.length; i++) {
if (condition) continue;
console.log(fruitsArray[i]);
}
Skips step if condition is true.
for (let i = 0; i < fruitsArray.length; i++) {
if (condition) break;
console.log(fruitsArray[i]);
}
Stops iteration if condition is true.
Prototypes are the mechanism by which objects inherit properties and methods from other objects. Every JavaScript object has an internal property called [[Prototype]], which links to another object (its prototype). This allows for prototypal inheritance.
const person = {
greet: function() {
console.log("Hello!");
}
};
const student = Object.create(person); // student inherits from person
student.name = "Alice";
console.log(student.name); // "Alice" (found on student)
student.greet(); // "Hello!" (found on person)
When you access a property or method on an object, JavaScript first looks for it on the object itself. If it doesn't find it, JavaScript looks at the object's prototype (i.e., its [[Prototype]]). This continues up the prototype chain until it reaches null (the end of the chain). In this example student does not have greet(), but JavaScript finds it on its prototype (person).
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hi, I'm ${this.name}`);
};
const john = new Person("John");
john.sayHello(); // "Hi, I'm John"
Functions in JavaScript have a special property called prototype. When a function is used as a constructor, objects created from it inherit properties from its prototype. In this example sayHello() is not directly on john, but it's found in Person.prototype.
console.log(john.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
In this example we can see how the prototype chain works:
john → Person.prototype → Object.prototype → null
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hi, I'm ${this.name}`);
}
}
const emma = new Person("Emma");
emma.sayHello(); // "Hi, I'm Emma"
The class syntax is just a cleaner way of working with prototypes. Under the hood, JavaScript still uses prototypes for inheritance.
Prototypal Inheritance vs. Classical Inheritance:
class Product {
constructor(name, type, color, stock) {
this.name = name;
this.type = type;
this.color = color;
this.stock = stock;
}
getTitle() {
return `${this.stock} ${this.color}
${this.name}`;
}
}
const carrot = new Product("carrot", "vegetable", "orange", 42);
Creates new class.
class Food extends Product {
constructor(name, type, color, stock, taste) {
super(name, type, color, stock);
this.taste = taste;
}
getTasteAndName() {
return this.taste + " " + this.name;
}
}
const lemon = new Food("lemon", "fruit", "yellow", 22, "sour");
Creates new class that inherits properties and methods from the parent class and adds to them.
class ClassWithPrivateFieldAndMethod {
#privateField; // must be declared outside of constructor
constructor(number) {
this.#privateField = number;
}
#privateMethod() {
return this.#privateField;
}
publicMethod() {
return this.#privateMethod();
}
}
const object = new ClassWithPrivateFieldAndMethod(42);
console.log(object.privateField); // doesn't work
console.log(object.#privateField); // doesn't work
console.log(object.privateMethod()); // doesn't work
console.log(object.#privateMethod()); // doesn't work
console.log(object.publicMethod()); // returns 42
Private properties and methods can be declared with #
class ClassWithStaticMethod {
static staticProperty = 'someValue';
static staticMethod() {
return 'static method has been called.';
}
static {
console.log('Class static initialization block
called');
}
}
console.log(ClassWithStaticMethod.staticMethod());
console.log(ClassWithStaticMethod.staticProperty);
You cannot call a static method on an object, only on an object class.
Returns
Class static initialization block called
static method has been called.
someValue
class ClassWithGetterAndSetter {
#myProperty;
constructor() {
this.#myProperty = null;
}
get myProperty() {
return this.#myProperty;
}
set myProperty(value) {
this.#myProperty = value;
}
}
const myClass = new ClassWithGetterAndSetter();
myClass.myProperty = "testing";
console.log(myClass.myProperty);
Getters and setters provide a nice way to manipulate private properties in an object.
call(), apply(), and bind() are methods that allow you to control the this value when invoking a function. During every function call the call() method runs, meaning that myFunction() is just a shorthand for myFunction.call().
const wizard = {
name: 'Merlin',
health: 50,
heal(num) {
return this.health += num;
}
};
const archer = {
name: 'Robin Hood',
health: 30
};
wizard.heal.call(archer, 50);
wizard.heal.apply(archer, [50]);
const healArcher = wizard.heal.bind(archer, 50);
healArcher();
call() and apply() are very similar methods that can call a function, call() using individual arguments while apply() using an array of arguments. They are useful to borrow methods from other objects while bind() can do the same, but instead of calling the method immediately it instead returns a function for later use.
function multiply(num1, num2) {
return num1 * num2;
}
const multiplyByTwo = multiply.bind(this, 2);
console.log(multiplyByTwo(4)); // Returns 8
const multiplyByTen = multiply.bind(this, 10);
console.log(multiplyByTen(4)); // Returns 40
bind() is also useful when using a technique called currying, which is an important part of functional programming.
Hoisting is a mechanism where variables and function declarations are moved to the top of their containing scope before code execution. This means you can use functions and variables before they are declared in the code.
console.log(a); // undefined (hoisted and initialized with undefined)
var a = 10;
console.log(a); // 10
// Behind the scenes, JavaScript interprets the code above like this:
var a; // Declaration is hoisted
console.log(a); // undefined
a = 10; // Initialization happens here
console.log(a);
Variables declared with var are hoisted, but their value is undefined until assigned.
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20;
Variables declared with let and const are also hoisted, but they are not initialized. Accessing them before declaration results in a ReferenceError. let and const exist in a Temporal Dead Zone (TDZ) from the start of the block until their declaration.
hello(); // Works fine
function hello() {
console.log("Hello, world!");
}
Function declarations are fully hoisted, meaning you can call them before they appear in the code.
greet(); // TypeError: greet is not a function
var greet = function () {
console.log("Hi there!");
};
Function expressions are not hoisted the same way as function declarations. In this example the variable greet is hoisted, but it is initialized as undefined, so calling it before assignment gives an error.
Hoisting:
const now = new Date();
const time = new Date(year, month, day, hours, minutes, seconds, milliseconds);
const customDate = new Date("2020-02-15 21:45:56");
customDate.getDate();
Returns the day of the month (from 1-31), 15
customDate.getDay();
Returns the day of the week (from 0-6), 6
customDate.getFullYear();
Returns the year, 2020
customDate.getHours();
Returns the hour (from 0-23), 21
customDate.getMonth();
Returns the month (from 0-11), 1
customDate.getTime();
Returns the number of milliseconds since midnight Jan 1 1970, and a specified date (timestamp), 1581803156000
now.now();
Returns the number of milliseconds since midnight Jan 1, 1970
now.UTC();
Returns the number of milliseconds in a date since midnight of January 1, 1970, according to UTC time
customDate.getUTCDate();
Returns the day of the month, according to universal time (from 1-31)
customDate.toISOString();
Returns the date as a string, using the ISO standard, 2020-02-15T21:45:56.000Z
customDate.toISOString().split("T")[0];
Returns the date in YYYY-MM-DD format, like: "2020-02-15". It works in UTC.
customDate.toISOString().slice(0, 19).replace("T", " ");
Returns the date in YYYY-MM-DD HH:MM:SS format, like "2020-02-15 21:45:56". It works in UTC.
const customDate = new Date("2020-02-15 21:45:56");
const pad = (num) => num.toString().padStart(2, "0");
const sqlDateLocal = `${customDate.getFullYear()}-${pad(customDate.getMonth() + 1)}-${pad(customDate.getDate())} ${pad(customDate.getHours())}:${pad(customDate.getMinutes())}:${pad(customDate.getSeconds())}`;
console.log(sqlDateLocal);
Returns the date in local time, in YYYY-MM-DD HH:MM:SS format, like: "2020-02-15 21:45:56".
customDate.toUTCString();
Returns the data as a string, according to universal time, Sat, 15 Feb 2020 21:45:56 GMT
customDate.setDate(customDate.getDate() + 7);
Adds a week to a date.
console.log('asd');
const now = Date.now();
while (Date.now() < now + 10000) {}
console.log('dsa');
Waits 10 seconds before logs "dsa" to the console. Prevents thread from doing anything else.
const start = new Date();
const now = Date.now();
while (Date.now() < now + 10000) {}
const end = new Date();
const diffInMilliseconds = end - start;
console.log(diffInMilliseconds);
Measures how much time it takes for a code to finish running.
const dateString = "2023-05-21 14:52:09";
const dateObject = new Date(dateString);
const formattedDate = dateObject.toLocaleDateString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
const formattedTime = dateObject.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: true
});
console.log(`${formattedDate} ${formattedTime}`);
Formats SQL format to USA format, returns 05/21/2023 02:52:09 PM
function displayResult(result) {
console.log(result);
}
function add(number1, number2, callback) {
const sum = number1 + number2;
callback(sum);
}
add(1, 2, displayResult);
function removeNegative(numbers, callback) {
const outputArray = [];
for (const x of numbers) {
if (callback(x)) {
outputArray.push(x);
}
}
return outputArray;
}
const numbersArray = [4, 1, -20, -7, 5, 9, -6];
const positiveNumbers = removeNegative(numbersArray, (x) => x >= 0);
// Method 1
const customPromise = new Promise(function(resolve, reject) {
resolve("operation successful");
reject("operation ran into an error");
});
// Method 2
function customPromise() {
return new Promise(function(resolve, reject) {
resolve("operation successful");
reject("operation ran into an error");
});
}
Defining promises.
// Method 1
customPromise().then(
function(value) {
console.log(value);
},
function(error) {
console.error(error);
}
);
// Method 2
customPromise().then((value) => {
console.log(value);
})
.catch((err) => {
console.error(err);
})
.finally(() => {
console.log("This block runs no matter the result");
});
Running promises.
// Method 1
async function promiseFunction() {
return "hi";
}
// Method 2
function promiseFunction() {
return Promise.resolve("hi");
}
Async functions always return a promise.
async function runPromise() {
const result = await customPromise();
console.log(result);
}
runPromise();
Await keyword waits for the promise to finish. Can only be used in async functions.
new Promise(async (resolve, reject) => {
Async functions in callbacks should be avoided where functions don't except them, like in the example above, as error handling won't work and makes the code prone to memory leaks. In general it's not advisable to mix callbacks and promises.
function toUpper(items) {
return items.map((i) => i.toUpperCase());
}
async function formatData() {
return ["item1", "item2", "item3"];
}
formatData().then(toUpper).then(console.log);
Do not use asyncronous functions for purely non asyncronous operations as they are going to block the event loop.
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("promise1 resolved");
}, 300);
});
const promise2 = new Promise((resolve, reject) => {
reject("promise2 rejected");
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("promise3 resolved");
}, 600);
});
async function processPromisesSequentially() {
for (const promise of [promise1, promise2, promise3]) {
try {
const result = await promise;
console.log(result);
} catch (error) {
console.error(error);
}
}
}
processPromisesSequentially();
Running promises sequencially.
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
}).catch((error) => {
console.error(error);
});
Running promises in parallel. Will reject if any of the promises reject.
Promise.allSettled([promise1, promise2, promise3]).then((values) => {
console.log(values);
}).catch((error) => {
console.error(error);
});
Same as .all(), but will wait for all the promises whether they resolve or reject.
Promise.any([promise1, promise2, promise3]).then((values) => {
console.log(values);
}).catch((error) => {
console.error(error);
});
Returns the first promise that is not rejected.
Promise.race([promise1, promise2, promise3]).then((values) => {
console.log(values);
}).catch((error) => {
console.error(error);
});
Returns the first promise that settles whether it's rejected or resolved.
Modules only work via http(s), pages opened as file cannot use them.
<script type="module">
import something from "./some-module.js";
</script>
Importing modules in the browser can be done using type="module" in the <script> tag.
// Method 1
export default const something = "some string";
// Method 2
const something = "some string";
export default something;
Default exports work in the browser and with Node.js with the same syntax.
// Method 1
export const name = "watermelon";
export const color = "green";
// Method 2
const name = "watermelon";
const color = "green";
export {name, color}
Named exports work in the browser and with Node.js with the same syntax.
// Method 1
<script type="module">
import {name, color} from "./fruit.js"; // In the browser
</script>
// Method 2
import {name, color} from "./fruit.js"; // In Node.js
Named exports can be imported as such.
const name = "watermelon";
const color = "green";
module.exports.name = name;
module.exports.color = color;
OR
module.exports.name = "watermelon";
module.exports.color = "green";
OR
exports.name = "watermelon";
exports.color = "green";
OR
const name = "watermelon";
const color = "green";
module.exports = {
name,
color
};
In CommonJS it's possible to export variables, functions and classes many different ways.
const { name, color } = require('./fruit.js');
The exported module can be imported using destructuring in CommonJS.
ES modules use the export / import format.
Buffer.from("Hello World").toString('base64')
Encodes string to base64 in Node.js.
Buffer.from("SGVsbG8gV29ybGQ=", 'base64').toString('utf8');
Decodes base64 string in Node.js.
const buffer1kb = Buffer.alloc(1024);
Creates a buffer with 1 KB of data.
import path from 'path';
const __dirname = path.resolve();
import {createReadStream, createWriteStream} from 'fs';
const readStream = createReadStream(__dirname + '/big-file.txt',
'utf-8');
const writeStream = createWriteStream(__dirname +
'/processed-big-file.txt');
readStream.on('data', function(chunk) {
writeStream.write(chunk);
});
Reading and writing data chunk by chunk istead of loading in completely in the memory first can be done with streams.
import path from 'path';
const __dirname = path.resolve();
import {createReadStream, createWriteStream} from 'fs';
const readStream = createReadStream(__dirname + '/big-file.txt',
'utf-8');
const writeStream = createWriteStream(__dirname +
'/processed-big-file.txt');
readStream.pipe(writeStream);
Instead of listening for data on the read stream and writing them manually, pipes can be useful.
The Set object lets you store unique values of any type, whether primitive values or object references.
const setObject = new Set([1, 2, 3, 3, 4]);
console.log(setObject);
Returns Set(4) [ 1, 2, 3, 4 ]
setObject.add(["string1", "string2", { key: "value" }])
setObject.delete(2);
console.log(setObject);
Returns Set(3) [ 1, 3, 4 ]
console.log(setObject.has(5));
Returns false
setObject.clear();
console.log(setObject);
Returns Set []
console.log(setObject.size);
Returns 4
const setObject = new Set([1, 2, 3, 3, 4]);
setObject.forEach((value1) => {
console.log(value1);
});
Returns
1
2
3
4
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([38, 4, 1, 125]);
console.log(setA.difference(setB));
Returns the values from setA that are not present in setB, so:
Set [ 2, 3 ]
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([38, 4, 1, 125]);
console.log(setA.intersection(setB));
Returns the values that both setA and setB have, so:
Set [ 1, 4 ]
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([38, 4, 1, 125]);
console.log(setA.symmetricDifference(setB));
Returns the values that setA and setB have but not both of them, so:
[ 2, 3, 38, 125 ]
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([38, 4, 1, 125]);
console.log(setA.union(setB));
Returns the values that setA and setB have, so:
Set(6) [ 1, 2, 3, 4, 38, 125 ]
The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.
const map1 = new Map([
['a', 1],
['b', 2],
['c', 3]
]);
OR
const map1 = new Map();
map1.set('a', 1);
map1.set('b', 2);
map1.set('c', 3);
console.log(map1.get('a'));
Returns 1
map1.set('a', 97);
Changes the value of a to 97.
map1.delete('b');
Deletes the specified element.
console.log(map1.size);
Returns 3
map1.clear();
Deletes all the elements of the map.
map1.has("c");
Returns true if element is present.
map1.forEach(function(value, key) {
console.log(`${key}: ${value}`);
});
Returns
a: 1
b: 2
c: 3
const fruits = [
{name:"apples", quantity:300},
{name:"bananas", quantity:500},
{name:"oranges", quantity:200},
{name:"kiwi", quantity:150}
];
const result = Map.groupBy(fruits, function({ quantity }) {
return quantity > 200 ? "ok" : "low";
});
console.log("low", JSON.stringify(result.get("low")));
console.log("ok", JSON.stringify(result.get("ok")));
Returns
low [{"name":"oranges","quantity":200},{"name":"kiwi","quantity":150}]
ok [{"name":"apples","quantity":300},{"name":"bananas","quantity":500}]
The Proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object.
const user = {
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
}
const handler = {
get(target, property) {
console.log(`Property ${property} has been
read.`);
return target[property];
}
};
const proxyUser = new Proxy(user, handler);
console.log(proxyUser.firstName);
Returns:
Property firstName has been read.
John
const handler = {
set(target, property, value) {
if (property === 'age') {
if (typeof value !== 'number') {
throw new Error('Age
must be a number.');
}
if (value < 18) {
throw new Error('The
user must be 18 or older.')
}
}
target[property] = value;
}
};
const proxyUser = new Proxy(user, handler);
proxyUser.age = 'foo';
Returns:
Uncaught Error: Age must be a number.
try {
notExistingFunction();
} catch (err) {
console.log(err.message);
}
Returns "notExistingFunction is not defined"
const testObject = {
property1: {
property2: {
property4: "this is a string"
}
}
}
try {
const property3 =
testObject?.property1?.property2?.property3;
if (!property3) throw new Error("property3 has no value.");
} catch (err) {
console.log(err.message);
}
Returns "property3 has no value."
console.log("Message on the console.", "Another message on the console.");
console.log(false, "Only logs this message if first argument is false.");
console.log({variable1, variable2});
Returns an object with the variable names and values, like: { variable1: "message", variable2: 2 }
console.table({variable1, variable2});
Puts the variables in a table.
console.count("Every time the same message received it increments a
counter that it returns.");
// Returns Every time the same message received it increments a counter
that it returns.: 1
console.count("Every time the same message received it increments a
counter that it returns.");
// Returns Every time the same message received it increments a counter
that it returns.: 2
import { inspect } from "util";
console.log(inspect(bigVariable, { depth: null }));
Inspect() method of utils package in Node.js allows you to inspect and log the full content of an object, including nested structures, arrays, and other complex data types.
try {
const response = await
fetch('https://example.org/products');
if (!response.ok) {
throw new Error(`Response status:
${response.status}`);
}
const json = await response.json();
console.log(json);
} catch (error) {
console.error(error.message);
}
Making GET request, converting response to JSON and handling errors.
const response = await fetch("https://example.org/post", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title: "example" })
});
Making POST request with data in the request body.
const response = await fetch("https://example.org/get");
const contentType = response.headers.get("content-type");
if (!contentType || !contentType.includes("application/json")) {
throw new TypeError("Oops, we haven't got JSON!");
}
Checking the type of content we received.
await response.text(); // Reading the response as text
await response.json(); // Reading the response as json
await response.formData(); // Reading the response as form data
await response.blob(); // Reading the response as blob for example to
handle images
const response = await
fetch("https://www.example.org/a-large-file.txt");
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
const stream = response.body.pipeThrough(new TextDecoderStream());
for await (const value of stream) {
console.log(value);
}
Streaming content, so we don't have to wait for the whole file to get downloaded, instead we can process chunks of it while it's downloading.
const formData = new FormData();
formData.append('name', 'user');
formData.append('password', 'secret');
fetch("https://www.example.org/api", {
method: "POST"
body: formData,
});
Sending form data can be done by using the FormData class.
const controller = new AbortController();
const response = await fetch("https://example.org/get", {
signal: controller.signal
});
setTimeout(function() {
controller.abort();
}, 3000)
Cancelling a fetch request can be done by utilizing AbortController();
An iterator is any object with a next() method. An iterable is an object that contains [Symbol.iterator] property which makes them be able to work with for..of loops.
const myObject = {
data: [1, 2, 3, 4, 5],
[Symbol.iterator]: function () {
let index = 0;
const array = this.data;
// Return an iterator object
return {
next: function () {
if (index < array.length) {
return {
value: array[index++],
done: false
};
} else {
return {
done: true
};
}
}
};
}
};
for (const value of myObject) {
console.log(value);
}
console.log(...myObject);
The first one returns:
1
2
3
4
5
the second one returns:
1 2 3 4 5
Generators are a special type of function that allows you to pause and resume execution. They are defined using the function* syntax and work hand-in-hand with the yield keyword to produce a series of values over time. They produce values one at a time, as requested, instead of computing them all at once, and return an iterator object that conforms to the iterator protocol, making them naturally iterable. The yield eeyword pauses the function and sends a value to the caller.
function* myGenerator() {
yield 1
yield 2;
yield 3;
}
const gen = myGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
const myIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
};
for (const value of myIterable) {
console.log(value);
}
Combined with an iterator returns
1
2
3
async function* asyncGenerator() {
for (let i = 0; i < 3; i++) {
await new Promise((resolve) => setTimeout(resolve, 1000));
yield i;
}
}
(async () => {
for await (const value of asyncGenerator()) {
console.log(value);
}
})();
Logs 0, 1, 2 with a 1 second delay.
The event loop is a runtime model, which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks. It continuously checks the call stack and the task queue for tasks to execute.
The Call Stack is a a LIFO data structure that keeps track of the execution context of the currently running functions. When the code starts running, the global execution context is created and enters the call stack first. Each time a function is called, a new frame is pushed onto the call stack, and when the function completes its execution, the frame is popped off the stack.
This process continues until the call stack is empty (all functions have finished executing). The global execution context, is the last one to be removed from the call stack.
The Callback Queue is a mechanism used by the JavaScript runtime to handle asynchronous tasks. It is a FIFO data structure that holds callback functions waiting to be executed. The Event Loop monitors the Call Stack and the Callback Queue, ensuring that callback functions are executed once the Call Stack is empty. This enables JavaScript to manage and execute asynchronous operations efficiently while maintaining its single-threaded nature.
Web APIs (like setTimeout, fetch, localStorage, Web Workers or DOM events) are not part of JavaScript itself; they are provided by the browser or the runtime (e.g., Node.js). They enable developers to interact with the underlying system, browser capabilities, or external resources. They extend the functionality of JavaScript beyond its core language features.
The cluster module in Node.js provides a way to create multiple isolated child processes that run simultaneously on their own single thread, therefore the application can handle bigger load. When process isolation is not needed, use the worker_threads module instead, which allows running multiple application threads within a single Node.js instance.
/* primary.js */
import cluster from 'cluster';
import os from 'os';
if (cluster.isPrimary) {
console.log(`Primary process ID: ${process.pid}`);
const numCPUs = os.availableParallelism();
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} exited`);
cluster.fork();
});
} else {
import('./worker.js');
}
/* worker.js */
import http from 'http';
http.createServer((req, res) => {
res.writeHead(200);
res.end(`Handled by worker ${process.pid}`);
}).listen(80);
console.log(`Worker process ID: ${process.pid}`);
primary.js checks if the current process is the primary process and if so forks one worker per CPU core. If a worker exists for some reason, e.g.: unhandled error or hot reloading because code changes have been deployed, it forks a new one. If the process is not primary then it runs the worker.js which creates a basic http server.
npx pm2 start worker.js
Clustering can be also achieved with a 3rd party package called pm2.
Web workers are useful to offload computationally expensive work to a separate thread in the browser, so it works in parallel and doesn't block the main thread. Worker scripts cannot modify the dom directly.
/* main.js */
const worker = new Worker('./worker.js');
worker.postMessage(1000000);
worker.onmessage = function(event) {
console.log(`The sum is: ${event.data}`);
}
/* worker.js */
self.onmessage = function(event) {
let sum = 0;
for (let i = 0; i < event.data; i++) {
sum++;
}
self.postMessage(sum);
}
Returns "The sum is: 1000000"
const workerScript = `
self.onmessage = function(event) {
let sum = 0;
for (let i = 0; i < event.data; i++) {
sum++;
}
self.postMessage(sum);
}`;
const blob = new Blob([workerScript], { type: "text/javascipt" });
const worker = new Worker(window.URL.createObjectURL(blob));
worker.postMessage(1000000);
worker.onmessage = function(event) {
console.log(`The sum is: ${event.data}`);
}
Workers can be defined in the same file using Blob.
In Node.js the worker_threads module enables the use of threads to execute code in parallel, so it won't block the main thread, similarly to web workers in the browser.
/* main.js */
import { Worker } from "worker_threads";
const worker = new Worker("./worker.js", {workerData: 1000000});
worker.on('message', (message) => {
console.log(`The sum is: ${message}`);
});
worker.on('error', (error) => {
console.error(error);
});
worker.on('exit', (code) => {
if (code !== 0) {
console.error(new Error(`Worker stopped with exit code ${code}`));
}
});
/* worker.js */
import {parentPort, workerData } from "worker_threads";
let sum = 0;
for (let i = 0; i < workerData; i++) {
sum++;
}
parentPort.postMessage(sum);
Returns "The sum is: 1000000"
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah',
},
};
const dogName = adventurer.dog?.name;
console.log(dogName);
// Expected output: undefined
The optional chaining (?.) operator accesses an object's property or calls a function. If the object accessed or function called using this operator is undefined or null, the expression short circuits and evaluates to undefined instead of throwing an error.
const foo = null ?? 'default string';
console.log(foo);
// Expected output: "default string"
const baz = 0 ?? 42;
console.log(baz);
// Expected output: 0
The nullish coalescing (??) operator is a logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand. The difference between ?? and || is that || won't take 0 or false as a value.
const a = { duration: 50 };
a.speed ??= 25;
console.log(a.speed);
// Expected output: 25
a.duration ??= 10;
console.log(a.duration);
// Expected output: 50
The nullish coalescing assignment (??=) operator, also known as the logical nullish assignment operator, only evaluates the right operand and assigns to the left if the left operand is nullish (null or undefined).
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers));
// Expected output: 6
console.log(sum.apply(null, numbers));
// Expected output: 6
The spread (...) syntax allows an iterable, such as an array or string, to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected. In an object literal, the spread syntax enumerates the properties of an object and adds the key-value pairs to the object being created.
console.log(1 == 1);
// Expected output: true
console.log('hello' == 'hello');
// Expected output: true
console.log('1' == 1);
// Expected output: true
console.log(0 == false);
// Expected output: true
The equality (==) operator checks whether its two operands are equal, returning a Boolean result. Unlike the strict equality operator, it attempts to convert and compare operands that are of different types.
console.log(1 === 1);
// Expected output: true
console.log('hello' === 'hello');
// Expected output: true
console.log('1' === 1);
// Expected output: false
console.log(0 === false);
// Expected output: false
The strict equality (===) operator checks whether its two operands are equal, returning a Boolean result. Unlike the equality operator, the strict equality operator always considers operands of different types to be different.
let a, b, rest;
[a, b] = [10, 20];
console.log(a);
// Expected output: 10
console.log(b);
// Expected output: 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(rest);
// Expected output: Array [30, 40, 50]
The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
let x = 1;
x = (x++, x);
console.log(x);
// Expected output: 2
x = (2, 3);
console.log(x);
// Expected output: 3
The comma (,) operator evaluates each of its operands (from left to right) and returns the value of the last operand. This is commonly used to provide multiple updaters to a for loop's afterthought.
function getFee(isMember) {
return isMember ? '$2.00' : '$10.00';
}
console.log(getFee(true));
// Expected output: "$2.00"
console.log(getFee(false));
// Expected output: "$10.00"
console.log(getFee(null));
// Expected output: "$10.00"
The conditional (ternary) operator is the only JavaScript operator that takes three operands: a condition followed by a question mark (?), then an expression to execute if the condition is truthy followed by a colon (:), and finally the expression to execute if the condition is falsy. This operator is frequently used as an alternative to an if...else statement.
console.log(3 ** 4);
// Expected output: 81
console.log(10 ** -2);
// Expected output: 0.01
console.log(2 ** (3 ** 2));
// Expected output: 512
console.log((2 ** 3) ** 2);
// Expected output: 64
The exponentiation (**) operator returns the result of raising the first operand to the power of the second operand. It is equivalent to Math.pow(), except it also accepts BigInts as operands.
let x = 3;
const y = x++;
console.log(`x:${x}, y:${y}`);
// Expected output: "x:4, y:3"
let a = 3;
const b = ++a;
console.log(`a:${a}, b:${b}`);
// Expected output: "a:4, b:4"
The increment (++) operator increments (adds one to) its operand and returns the value before or after the increment, depending on where the operator is placed.
let x = 3;
const y = x--;
console.log(`x:${x}, y:${y}`);
// Expected output: "x:2, y:3"
let a = 3;
const b = --a;
console.log(`a:${a}, b:${b}`);
// Expected output: "a:2, b:2"
The decrement (--) operator decrements (subtracts one from) its operand and returns the value before or after the decrement, depending on where the operator is placed.
const urlParams = new URLSearchParams(window.location.search);
urlParams.get('page');
Returns the value of the 'page' get parameter from the URL.
const urlParams = new URLSearchParams(window.location.search);
urlParams.set('page', 2);
const newUrl = window.location.pathname + '?' + urlParams.toString();
window.history.pushState({ path: newUrl }, '', newUrl);
Sets 'page' parameter to 2 in the url WITHOUT reloading the page.
const urlParams = new URLSearchParams(window.location.search);
const myParam = urlParams.get('page');
urlParams.set('page', 2);
window.location.search = urlParams;
Sets 'page' parameter to 2 in the url WHILE reloading the page.
const queryParams = new URLSearchParams(window.location.search);
const fetchUrl = new URL('https://example.com?existing_query=some_value');
queryParams.forEach((value, key) => {
fetchUrl.searchParams.append(key, value);
});
fetch(fetchUrl, {
method: 'GET',
});
Existing GET queries can be added to a url of a fetch request with the append() method.
const itemToObserve = document.getElementById('idOfDomElement');
itemToObserve.addEventListener('click', function() {
console.log('item is clicked');
});
When the dom element gets clicked on the event listener executes its callback.
itemToObserve.addEventListener('click',
function() {
onsole.log('This will only run once.')
},
{
once: true
}
);
When the "once: true" option is provided the event listener will only run once.
itemToObserve.addEventListener('click', function(event) {
const targetElement = event.target;
console.log(`The following item was clicked on: ${targetElement}`);
});
The target element can be captured this way.
itemToObserve.addEventListener('click', function(event) {
event.preventDefault();
});
The preventDefault() method on the event makes sure that the default event, like opening a link, will be prevented.
itemToObserve.addEventListener('click', function(event) {
event.stopImmediatePropagation();
});
The stopImmediatePropagation() method of the Event interface prevents other listeners of the same event from being called. If several listeners are attached to the same element for the same event type, they are called in the order in which they were added. If stopImmediatePropagation() is invoked during one such call, no remaining listeners will be called, either on that element or any other element.
itemToObserve.addEventListener('click', function(event) {
event.stopPropagation();
});
The stopPropagation() method prevents propagation of the same event from being called. Propagation means bubbling up to parent elements or capturing down to child elements.
function clickEventHandler(event) {
event.preventDefault();
console.log('Item clicked');
}
itemToObserve.addEventListener('click', 'clickEventHandler');
itemToObserve.removeEventListener('clickEventHandler');
The removeEventListener() method of the EventTarget interface removes an event listener previously registered with EventTarget.addEventListener() from the target.
itemToObserve.addEventListener('customEvent', function() {
console.log('Custom event is captured');
});
itemToObserve.dispatchEvent(new Event('customEvent'));
The dispatchEvent() method allows custom events.
itemToObserve.addEventListener('customEvent', function(event) {
console.log(event.detail.property);
});
itemToObserve.dispatchEvent(new CustomEvent("customEvent", { detail: { property: value } }));
If custom data is needed in the custom event that can be achieved with the CustomEvent class.
parentElementOfDynamicElements.addEventListener('click', function(event) {
const targetElement = event.target;
if (!targetElement.classList.contains('dynamic-element')) return;
console.log('dynamic item is clicked');
});
Event delegation can be applied when elements are added/removed dynamically.
The EventEmitter class is a core part of Node.js that provides a powerful mechanism for handling and managing events. It's based on the observer pattern, where you can define custom events and set up listeners to handle those events asynchronously, thus achieving event-driven architecture.
import { EventEmitter } from 'events';
const myEmitter = new EventEmitter();
myEmitter.on('eventName', (message) => {
console.log(`Message received: ${message}`);
});
myEmitter.emit('eventName', 'Hello, World!');
Events can be listened to with the 'on' method and dispatched with the 'emit' method.
myEmitter.once('connection', () => {
console.log('Connection established!');
});
myEmitter.emit('connection');
myEmitter.emit('connection'); // Won't run again
The 'once' method will make sure to only listen to an event only once.
function listener(message) {
console.log(`Other message received: ${message}`);
}
myEmitter.on('otherEventName', listener);
myEmitter.off('otherEventName', listener);
myEmitter.emit('otherEventName', 'Will not log this for removed listener');
Event listeners can be removed with the 'off' or the 'removeListener' method.
The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
Intersection information is needed for many reasons, such as:
const target = document.getElementById('target');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
} else {
entry.target.classList.remove('visible');
}
});
}, { threshold: 0.5 });
observer.observe(target);
This script will toggle the class 'visible' on the target element when 50% of it is visible.
Most modern browsers (Firefox, Chrome) come with built-in Developer Tools (DevTools).
Open them with:
Ctrl + Shift + I
The Console tab lets you view logs, errors, and warnings.
The Debugger tab (or Sources tab in Chrome) allows you to inspect and set breakpoints in your code.
You can pause code execution using breakpoints to inspect variable states at specific points.
Find your JavaScript file.
Click on the line number where you want to pause execution.
Reload the page to trigger the breakpoint.
On the debugger tab you can also add breakpoints to different events. For example Event Listener breakpoints (e.g., when a click event happens on the site).
function testDebug() {
let a = 5;
debugger; // Execution will pause here if DevTools are open
let b = 10;
console.log(a + b);
}
testDebug();
Another way to trigger the breakpoints is using the debugger; statement inside the code.
node --inspect main.js
The --inspect flag enables Node.js's built-in debugging capabilities by opening a debugging WebSocket interface on your local machine.
This allows you to connect tools like Chrome DevTools, Visual Studio Code, or any debugger that speaks the DevTools protocol.
In this example we start the script and open a debugger on localhost:9229 by default.
After starting, you'll see output like:
Debugger listening on ws://127.0.0.1:9229/...
For help, see: https://nodejs.org/en/docs/inspector
node --inspect-brk yourScript.js
The --inspect-brk option pauses execution on the first line of your script, letting you start debugging before any code runs.
To connect with Chrome DevTools:
One of the most popular testing frameworks in the JavaScript ecosystem is Jest. It needs to be installed to Node.js so it can be used.
/* math.js */
function add(a, b) {
return a + b;
}
module.exports = add;
/* math.test.js */
const add = require('./math');
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
In this example Jest tests the function exported from math.js.
Names of the files where test are defined usually end with moduleName.test.js or moduleName.spec.js
The test can be run by running `npx jest` or `npm test` command if Jest is configured in package.json.
/* calculator.js */
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) throw new Error("Cannot divide by zero");
return a / b;
}
module.exports = { add, subtract, multiply, divide };
/* calculator.test.js */
const { add, subtract, multiply, divide } = require('./calculator');
describe('Calculator Functions', () => {
test('adds 2 + 3 to equal 5', () => {
expect(add(2, 3)).toBe(5);
});
test('subtracts 5 - 2 to equal 3', () => {
expect(subtract(5, 2)).toBe(3);
});
test('multiplies 3 * 4 to equal 12', () => {
expect(multiply(3, 4)).toBe(12);
});
test('divides 10 / 2 to equal 5', () => {
expect(divide(10, 2)).toBe(5);
});
test('throws error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow("Cannot divide by zero");
});
});
In this example we defined a test suite using describe() and multiple test cases.
Unit tests should be kept independent, isolated and simple. If a unit to be tested requires external dependencies we can use fakes, spies or mocks.
class FakeDatabase {
constructor() {
this.data = [];
}
save(item) {
this.data.push(item);
}
getAll() {
return this.data;
}
}
const db = new FakeDatabase();
db.save({ id: 1, name: "Item 1" });
console.log(db.getAll()); // [{ id: 1, name: "Item 1" }]
A fake is a simpler, lightweight implementation of a dependency that behaves like the real thing but is not production-ready. Used when the real dependency is too complex, slow, or unavailable (e.g., a database or API call) and you need a lightweight version of it.
const myFunction = jest.fn(); // Creates a spy function
myFunction("hello");
expect(myFunction).toHaveBeenCalled(); // Passes
expect(myFunction).toHaveBeenCalledWith("hello"); // Passes
expect(myFunction).toHaveBeenCalledTimes(1); // Passes
A spy is a function that records how it was called, but it does not change the behavior of the function. It's mainly used to track function calls. It verifies if a function was called, how many times, and with what arguments. Used you need to track function calls without altering behavior.
const fetchData = jest.fn(() => "Mocked Data");
test("should return mocked data", () => {
expect(fetchData()).toBe("Mocked Data");
});
A mock replaces a function or module with a predefined response, allowing you to control test behavior. Used when you don't want to call the actual function (e.g., API requests, database operations), but you need to replace it and control its output.
const axios = require("axios");
jest.mock("axios"); // Mock the axios module
test("fetches data from API", async () => {
axios.get.mockResolvedValue({ data: { message: "Hello" } });
const response = await axios.get("https://api.example.com");
expect(response.data.message).toBe("Hello");
});
In this example we are mocking an API call.
Jest provides hooks like beforeEach, beforeAll, afterEach and afterAll to help set up and tear down test environments efficiently. These hooks allow you to run code before or after each test, making test suites more maintainable.
let counter = 0;
beforeEach(() => {
counter = 10; // Reset before each test
});
test("increments counter by 1", () => {
counter += 1;
expect(counter).toBe(11);
});
test("decrements counter by 1", () => {
counter -= 1;
expect(counter).toBe(9);
});
Runs before each test in a test suite. Useful for setting up a fresh state before every test to avoid shared state issues. Ensures test independence by resetting the state before each test. Prevents side effects from previous tests.
let database = [];
beforeAll(() => {
database = ["User1", "User2", "User3"]; // Runs once before all tests
});
test("checks if User1 exists", () => {
expect(database).toContain("User1");
});
test("checks if User3 exists", () => {
expect(database).toContain("User3");
});
Runs once before all tests in a test suite. Useful for expensive setup tasks like connecting to a database. Avoids repetitive setup for every test. Useful for initializing resources like databases, API connections, or mock services.
import { test } from 'node:test';
import assert from 'node:assert/strict';
test('Addition works correctly', async (t) => {
assert.strictEqual(2 + 2, 4);
});
test('Asynchronous test example', async (t) => {
await new Promise((resolve) => setTimeout(resolve, 100));
assert.ok(true);
});
Basic tests that can be run using
node --test
Node.js will run all files named *.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
test('Math operations', async (t) => {
await t.test('Addition', () => {
assert.strictEqual(1 + 1, 2);
});
await t.test('Multiplication', () => {
assert.strictEqual(2 * 3, 6);
});
});
If you want a structure similar to Jest (with describe, beforeEach, etc.), you can use test's built-in subtests.
import { test } from 'node:test';
import assert from 'node:assert/strict';
async function fetchData() {
return new Promise((resolve) => setTimeout(() => resolve('data'), 100));
}
test('fetchData returns correct data', async () => {
const result = await fetchData();
assert.strictEqual(result, 'data');
});
Since test supports promises, you can write async tests naturally.
import { describe, it, before } from 'node:test';
import assert from 'node:assert';
import Calculator from './calculator.js';
describe('Calculator', () => {
let calculator;
before(() => {
calculator = new Calculator();
});
describe('Addition', () => {
it('can add numbers', () => {
const result = calculator.add(5, 5);
assert.strictEqual(result, 10);
});
it('can add a positive and a negative number', () => {
const result = calculator.add(6, -4);
assert.strictEqual(result, 2);
});
it('can work with a zero value', () => {
const result = calculator.add(8, 0);
assert.strictEqual(result, 8);
});
});
});
You can use Jest/Mocha like syntax with the native node:test module.
const fs = require('fs');
// Asynchronous
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data);
});
// Synchronous
try {
const data = fs.readFileSync('example.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
Reading a file.
const fs = require('fs');
// Asynchronous
fs.writeFile('example.txt', 'Hello, Node.js!', (err) => {
if (err) {
console.error(err);
return;
}
console.log('File written successfully');
});
// Synchronous
try {
fs.writeFileSync('example.txt', 'Hello, Node.js!');
console.log('File written successfully');
} catch (err) {
console.error(err);
}
Creating a new file or overwriting an existing one.
const fs = require('fs');
fs.appendFile('example.txt', '\nAppended text.', (err) => {
if (err) throw err;
console.log('Text appended');
});
Appending to a file.
const fs = require('fs');
fs.unlink('example.txt', (err) => {
if (err) throw err;
console.log('File deleted');
});
Deleting a file.
const fs = require('fs');
fs.rename('example.txt', 'newname.txt', (err) => {
if (err) throw err;
console.log('File renamed');
});
Renaming a file.
const fs = require('fs');
fs.access('example.txt', fs.constants.F_OK, (err) => {
console.log(err ? 'File does not exist' : 'File exists');
});
Checking if a file exists.
const fs = require('fs');
fs.mkdir('newDir', (err) => {
if (err) throw err;
console.log('Directory created');
});
Creating a directory.
const fs = require('fs');
fs.rmdir('newDir', (err) => {
if (err) throw err;
console.log('Directory removed');
});
Removing a directory.
const fs = require('fs');
const readStream = fs.createReadStream('largeFile.txt', 'utf8');
readStream.on('data', (chunk) => {
console.log(chunk);
});
For reading large files efficiently, use streams.
import http from 'http';
const server = http.createServer((req, res) => {
// Log request info
console.log(`${req.method} ${req.url}`);
// Set headers
res.setHeader('Content-Type', 'text/plain');
// Routing
if (req.url === '/' && req.method === 'GET') {
res.statusCode = 200;
res.end('Hello World');
} else if (req.url === '/about' && req.method === 'GET') {
res.statusCode = 200;
res.end('About page');
} else {
res.statusCode = 404;
res.end('Not Found');
}
});
// Start the server
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
In this example we create a basic HTTP server with Node.js.
if (req.method === 'POST' && req.url === '/echo') {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ received: body }));
});
}
For POST and PUT requests, you usually want to read the request body.
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
if (req.url === '/index.html') {
const filePath = path.join(__dirname, 'public', 'index.html');
fs.readFile(filePath, (err, data) => {
if (err) {
res.statusCode = 500;
res.end('Server Error');
} else {
res.setHeader('Content-Type', 'text/html');
res.end(data);
}
});
}
To serve files (like HTML, CSS, JS), use fs.
Express.js is a minimal and flexible web application library for Node.js. It provides a simple API to build web servers and REST APIs, handling things like:
npm install express
Installs Express.
import express from 'express';
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.send('Hello, Express!');
});
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
Basic Express server example.
app.get('/hello', (req, res) => {
res.send('GET request to /hello');
});
app.post('/submit', (req, res) => {
res.send('POST request to /submit');
});
app.get('/user/:id', (req, res) => {
res.send(`User ID: ${req.params.id}`);
});
Routing in Express.
Uses of req and res:
app.use(express.json()); // Parses JSON bodies
app.use(express.urlencoded({ extended: true })); // Parses URL-encoded bodies
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // Pass control to the next middleware
});
Examples for middlewares. Middleware functions run between the request and the response. Common uses:
import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
const app = express();
const PORT = 3000;
// This is needed to work with __dirname in ESM
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Serve static files from the "public" folder
app.use(express.static(path.join(__dirname, 'public')));
// Optional: if you are building an SPA (e.g. a React app), you can serve it with a catch-all route like this
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
In this example we serve static files.
If you visit http://localhost:3000/, your browser will render index.html and load CSS, JS, and image files automatically.
You can serve files from different directories:
app.use(express.static('public'));
app.use('/assets', express.static('assets'));
This way files inside public/ are served without a prefix. While files inside assets/ are served under /assets.
app.use((err, req, res, next) => {
console.error(err.stack); // log the error
res.status(500).json({ error: 'Something went wrong!' });
});
Basic custom error handling. Place this after all routes and middleware — Express will call it when any previous middleware calls next(err) or throws.
// Method 1
app.get('/fail', (req, res, next) => {
const err = new Error('Something failed!');
err.status = 400;
next(err); // Passes the error to the error middleware
});
// Method 2
app.get('/crash', (req, res) => {
throw new Error('Unhandled exception!');
});
You can manually throw errors like this.
// Without handling
app.get('/async', async (req, res) => {
const data = await somePromise(); // if this throws, Express won't catch it
res.send(data);
});
// Use try/catch
app.get('/async', async (req, res, next) => {
try {
const data = await somePromise();
res.send(data);
} catch (err) {
next(err); // Passes the error to error handler
}
});
If you're using async functions, you need to catch errors manually, or use a wrapper.
app.use((err, req, res, next) => {
if (err.name === 'ValidationError') {
res.status(400).json({ error: err.message });
} else {
res.status(err.status || 500).json({
error: err.message || 'Internal Server Error',
});
}
});
You can check the error type or add custom logic.
app.use((req, res, next) => {
res.status(404).json({ error: 'Route not found' });
});
Handle unknown routes like this — before the error handler.
app.get('/api/users', (req, res) => {
res.json([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]);
});
A simple API example.
import { exec } from 'child_process';
exec('ls', (error, stdout, stderr) => {
if (error) {
console.error(`Error: ${error.message}`);
return;
}
if (stderr) {
console.error(`stderr: ${stderr}`);
return;
}
console.log(`stdout:\n${stdout}`);
});
It works for simple shell commands like ls, pwd, echo.
import { promisify } from 'util';
import { exec } from 'child_process';
const execPromise = promisify(exec);
async function lsExample() {
const { stdout, stderr } = await execPromise('ls');
console.log('stdout:', stdout);
console.error('stderr:', stderr);
}
lsExample();
Example using exec as a promise.
import { spawn } from 'child_process';
const child = spawn('ping', ['google.com']);
child.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
child.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
child.on('close', (code) => {
console.log(`Process exited with code ${code}`);
});
If you need streaming output (better for large outputs and long-running processes), use spawn.
import { execSync } from 'child_process';
try {
const output = execSync('pwd').toString();
console.log(output);
} catch (error) {
console.error(`Error: ${error.message}`);
}
If you need to wait for a command to finish, use execSync. It blocks execution until the command finishes.
console.log(process.env.NODE_ENV); // e.g., "development" or "production"
console.log(process.env.PORT); // e.g., "3000"
Reads an environment variable. Useful for configuration settings like API keys, database URLs, and ports. If the variable is not set, it returns undefined.
// Linux/macOS
NODE_ENV=production node app.js
// Windows (PowerShell)
$env:NODE_ENV="production"; node app.js
// Windows (CMD)
set NODE_ENV=production && node app.js
Sets an environment variable when running a Node.js script. Only applies for the current command session. Does not persist across reboots.
// Step 1: Install dotenv
npm install dotenv
// Step 2: Create a .env file
NODE_ENV=production
PORT=5000
API_KEY=123456
// Step 3: Load .env in your app.js
import dotenv from 'dotenv';
dotenv.config();
console.log(process.env.NODE_ENV); // Output: production
console.log(process.env.PORT); // Output: 5000
console.log(process.env.API_KEY); // Output: 123456
For a persistent and secure way to store environment variables, use a .env file with the dotenv package. This keeps secrets out of your code and it works across different environments. Make sure .env is in .gitignore to avoid leaking secrets!
const PORT = process.env.PORT || 3000;
console.log(`Server running on port ${PORT}`);
If an environment variable is missing, provide a default. Prevents crashes when an environment variable is missing.
// Step 1: Install cross-env
npm install cross-env
// Step 2: Update package.json scripts
"scripts": {
"start": "cross-env NODE_ENV=production node app.js"
}
On Windows, NODE_ENV=production doesn't work directly in scripts. Use cross-env to fix this. Thks works on all platforms (Windows, macOS, Linux).
console.log(process.env);
This logs all environment variables.
// .env file
API_KEY=test_key
API_URL=https://example.com/api/v1
// index.js file
console.log(process.env.API_KEY) // "test_key"
From v20.6.0, Node.js supports built-in environment variables. The application needs to be started using the "--env-file" flag, such as: node --env-file=.env index.js
npm -v
Checks installed version of npm.
// Method 1 - asks questions
npm init
// Method 2 - accepts defaults
npm init -y
Creates a package.json file (which stores project metadata and dependencies).
// Method 1
npm install express
// Method 2
npm i express
// Globally (for CLI tools and utilities)
npm install -g nodemon
Installs the package inside node_modules/ and adds it to package.json (local install). By default, dependencies are added under "dependencies" in package.json.
package.json: Lists dependencies and project info.
package-lock.json: Locks dependencies to specific versions for consistency.
// Method 1
npm install nodemon --save-dev
// Method 2
npm i nodemon -D
Used for development tools (e.g., testing frameworks, linters).
npm update axios
Updates a package.
npm outdated
Checks for outdated packages.
// Method 1
npm uninstall axios
// Method 2
npm rm axios
Removes a package.
// package.json:
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
}
Defines scripts in package.json. We can run them afterwards with npm run dev.
npm cache clean --force
If you face issues with npm, clear the cache.
npx create-react-app my-app
npx can execute packages without installing.
{
"name": "my-app",
"version": "1.0.0",
"description": "A simple Node.js project",
"main": "app.js",
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
},
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.0"
}
}
This is an example of a package.json file.
It stores important metadata about a project, including dependencies, scripts, and configurations.
Key sections:
{
"name": "my-app",
"version": "1.0.0",
"dependencies": {
"express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-xxxxx"
}
}
}
This is a simplified example of a package-lock.json file.
The package-lock.json file ensures that every developer on a team installs the exact same versions of dependencies.
It:
npm outdated
Lists outdates packages.
npm audit fix
Installs security updates for known vulnerabilities.
npm update
Updates non-breaking changes (^ & ~ respected).
For major version changes it's best to either manually edit package.json file, for example change
"lodash": "^4.17.21"
to
"lodash": "^5.0.0"
and then run
npm install
or run
npm install lodash@latest
or
npm install lodash@5.0.0
for installing a specific version.
It's best to use a different git branch for dependency updates.
// Set data
localStorage.setItem("username", "john_doe");
// Get data
const user = localStorage.getItem("username");
// Remove a key
localStorage.removeItem("username");
// Clear all keys
localStorage.clear();
Local storage:
// Set data
sessionStorage.setItem("tempData", "123");
// Get data
const temp = sessionStorage.getItem("tempData");
// Remove a key
sessionStorage.removeItem("tempData");
// Clear all keys
sessionStorage.clear();
Session storage:
// Set a cookie
document.cookie = "username=JohnDoe; expires=Fri, 31 Dec 2022 23:59:59 GMT; path=/";
// Read all cookies
console.log(document.cookie); // "username=JohnDoe; anotherKey=value"
// Delete a cookie (by setting it with a past expiration date)
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/";
This example shows the basic cookie usage.
document.cookie = "username=JohnDoe; path=/"; // sent to all paths
document.cookie = "username=JohnDoe; path=/account"; // only sent to URLs under /account
path attribute specifies the URL path that must exist in the requested resource before sending the cookie. Default is the current path of the document.
document.cookie = "username=JohnDoe; expires=Fri, 31 Dec 2022 23:59:59 GMT";
expires attribute sets an exact date and time when the cookie expires. If omitted, the cookie is a session cookie (deleted when the browser/tab closes).
document.cookie = "username=JohnDoe; max-age=3600"; // 1 hour
max-age attribute is an alternative to expires. Sets cookie lifetime in seconds from now. If both expires and max-age are set, max-age takes precedence.
document.cookie = "username=JohnDoe; domain=example.com"; // sent to sub.example.com too
domain attribute specifies the domain the cookie belongs to. Its fefault is current domain (e.g., example.com). It can be set to a parent domain to share across subdomains.
document.cookie = "username=JohnDoe; secure";
secure attribute sends the cookie only over HTTPS. It protects against man-in-the-middle attacks on unencrypted connections.
Set-Cookie: sessionToken=abc123; HttpOnly
httpOnly attribute makes the cookie inaccessible to JavaScript via document.cookie. It helps prevent XSS (Cross-site scripting) attacks. It can only be set from the server, not client-side JS.
Set-Cookie: sessionToken=abc123; SameSite=Strict
sameSite attribute controls cross-site request behavior.
It helps defend against CSRF attacks.
Its values can be:
HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: sessionId=abc123; Path=/; HttpOnly; Secure; SameSite=Strict
A server sets cookies by including a Set-Cookie header in its HTTP response to the client.
Set-Cookie: sessionToken=abc123;
Path=/;
Domain=example.com;
Expires=Fri, 31 Dec 2022 23:59:59 GMT;
Secure;
HttpOnly;
SameSite=Strict
This is how a production cookie might look set from a server.
// Frontend
fetch('https://api.example.com/data', {
method: 'GET',
credentials: 'include' // Required to send cookies
});
// Server must respond with
Access-Control-Allow-Origin: https://your-frontend.com
Access-Control-Allow-Credentials: true
// And your cookie must be set with
Set-Cookie: sessionId=abc123; SameSite=None; Secure
Cookies set by the server will automatically be sent by the browser in future requests to the same domain/path, but not in cross-site requests.
To fix this, you must:
How sessions work:
// Install session package for Express
npm install express-session
// index.js
import express from 'express';
import session from 'express-session';
const app = express();
app.use(session({
secret: 'my-secret-key', // used to sign the session ID cookie
resave: false, // don't save if unmodified
saveUninitialized: false, // don't save new sessions with no data
cookie: {
maxAge: 1000 * 60 * 60 // 1 hour
}
}));
app.get('/login', (req, res) => {
req.session.user = { id: 123, name: 'Alice' };
res.send('Logged in!');
});
app.get('/dashboard', (req, res) => {
if (req.session.user) {
res.send(`Hello ${req.session.user.name}`);
} else {
res.status(401).send('Not authenticated');
}
});
Installing and basic setup using Express.
Sessions can be stored:
Best practices using sessions:
How WebSockets work:
// Install package for handling WebSockets
npm install ws
// main.js
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => {
console.log('Client connected');
ws.on('message', (message) => {
console.log(`Received: ${message}`);
ws.send(`Echo: ${message}`);
});
ws.on('close', () => {
console.log('Client disconnected');
});
ws.send('Welcome!');
});
Basic WebSocket server.
const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('open', () => {
console.log('Connected to server');
socket.send('Hello server!');
});
socket.addEventListener('message', (event) => {
console.log('Message from server:', event.data);
});
WebSocket client in browser.
Best WebSocket practices:
When not to use WebSockets:
Polling is a technique where the client (like a browser) repeatedly sends requests to the server at regular intervals to check if new data is available.
setInterval(() => {
fetch('/api/messages')
.then(res => res.json())
.then(data => console.log(data));
}, 5000); // every 5 seconds
Regular polling:
function longPoll() {
fetch('/api/updates')
.then(res => res.json())
.then(data => {
console.log('Got update:', data);
longPoll(); // Start again immediately
})
.catch(() => setTimeout(longPoll, 5000)); // Retry on error
}
longPoll();
Long polling:
When to use polling:
Best polling practices:
SSE is a browser-native API that allows a server to push updates to the client over a single HTTP connection.
The communication is simplex, as only the server can send messages to the client.
How SSE works:
import express from 'express';
const app = express();
app.get('/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Send an initial event
res.write(`data: Hello from server\n\n`);
// Stream updates every 5 seconds
const interval = setInterval(() => {
res.write(`data: ${new Date().toISOString()}\n\n`);
}, 5000);
req.on('close', () => {
clearInterval(interval);
console.log('Client disconnected');
});
});
app.listen(3000, () => console.log('SSE server running on port 3000'));
Server side (Node.js with Express).
const source = new EventSource('/events');
source.onmessage = (event) => {
console.log('New event:', event.data);
};
source.onerror = (err) => {
console.error('EventSource failed:', err);
};
Client side (in the browser). Browsers handle reconnection automatically.
This document's home with other cheat sheets you might be interested in:
https://gitlab.com/davidvarga/it-cheat-sheets
Sources:
https://developer.mozilla.org/
https://www.wikipedia.org/
https://stackoverflow.com/
https://dev.to/
https://www.w3schools.com/
https://itnext.io
License:
GNU General Public License v3.0 or later