Modern JS from basics to advanced โ 10 lessons covering variables, DOM, async, ES6+, OOP, Canvas, Node.js, and testing.
JavaScript has three ways to declare variables. let allows reassignment, const does not, and var is the legacy way โ avoid it in modern code.
// const โ cannot be reassigned (preferred for most cases)
const name = "Mayank";
const PI = 3.14159;
// let โ block-scoped, can be reassigned
let count = 0;
count = 1; // โ
allowed
// var โ function-scoped, hoisted (avoid)
var legacy = "old style";
const by default. Only use let when you know the value will change. Never use var in modern JS.JavaScript has 8 data types โ 7 primitives and Object.
// Primitives
const str = "Hello"; // string
const num = 42; // number
const big = 9007199254740991n; // bigint
const flag = true; // boolean
const empty = null; // null
const nothing = undefined; // undefined
const sym = Symbol("id"); // symbol
// Reference type
const obj = { name: "Mayank" }; // object
const arr = [1, 2, 3]; // array (also object)
// typeof operator
console.log(typeof str); // "string"
console.log(typeof num); // "number"
console.log(typeof obj); // "object"
console.log(typeof null); // "object" (historic bug!)
console.log(typeof sym); // "symbol"
// Arithmetic
5 + 3 // 8
5 - 3 // 2
5 * 3 // 15
5 / 3 // 1.666...
5 % 3 // 2 (remainder)
5 ** 3 // 125 (exponentiation)
// Comparison
5 == "5" // true (loose โ type coercion!)
5 === "5" // false (strict โ always prefer this)
null == undefined // true
null === undefined // false
// Logical
true && false // false (AND)
true || false // true (OR)
!true // false (NOT)
?? // nullish coalescing (newer)
?. // optional chaining (newer)
// Nullish coalescing
const input = null;
const value = input ?? "default"; // "default"
// vs || which treats 0 and "" as falsy
const val2 = 0 || "fallback"; // "fallback"
const val3 = 0 ?? "fallback"; // 0
const name = "Mayank";
const age = 15;
// String concatenation (old way)
const old = "My name is " + name + " and I am " + age + " years old.";
// Template literal (modern way)
const modern = `My name is ${name} and I am ${age} years old.`;
// Multi-line strings
const html = `
<div class="card">
<h2>${name}</h2>
<p>Age: ${age}</p>
</div>
`;
// Expressions inside ${}
const price = 9.99;
const msg = `Total: $${(price * 1.18).toFixed(2)}`;
const by default, let when reassignment is needed=== instead of == for comparisons`${}`) replace string concatenationtypeof null returns "object" โ it's a known JS bug?? (nullish coalescing) is better than || for defaults// Modern selectors (preferred)
const el = document.querySelector('.my-class');
const all = document.querySelectorAll('li.item');
// Classic selectors (still valid)
const byId = document.getElementById('app');
const byTag = document.getElementsByTagName('div');
const byClass = document.getElementsByClassName('card');
// querySelector is the most flexible โ CSS selectors work
const first = document.querySelector('#app > ul > li:first-child');
const links = document.querySelectorAll('a[href^="https"]');
const btn = document.querySelector('#submitBtn');
// addEventListener (preferred)
btn.addEventListener('click', (e) => {
e.preventDefault();
console.log('Clicked!', e.target);
});
// Common events
'click' 'dblclick' 'mouseenter' 'mouseleave'
'keydown' 'keyup' 'input' 'change'
'submit' 'focus' 'blur' 'scroll'
'load' 'resize' 'DOMContentLoaded'
// Event delegation (handle events on parent)
document.querySelector('ul').addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
console.log('List item clicked:', e.target.textContent);
}
});
e.target.const card = document.querySelector('.card');
// Text & HTML
card.textContent = 'New text';
card.innerHTML = '<strong>Bold</strong> text';
// Attributes
card.setAttribute('id', 'main-card');
card.getAttribute('href');
card.removeAttribute('disabled');
// Dataset (data-* attributes)
// <div data-user-id="42" data-role="admin">
card.dataset.userId; // "42"
card.dataset.role; // "admin"
card.dataset.newKey = 'value'; // sets data-new-key
// Classes
card.classList.add('active');
card.classList.remove('hidden');
card.classList.toggle('selected');
card.classList.contains('active'); // true/false
// Styles
card.style.backgroundColor = '#1a1a2e';
card.style.color = '#fff';
card.style.cssText = 'padding: 1rem; margin: 0;';
// Create & append elements
const div = document.createElement('div');
div.className = 'alert';
div.textContent = 'Hello!';
document.body.appendChild(div);
// Remove element
card.remove();
querySelector / querySelectorAll are the most flexible selectorsdataset to read/write data-* attributesclassList.toggle() is great for show/hide patterns.remove() deletes an element; .replaceChildren() replaces all childrenJavaScript is single-threaded. The event loop lets it handle async operations without blocking. The call stack runs synchronous code first, then processes the microtask queue (Promises), then the macrotask queue (setTimeout, etc.).
console.log('1'); // sync
setTimeout(() => console.log('2'), 0); // macrotask
Promise.resolve().then(() => console.log('3')); // microtask
console.log('4'); // sync
// Output: 1, 4, 3, 2
// Sync code runs first, then microtasks, then macrotasks
// Callback pattern (old way)
function fetchData(url, callback) {
setTimeout(() => {
callback(null, { data: 'result' });
}, 1000);
}
fetchData('/api/users', (err, data) => {
if (err) console.error(err);
console.log(data);
});
// Callback Hell (Christmas Tree Problem)
fetchUser(id, (user) => {
fetchPosts(user.id, (posts) => {
fetchComments(posts[0].id, (comments) => {
// ๐ฉ Deeply nested, hard to read/debug
});
});
});
// Creating a Promise
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) resolve('Done!');
else reject(new Error('Failed'));
});
// Chaining โ no more callback hell
fetchUser(id)
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => console.log(comments))
.catch(err => console.error(err)) // catches any error in chain
.finally(() => console.log('cleanup'));
// Promise.all โ run in parallel, wait for all
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);
// Promise.allSettled โ wait for all, regardless of success
const results = await Promise.allSettled([
fetch('/api/fast'),
fetch('/api/slow'),
fetch('/api/fail')
]);
results.forEach(r => {
if (r.status === 'fulfilled') console.log(r.value);
else console.log(r.reason);
});
// async/await โ syntactic sugar over Promises
async function loadUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const user = await res.json();
return user;
} catch (err) {
console.error('Failed to load user:', err);
throw err;
}
}
// Usage
const user = await loadUser(42);
// Parallel async operations
async function loadDashboard() {
const [user, posts, notifications] = await Promise.all([
loadUser(1),
fetchPosts(),
fetchNotifications()
]);
return { user, posts, notifications };
}
// Async iteration
async function processItems(items) {
for (const item of items) {
await processItem(item); // sequential
}
}
// For parallel
async function processAll(items) {
await Promise.all(items.map(item => processItem(item)));
}
try/catch with async/await. Unhandled promise rejections can crash Node.js apps and show warnings in browsers.// Cancel fetch requests
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(res => res.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') console.log('Request cancelled');
});
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);
async/await makes async code read like synchronous codePromise.all() for parallel operations, for...of with await for sequentialAbortController to cancel fetch requests// GET request
const res = await fetch('https://api.example.com/users');
const data = await res.json();
console.log(data);
// POST with JSON body
const res2 = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({
name: 'Mayank',
age: 15
})
});
const created = await res2.json();
console.log(created);
// Pattern 1: Check res.ok
async function fetchData(url) {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
return res.json();
}
// Pattern 2: Wrapper function
async function safeFetch(url, options = {}) {
try {
const res = await fetch(url, options);
if (!res.ok) {
const error = await res.json().catch(() => ({}));
throw new Error(error.message || `HTTP ${res.status}`);
}
return { data: await res.json(), error: null };
} catch (err) {
return { data: null, error: err.message };
}
}
// Usage
const { data, error } = await safeFetch('/api/users');
if (error) console.error(error);
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Custom-Header': 'value'
},
body: JSON.stringify({ key: 'value' }),
mode: 'cors', // 'cors' | 'no-cors' | 'same-origin'
credentials: 'include', // 'include' sends cookies cross-origin
cache: 'no-cache', // 'default' | 'no-cache' | 'reload' | 'force-cache'
redirect: 'follow', // 'follow' | 'error' | 'manual'
signal: controller.signal // AbortController
});
// FormData for file uploads
const form = document.querySelector('#uploadForm');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
// or manually:
// const formData = new FormData();
// formData.append('file', fileInput.files[0]);
// formData.append('name', 'My File');
const res = await fetch('/api/upload', {
method: 'POST',
body: formData // browser sets Content-Type automatically
});
});
// Reusable fetch wrapper with auth
async function apiFetch(url, options = {}) {
const token = localStorage.getItem('token');
const headers = {
'Content-Type': 'application/json',
...options.headers
};
if (token) headers['Authorization'] = `Bearer ${token}`;
const res = await fetch(url, { ...options, headers });
if (res.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
throw new Error('Unauthorized');
}
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
fetch() only rejects on network failure, not HTTP errors (404, 500). Always check res.ok or res.status.fetch() returns a Promise โ use await or .then()res.ok โ fetch doesn't reject on 404/500Content-Type: application/json and use JSON.stringify() for POSTFormData for file uploads โ don't set Content-Type manually// Regular function
function add(a, b) {
return a + b;
}
// Arrow function (concise body โ implicit return)
const add = (a, b) => a + b;
// Single parameter โ no parens needed
const double = x => x * 2;
// No parameters
const getTimestamp = () => Date.now();
// Multi-line body โ needs return
const process = (data) => {
const cleaned = data.trim().toLowerCase();
return { original: data, cleaned, length: cleaned.length };
};
// Arrow functions do NOT have their own 'this'
// They inherit 'this' from the enclosing scope
class Timer {
constructor() {
this.seconds = 0;
}
start() {
// โ ๏ธ Regular function: 'this' would be the timer, but setTimeout callback loses it
setInterval(() => {
this.seconds++; // 'this' correctly refers to Timer instance
}, 1000);
}
}
// Object destructuring
const user = { name: 'Mayank', age: 15, city: 'Nagpur' };
const { name, age } = user; // name="Mayank", age=15
// Rename variables
const { name: userName, age: userAge } = user;
// Default values
const { role = 'student' } = user; // role="student"
// Nested destructuring
const data = {
user: { profile: { name: 'Mayank' } }
};
const { user: { profile: { name } } } = data; // name="Mayank"
// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3, 4, 5]
// Skip elements
const [, , third] = ['a', 'b', 'c']; // third='c'
// Swap variables
let a = 1, b = 2;
[a, b] = [b, a]; // a=2, b=1
// Function parameter destructuring
function greet({ name, age }) {
return `Hello ${name}, you are ${age}!`;
}
greet({ name: 'Mayank', age: 15 });
// Spread operator (...) โ expands
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
// Shallow copy
const copy = { ...obj1 };
// Spread in function calls
const nums = [3, 1, 4, 1, 5];
Math.max(...nums); // 5
// Rest parameters โ collects into array
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3, 4); // 10
// Rest in destructuring
const [head, ...tail] = [1, 2, 3]; // tail=[2, 3]
// math.js โ export named functions
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const PI = 3.14159;
// Default export โ one per file
export default class Calculator {
// ...
}
// app.js โ import
import Calculator, { add, subtract, PI } from './math.js';
// Import all
import * as Math from './math.js';
Math.add(1, 2);
// Dynamic import (code splitting)
const module = await import('./heavy-module.js');
// Optional chaining (?.) โ safe deep access
const user = { profile: { address: { city: 'Nagpur' } } };
const city = user?.profile?.address?.city; // "Nagpur"
const zip = user?.profile?.address?.zip; // undefined (no error!)
// Optional chaining with methods
const result = arr?.map(x => x * 2); // undefined if arr is null/undefined
// Nullish coalescing (??) โ default for null/undefined only
const config = { timeout: 0 };
const timeout = config.timeout ?? 3000; // 0 (not 3000!)
const fallback = config.timeout || 3000; // 3000 (wrong!)
// Object shorthand
const name = 'Mayank';
const age = 15;
const person = { name, age }; // { name: 'Mayank', age: 15 }
// Computed property names
const key = 'score';
const obj = { [key]: 100 }; // { score: 100 }
// for...of (iterates values)
for (const item of [1, 2, 3]) console.log(item);
// for...in (iterates keys โ for objects)
for (const key in { a: 1, b: 2 }) console.log(key);
this, arguments, or super. They're great for callbacks but not for object methods or constructors.thisimport/export โ one default, many named per file?.) prevents TypeError on null/undefinedclass Animal {
// Public field
species;
// Private field (#)
#age;
// Constructor
constructor(name, age) {
this.name = name;
this.#age = age;
this.species = 'Unknown';
}
// Public method
speak() {
return `${this.name} makes a sound`;
}
// Getter
get age() {
return this.#age;
}
// Setter with validation
set age(value) {
if (value < 0) throw new Error('Age cannot be negative');
this.#age = value;
}
// Static method
static create(name, age) {
return new Animal(name, age);
}
// Static property
static kingdom = 'Animalia';
}
const cat = new Animal('Cat', 3);
console.log(cat.speak()); // "Cat makes a sound"
console.log(cat.age); // 3 (getter)
cat.age = 5; // setter
console.log(Animal.kingdom); // "Animalia"
class Dog extends Animal {
constructor(name, age, breed) {
super(name, age); // call parent constructor
this.breed = breed;
}
// Override parent method
speak() {
return `${this.name} barks!`;
}
// New method
fetch(item) {
return `${this.name} fetches the ${item}`;
}
}
const rex = new Dog('Rex', 5, 'German Shepherd');
console.log(rex.speak()); // "Rex barks!"
console.log(rex.fetch('ball')); // "Rex fetches the ball"
console.log(rex.species); // "Unknown" (inherited)
class User {
#email;
#password;
constructor(email, password) {
this.#email = email;
this.#password = password;
this.createdAt = new Date();
}
// Factory method โ alternative to constructor
static fromJSON(json) {
const data = JSON.parse(json);
return new User(data.email, data.password);
}
// Static method for validation
static validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
checkPassword(password) {
return this.#password === password;
}
}
// Usage
const user = User.fromJSON('{"email":"m@test.com","pw":"123"}');
console.log(User.validateEmail('test@test.com')); // true
// JS doesn't support multiple inheritance, but mixins work
const Serializable = (superclass) => class extends superclass {
serialize() {
return JSON.stringify(this);
}
};
const Loggable = (superclass) => class extends superclass {
log() {
console.log(this.toString());
}
};
class BaseModel {
constructor(id) { this.id = id; }
}
// Mixin chain
class User extends Loggable(Serializable(BaseModel)) {
constructor(id, name) {
super(id);
this.name = name;
}
}
const u = new User(1, 'Mayank');
u.serialize(); // '{"id":1,"name":"Mayank"}'
u.log(); // User { id: 1, name: 'Mayank' }
#field) are truly private โ they can't be accessed from outside the class. Use them instead of the _prefix convention.# for truly private fields and methodsextends + super() for inheritance// localStorage โ persists until manually cleared
localStorage.setItem('theme', 'dark');
localStorage.getItem('theme'); // "dark"
localStorage.removeItem('theme');
localStorage.clear();
// Store objects โ must serialize
const user = { name: 'Mayank', age: 15 };
localStorage.setItem('user', JSON.stringify(user));
const stored = JSON.parse(localStorage.getItem('user'));
// sessionStorage โ cleared when tab closes
sessionStorage.setItem('token', 'abc123');
// Helper wrapper
const Storage = {
set(key, value, type = 'local') {
const store = type === 'local' ? localStorage : sessionStorage;
store.setItem(key, JSON.stringify(value));
},
get(key, type = 'local') {
const store = type === 'local' ? localStorage : sessionStorage;
const item = store.getItem(key);
return item ? JSON.parse(item) : null;
},
remove(key, type = 'local') {
const store = type === 'local' ? localStorage : sessionStorage;
store.removeItem(key);
}
};
Storage.set('user', { name: 'Mayank' });
Storage.get('user'); // { name: 'Mayank' }
// Set cookie (basic โ no helper)
document.cookie = "username=Mayank; expires=Fri, 31 Dec 2026 23:59:59 GMT; path=/";
// Read all cookies
console.log(document.cookie); // "username=Mayank; theme=dark"
// Cookie options
document.cookie = "token=abc123; path=/; max-age=86400; SameSite=Strict; Secure";
// Delete cookie
document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
// Better cookie helper
const Cookie = {
set(name, value, days = 7) {
const expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Strict`;
},
get(name) {
return document.cookie
.split('; ')
.find(row => row.startsWith(name + '='))
?.split('=')[1]
?.decodeURIComponent();
},
remove(name) {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
}
};
// IndexedDB โ for large/structured data
const DB_NAME = 'MyApp';
const DB_VERSION = 1;
function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onupgradeneeded = (e) => {
const db = e.target.result;
if (!db.objectStoreNames.contains('users')) {
const store = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
store.createIndex('name', 'name', { unique: false });
}
};
request.onsuccess = (e) => resolve(e.target.result);
request.onerror = (e) => reject(e.target.error);
});
}
async function addUser(user) {
const db = await openDB();
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.add(user);
return new Promise((resolve, reject) => {
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
async function getAllUsers() {
const db = await openDB();
const tx = db.transaction('users', 'readonly');
const store = tx.objectStore('users');
return new Promise((resolve, reject) => {
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
localStorage for small preferences (theme, settings). Use IndexedDB for large/complex data. Cookies are for server-readable data (auth tokens). All web storage is same-origin.const canvas = document.querySelector('#myCanvas');
const ctx = canvas.getContext('2d');
// Set canvas size
canvas.width = 800;
canvas.height = 600;
// Draw a rectangle
ctx.fillStyle = '#6c63ff';
ctx.fillRect(50, 50, 200, 100);
// Draw a circle
ctx.beginPath();
ctx.arc(400, 300, 50, 0, Math.PI * 2);
ctx.fillStyle = '#00e676';
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.stroke();
// Draw text
ctx.font = '24px Space Grotesk';
ctx.fillStyle = '#ffffff';
ctx.fillText('Hello Canvas!', 50, 400);
// Draw a line
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(800, 600);
ctx.strokeStyle = 'rgba(255,255,255,0.3)';
ctx.stroke();
// Smooth animation loop
const canvas = document.querySelector('#animCanvas');
const ctx = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 400;
let x = 0;
let y = 200;
let dx = 4;
let radius = 20;
function animate() {
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw ball
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = '#6c63ff';
ctx.fill();
// Update position
x += dx;
// Bounce off walls
if (x + radius > canvas.width || x - radius < 0) {
dx = -dx;
}
requestAnimationFrame(animate);
}
animate();
class Particle {
constructor(x, y) {
this.x = x;
this.y = y;
this.vx = (Math.random() - 0.5) * 4;
this.vy = (Math.random() - 0.5) * 4;
this.life = 1;
this.decay = Math.random() * 0.02 + 0.005;
this.size = Math.random() * 3 + 1;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life -= this.decay;
}
draw(ctx) {
ctx.globalAlpha = this.life;
ctx.fillStyle = '#6c63ff';
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1;
}
}
const particles = [];
canvas.addEventListener('click', (e) => {
for (let i = 0; i < 20; i++) {
particles.push(new Particle(e.offsetX, e.offsetY));
}
});
function animate() {
ctx.fillStyle = 'rgba(5, 5, 8, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = particles.length - 1; i >= 0; i--) {
particles[i].update();
particles[i].draw(ctx);
if (particles[i].life <= 0) particles.splice(i, 1);
}
requestAnimationFrame(animate);
}
// Three.js โ 3D graphics library
// Include: <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Create a cube
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshPhongMaterial({ color: 0x6c63ff });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// Add light
const light = new THREE.PointLight(0xffffff, 1, 100);
light.position.set(5, 5, 5);
scene.add(light);
// Camera position
camera.position.z = 3;
// Animation loop
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
requestAnimationFrame for smooth animations โ it syncs with the display refresh rate (usually 60fps). Use ctx.clearRect() before each frame to avoid trails.requestAnimationFrame is the correct way to animate in browsers// CommonJS (Node.js default)
// math.js
const add = (a, b) => a + b;
module.exports = { add };
// app.js
const { add } = require('./math.js');
// ES Modules (add "type": "module" to package.json)
// math.js
export const add = (a, b) => a + b;
// app.js
import { add } from './math.js';
import fs from 'fs/promises'; // async (preferred)
import fsSync from 'fs'; // sync
// Read file (async)
const data = await fs.readFile('file.txt', 'utf-8');
// Write file
await fs.writeFile('output.txt', 'Hello World');
// Append
await fs.appendFile('log.txt', 'New line\n');
// Check if file exists
const exists = await fs.access('file.txt').then(() => true).catch(() => false);
// List directory
const files = await fs.readdir('./src');
// Create directory
await fs.mkdir('new-dir', { recursive: true });
// Delete file
await fs.unlink('temp.txt');
// Rename/move
await fs.rename('old.txt', 'new.txt');
// Get file info
const stats = await fs.stat('file.txt');
console.log(stats.size, stats.mtime);
import http from 'http';
const server = http.createServer((req, res) => {
if (req.url === '/' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'Hello World' }));
} else if (req.url === '/users' && req.method === 'POST') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
const user = JSON.parse(body);
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id: 1, ...user }));
});
} else {
res.writeHead(404);
res.end('Not Found');
}
});
server.listen(3000, () => console.log('Server running on port 3000'));
# Initialize project
npm init -y
# Install dependencies
npm install express
npm install -D nodemon # dev dependency
# Scripts in package.json
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "jest"
}
}
# Run scripts
npm start
npm run dev
# Global install
npm install -g typescript
# Lock file โ never delete package-lock.json
# node_modules โ add to .gitignore
import express from 'express';
const app = express();
// Middleware
app.use(express.json()); // parse JSON bodies
app.use(express.urlencoded({ extended: true }));
// CORS middleware
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
next();
});
// Routes
app.get('/api/users', (req, res) => {
res.json([{ id: 1, name: 'Mayank' }]);
});
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).json({ error: 'Not found' });
res.json(user);
});
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
const user = { id: users.length + 1, name, email };
users.push(user);
res.status(201).json(user);
});
app.put('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).json({ error: 'Not found' });
Object.assign(user, req.body);
res.json(user);
});
app.delete('/api/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) return res.status(404).json({ error: 'Not found' });
users.splice(index, 1);
res.status(204).end();
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
app.listen(3000, () => console.log('API running on http://localhost:3000'));
express.json() middleware before routes to parse JSON request bodies. Always validate user input โ never trust req.body.import/export) are the modern standard โ add "type": "module"fs/promises (async) over fs (sync) โ don't block the event looppackage-lock.jsonreq.params, req.query, req.bodynodemon in development for auto-restart on file changesTests catch bugs before they reach production. They serve as documentation and enable safe refactoring. A good test suite gives you confidence to change code without breaking things.
# Jest
npm install -D jest
# package.json: "test": "jest"
# Vitest (faster, ESM-friendly)
npm install -D vitest
# package.json: "test": "vitest"
# Run tests
npm test
npm test -- --watch # watch mode
npm test -- --coverage # with coverage report
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const divide = (a, b) => {
if (b === 0) throw new Error('Cannot divide by zero');
return a / b;
};
// math.test.js
import { describe, it, expect } from 'vitest'; // vitest
// For Jest: just use global describe/it/expect
describe('Math operations', () => {
it('should add two numbers', () => {
expect(add(2, 3)).toBe(5);
});
it('should subtract two numbers', () => {
expect(subtract(5, 3)).toBe(2);
});
it('should throw on divide by zero', () => {
expect(() => divide(10, 0)).toThrow('Cannot divide by zero');
});
it('should handle negative numbers', () => {
expect(add(-1, -1)).toBe(-2);
expect(subtract(-1, -1)).toBe(0);
});
});
// Equality
expect(value).toBe(42); // strict equality (===)
expect(value).toEqual({ a: 1 }); // deep equality
// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
// Numbers
expect(value).toBeGreaterThan(5);
expect(value).toBeLessThanOrEqual(10);
expect(value).toBeCloseTo(3.14, 1); // precision
// Strings
expect(str).toMatch(/regex/);
expect(str).toContain('sub');
// Arrays
expect(arr).toHaveLength(3);
expect(arr).toContain('item');
expect(arr).toEqual(expect.arrayContaining([1, 2]));
// Objects
expect(obj).toHaveProperty('name');
expect(obj).toHaveProperty('name', 'Mayank');
expect(obj).toMatchObject({ name: 'Mayank' });
// Async
await expect(promise).resolves.toBe(value);
await expect(promise).rejects.toThrow(error);
// Mock a function
const mockFn = vi.fn(); // vitest
// const mockFn = jest.fn(); // jest
mockFn('hello');
expect(mockFn).toHaveBeenCalledWith('hello');
expect(mockFn).toHaveBeenCalledTimes(1);
// Mock return values
mockFn.mockReturnValue(42);
expect(mockFn()).toBe(42);
// Mock implementation
mockFn.mockImplementation((a, b) => a + b);
expect(mockFn(2, 3)).toBe(5);
// Mock a module
vi.mock('./api.js'); // vitest
// jest.mock('./api.js');
import { fetchUser } from './api.js';
fetchUser.mockResolvedValue({ id: 1, name: 'Mayank' });
// Spy on object method
const spy = vi.spyOn(obj, 'method');
obj.method();
expect(spy).toHaveBeenCalled();
// Testing async functions
describe('API', () => {
it('should fetch user data', async () => {
const user = await fetchUser(1);
expect(user).toHaveProperty('name');
expect(user.name).toBe('Mayank');
});
it('should handle API errors', async () => {
await expect(fetchUser(999)).rejects.toThrow('User not found');
});
});
// Testing with beforeEach/afterEach
describe('Counter', () => {
let counter;
beforeEach(() => {
counter = new Counter();
});
it('starts at 0', () => {
expect(counter.count).toBe(0);
});
it('increments', () => {
counter.increment();
expect(counter.count).toBe(1);
});
it('resets', () => {
counter.increment();
counter.increment();
counter.reset();
expect(counter.count).toBe(0);
});
});
// Using React Testing Library
import { render, screen, fireEvent } from '@testing-library/react';
it('renders correctly', () => {
render(<Button label="Click me" />);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('calls onClick when clicked', () => {
const handleClick = vi.fn();
render(<Button label="Click" onClick={handleClick} />);
fireEvent.click(screen.getByText('Click'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
describe blocks to group related testsdescribe/it/expect are the core testing primitivesbeforeEach/afterEach for setup and cleanup// Type coercion oddities
[] + [] // "" (empty string)
[] + {} // "[object Object]"
{} + [] // 0 (in some contexts)
true + true // 2
"5" - 3 // 2 (string โ number)
"5" + 3 // "53" (number โ string)
// Hoisting
var x = 1;
var y = 2;
console.log(x + y); // 3
// var is hoisted to top of function
// let/const are hoisted but not initialized (TDZ)
// console.log(z); // ReferenceError (Temporal Dead Zone)
let z = 1;
// Closures
function counter() {
let count = 0;
return {
increment: () => ++count,
getCount: () => count
};
}
const c = counter();
c.increment(); // 1
c.increment(); // 2
c.getCount(); // 2
// Debounce โ delay execution until user stops typing
function debounce(fn, ms = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
};
}
// Throttle โ execute at most once per interval
function throttle(fn, ms = 300) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= ms) {
last = now;
fn(...args);
}
};
}
// Memoize โ cache results
function memoize(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (!cache.has(key)) cache.set(key, fn(...args));
return cache.get(key);
};
}
// Deep clone (simple)
const clone = structuredClone(obj); // modern way
// or
const clone2 = JSON.parse(JSON.stringify(obj)); // no functions/dates
// Flatten array
const flat = [1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4]
const nums = [1, 2, 3, 4, 5];
// map โ transform each element
nums.map(n => n * 2); // [2, 4, 6, 8, 10]
// filter โ keep elements that pass test
nums.filter(n => n > 3); // [4, 5]
// reduce โ accumulate into single value
nums.reduce((sum, n) => sum + n, 0); // 15
// find โ first match
nums.find(n => n > 3); // 4
// some/every โ boolean checks
nums.some(n => n > 4); // true
nums.every(n => n > 0); // true
// flatMap โ map + flatten one level
['hello world', 'foo bar'].flatMap(s => s.split(' '));
// ['hello', 'world', 'foo', 'bar']
// at() โ negative indexing
nums.at(-1); // 5
nums.at(-2); // 4
const obj = { a: 1, b: 2, c: 3 };
Object.keys(obj); // ['a', 'b', 'c']
Object.values(obj); // [1, 2, 3]
Object.entries(obj); // [['a', 1], ['b', 2], ['c', 3]]
// fromEntries โ reverse of entries
Object.fromEntries([['a', 1], ['b', 2]]); // { a: 1, b: 2 }
// Freeze โ prevent mutations (shallow)
Object.freeze(obj);
obj.a = 99; // fails silently (strict mode: error)
// assign โ merge objects (mutates first!)
Object.assign({}, defaults, userSettings);
// pick/omit helpers
const pick = (obj, keys) =>
Object.fromEntries(keys.filter(k => k in obj).map(k => [k, obj[k]]));
const omit = (obj, keys) =>
Object.fromEntries(Object.entries(obj).filter(([k]) => !keys.includes(k)));