A comprehensive guide for developers new to JavaScript and its ecosystem - Updated for 2025
- Getting Started
- JavaScript Fundamentals
- Modern JavaScript (ES6+)
- Asynchronous JavaScript & Event Loop
- Introduction to TypeScript
- JavaScript Ecosystem
- Development Tools
- Best Practices
- Quick Reference
- Mini Projects
- Next Steps
Detailed Table of Contents
- JavaScript & TypeScript Beginner's Manual
- Table of Contents
- Getting Started
- JavaScript Fundamentals
- Modern JavaScript (ES6+)
- Template Literals
- Destructuring
- Spread Operator
- Optional Chaining & Nullish Coalescing
- Set Methods (ES2025 - Now Stable!)
- Promise.try() (ES2025)
- Import Attributes (ES2025)
- RegExp.escape() (ES2025)
- Array Methods
- Float16Array & Math.f16round() (ES2025)
- Popular Libraries & Frameworks
- Classes
- Modules (Import/Export)
- Module Resolution Gotchas
- Asynchronous JavaScript & Event Loop
- Introduction to TypeScript
- JavaScript Ecosystem
- Next Steps
- Final Tips for Success
JavaScript is a versatile, high-level programming language primarily used for:
- Web Development: Making websites interactive
- Server-Side Development: Building APIs and backends (Node.js)
- Mobile Apps: React Native, Ionic
- Desktop Apps: Electron
- Game Development: Browser-based games
Option 1: Browser Console (Immediate Start)
- Open any browser →
F12→ Console tab - Start typing JavaScript immediately
Option 2: Local Development
- Install Node.js (LTS v22+ recommended)
- Install a code editor (VS Code recommended)
- Create a
.jsfile and run withnode filename.js
Option 3: Online Playgrounds
// Hello World
console.log("Hello, World!");
// Variables and basic math
let name = "JavaScript";
let year = 2025;
console.log(`Welcome to ${name} in ${year}!`);// Variable declarations
var oldWay = "avoid this"; // Function-scoped, hoisted
let modernWay = "use this"; // Block-scoped
const constant = "binding is fixed"; // Block-scoped, binding immutable
// Primitive data types
let message = "Hello World";
let count = 42;
let isActive = true;
let emptyValue = null;
let notDefined = undefined;
let uniqueId = Symbol('unique');
let bigNumber = 123n; // Supported in all modern environments
// Type checking
console.log(typeof message); // "string"
console.log(typeof count); // "number"
console.log(typeof isActive); // "boolean"
// Important: const objects/arrays are still mutable
const user = { name: "Alice" };
user.name = "Bob"; // This works! Only the binding is immutable
console.log(user); // { name: "Bob" }// Objects
const person = {
name: "Alice",
age: 30,
city: "New York",
greet: function() {
return `Hello, I'm ${this.name}`;
}
};
// Accessing object properties
console.log(person.name); // Dot notation
console.log(person["age"]); // Bracket notation
console.log(person.greet()); // Method call
// Modern object property checking
console.log(Object.hasOwn(person, 'name')); // true (preferred over hasOwnProperty)
// Arrays
const fruits = ["apple", "banana", "orange"];
const scores = [1, 2, 3, 4, 5];
const mixedData = [1, "hello", true, null];
// Array methods
fruits.push("grape"); // Add to end
fruits.pop(); // Remove from end
fruits.unshift("mango"); // Add to beginning
fruits.shift(); // Remove from beginning
console.log(fruits.length); // Array length
// Modern array access
console.log(scores.at(-1)); // 5 (last element, ES2022)
console.log(scores.at(-2)); // 4 (second to last)// Function declaration
function greet(name) {
return `Hello, ${name}!`;
}
// Function expression
const greetExpression = function(name) {
return `Hello, ${name}!`;
};
// Arrow function (ES6+)
const greetArrow = (name) => {
return `Hello, ${name}!`;
};
// Short arrow function
const greetShort = name => `Hello, ${name}!`;
// Function with default parameters
function introduce(name, age = 25) {
return `I'm ${name}, ${age} years old`;
}
// Rest parameters
function calculateSum(...values) {
return values.reduce((total, num) => total + num, 0);
}
console.log(calculateSum(1, 2, 3, 4)); // 10// If/else statements
const userAge = 18;
if (userAge >= 18) {
console.log("Adult");
} else if (userAge >= 13) {
console.log("Teenager");
} else {
console.log("Child");
}
// Ternary operator
const status = userAge >= 18 ? "Adult" : "Minor";
// Switch statement
const dayOfWeek = "Monday";
switch (dayOfWeek) {
case "Monday":
console.log("Start of work week");
break;
case "Friday":
console.log("TGIF!");
break;
default:
console.log("Regular day");
}
// Loops
// For loop
for (let i = 0; i < 5; i++) {
console.log(i);
}
// For...of loop (arrays)
const colors = ["red", "green", "blue"];
for (const color of colors) {
console.log(color);
}
// For...in loop (objects)
const vehicle = { make: "Toyota", model: "Camry", year: 2025 };
for (const key in vehicle) {
console.log(`${key}: ${vehicle[key]}`);
}
// While loop
let counter = 0;
while (counter < 3) {
console.log(counter);
counter++;
}const firstName = "John";
const userAge = 25;
// Old way
const oldMessage = "Hello, my name is " + firstName + " and I'm " + userAge + " years old";
// Modern way
const newMessage = `Hello, my name is ${firstName} and I'm ${userAge} years old`;
// Multi-line strings
const htmlTemplate = `
<div>
<h1>${firstName}</h1>
<p>Age: ${userAge}</p>
</div>
`;// Array destructuring
const coordinates = [1, 2, 3, 4, 5];
const [x, y, ...remaining] = coordinates;
console.log(x); // 1
console.log(y); // 2
console.log(remaining); // [3, 4, 5]
// Object destructuring
const userProfile = { name: "Alice", email: "[email protected]", age: 30 };
const { name, email } = userProfile;
console.log(name); // "Alice"
console.log(email); // "[email protected]"
// Destructuring with renaming
const { name: displayName, age: displayAge } = userProfile;
// Destructuring with defaults
const { city = "Unknown" } = userProfile;// Array spread
const firstBatch = [1, 2, 3];
const secondBatch = [4, 5, 6];
const allNumbers = [...firstBatch, ...secondBatch]; // [1, 2, 3, 4, 5, 6]
// Object spread
const basicInfo = { name: "John", age: 30 };
const employeeInfo = { ...basicInfo, job: "Developer", salary: 50000 };
// Function arguments
function sum(a, b, c) {
return a + b + c;
}
const numberList = [1, 2, 3];
console.log(sum(...numberList)); // 6// Optional chaining (?.) - ES2020
const userData = {
profile: {
social: {
twitter: "@johndoe"
}
}
};
// Safe property access
console.log(userData?.profile?.social?.twitter); // "@johndoe"
console.log(userData?.profile?.social?.linkedin); // undefined (no error)
// Optional method calls
const api = {
getData: () => "some data"
};
console.log(api.getData?.()); // "some data"
console.log(api.postData?.()); // undefined (no error)
// Optional array access
const items = ["apple", "banana"];
console.log(items?.[0]); // "apple"
console.log(items?.[10]); // undefined
// Nullish coalescing (??) - ES2020
const config = {
timeout: 0,
retries: null,
debug: false
};
// Only null/undefined trigger fallback, not falsy values
console.log(config.timeout ?? 5000); // 0 (not 5000!)
console.log(config.retries ?? 3); // 3
console.log(config.debug ?? true); // false (not true!)
// Compare with || operator
console.log(config.timeout || 5000); // 5000 (falsy value replaced)
console.log(config.debug || true); // true (falsy value replaced)const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// Union - combine sets
const union = setA.union(setB);
console.log(union); // Set(6) {1, 2, 3, 4, 5, 6}
// Intersection - common elements
const intersection = setA.intersection(setB);
console.log(intersection); // Set(2) {3, 4}
// Difference - elements in setA but not setB
const difference = setA.difference(setB);
console.log(difference); // Set(2) {1, 2}
// Symmetric difference - elements in either set, but not both
const symmetricDifference = setA.symmetricDifference(setB);
console.log(symmetricDifference); // Set(4) {1, 2, 5, 6}
// Set relationship checks
console.log(setA.isSubsetOf(setB)); // false
console.log(setA.isSupersetOf(setB)); // false
console.log(setA.isDisjointFrom(new Set([7, 8]))); // true// Promise.try() - unified wrapper for sync/async operations
async function processData(data) {
return Promise.try(() => {
if (data.sync) {
return data.value * 2; // Synchronous operation
} else {
return fetch('/api/process').then(r => r.json()); // Async operation
}
})
.then(result => {
console.log('Result:', result);
return result;
})
.catch(error => {
console.error('Error:', error);
throw error;
});
}
// Both sync and async operations are handled consistently
await processData({ sync: true, value: 5 });
await processData({ sync: false });// Old import assertions (deprecated)
// import data from './data.json' assert { type: 'json' };
// New import attributes syntax
import data from './data.json' with { type: 'json' };
import styles from './styles.css' with { type: 'css' };
// Dynamic imports with attributes
const config = await import('./config.json', {
with: { type: 'json' }
});// Safely escape special regex characters
const userInput = "Hello (world) [2025]";
const escapedInput = RegExp.escape(userInput);
console.log(escapedInput); // "Hello \\(world\\) \\[2025\\]"
// Safe to use in regex patterns
const regex = new RegExp(escapedInput);
console.log(regex.test("Hello (world) [2025]")); // true
// Before ES2025, you had to manually escape:
function oldEscape(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}const numberSet = [1, 2, 3, 4, 5];
// map - transform each element
const doubled = numberSet.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter - keep elements that pass test
const evenNumbers = numberSet.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]
// reduce - accumulate values
const total = numberSet.reduce((acc, num) => acc + num, 0);
console.log(total); // 15
// find - get first element that matches
const found = numberSet.find(num => num > 3);
console.log(found); // 4
// includes - check if element exists
console.log(numberSet.includes(3)); // true
// forEach - execute function for each element
numberSet.forEach(num => console.log(num));
// Modern additions
const words = ["apple", "banana", "cherry"];
const longWords = words.filter(word => word.length > 5);
console.log(longWords); // ["banana", "cherry"]// 16-bit floating point arrays for memory efficiency
const float16Array = new Float16Array([1.5, 2.7, 3.14159]);
console.log(float16Array); // Float16Array(3) [1.5, 2.7, 3.14159]
// Round to 16-bit precision
const rounded = Math.f16round(3.14159265359);
console.log(rounded); // 3.140625 (16-bit precision)
// Useful for graphics, ML, and memory-constrained applications
const vertices = new Float16Array([
0.0, 1.0 // Top
]);This section provides a brief overview of some of the most popular and influential libraries and frameworks in the JavaScript ecosystem.
// Functional component with hooks
import React, { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}// Basic Express server
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3000
}
});class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
getAge() {
return this.age;
}
static getSpecies() {
return "Homo sapiens";
}
}
class Student extends Person {
constructor(name, age, school) {
super(name, age); // Call parent constructor
this.school = school;
}
study() {
return `${this.name} is studying at ${this.school}`;
}
}
const student = new Student("Alice", 20, "MIT");
console.log(student.greet()); // "Hello, I'm Alice"
console.log(student.study()); // "Alice is studying at MIT"// math.js - Named exports
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export const PI = 3.14159;
// utils.js - Default export
export default function formatCurrency(amount) {
return `${amount.toFixed(2)}`;
}
// main.js - Importing
import formatCurrency from './utils.js'; // Default import
import { add, multiply, PI } from './math.js'; // Named imports
import { add as addNumbers } from './math.js'; // Rename import
import * as mathUtils from './math.js'; // Namespace import
// Dynamic imports (ES2020)
async function loadModule() {
const { add } = await import('./math.js');
console.log(add(2, 3));
}
// Top-level await (ES2022) - in modules only
const mathModule = await import('./math.js');
console.log(mathModule.add(5, 10));Warning
Common Pitfall: Mixing CommonJS (CJS) and ES Modules (ESM) can cause ERR_REQUIRE_ESM errors.
Tip
2025 Recommendation: Use ESM (ES Modules) as your default choice. CommonJS should only be used for legacy codebases or specific compatibility requirements.
The Problem:
// ❌ This will fail if the imported module is ESM-only
const express = require('express'); // CJS syntax
// Error: ERR_REQUIRE_ESM: require() of ES Module not supported
// ✅ Use this instead
import express from 'express'; // ESM syntaxWhy ESM is the Future (2025):
- Better tree-shaking support (smaller bundles)
- Native browser compatibility
- Future-proof for tooling ecosystem
- Async module loading capabilities
- Static analysis benefits
Solutions:
- Set
"type": "module"in package.json (recommended) - Use
.mjsextension for ES modules - Use
.cjsextension for CommonJS modules
package.json for pure ESM project (recommended for 2025):
{
"type": "module",
"scripts": {
"start": "node src/index.js"
}
}Node.js 22 Mixed Environment Support:
// With Node.js 22's --experimental-require-module flag
// You can now require ESM modules synchronously in CommonJS
const { readFile } = require('fs/promises'); // Now works!
// For importing CJS in ESM (still needed occasionally)
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const someCommonJSModule = require('old-cjs-package');
// For importing ESM in CJS (async only)
const { default: someESModule } = await import('es-module-package');Event Loop Flow:
graph TD
A[Call Stack] --> B{Stack Empty?}
B -->|No| A
B -->|Yes| C[Check Microtask Queue]
C --> D{Microtasks Available?}
D -->|Yes| E[Execute Microtask]
E --> A
D -->|No| F[Check Macrotask Queue]
F --> G{Macrotasks Available?}
G -->|Yes| H[Execute Macrotask]
H --> A
G -->|No| I[Wait for I/O]
I --> C
Task Queue Priority:
flowchart LR
A[Synchronous Code<br/>Call Stack] --> B[Microtasks<br/>Higher Priority]
B --> C[Macrotasks<br/>Lower Priority]
B1[Promise.then<br/>queueMicrotask<br/>MutationObserver] --> B
C1[setTimeout<br/>setInterval<br/>setImmediate<br/>DOM Events] --> C
Key Concepts:
- Call Stack: Where your synchronous code runs
- Microtask Queue: Promises, queueMicrotask (higher priority)
- Macrotask Queue: setTimeout, setInterval, DOM events (lower priority)
// Event loop demonstration
console.log('1'); // Synchronous
setTimeout(() => console.log('2'), 0); // Macrotask
Promise.resolve().then(() => console.log('3')); // Microtask
console.log('4'); // Synchronous
// Output: 1, 4, 3, 2| Test Case | Output |
|---|---|
| Expected | 1, 2, 3, 4 |
| Actual | 1, 4, 3, 2 |
Why? Microtasks run before macrotasks!
Important
Callbacks are legacy patterns. Don't start new projects with callbacks - use Promises and async/await instead. Callbacks lead to "callback hell" and make error handling difficult.
The Evolution:
- Callbacks (legacy) → Hard to read, error-prone
- Promises (ES6) → Chainable, better error handling
- Async/Await (ES2017) → Synchronous-looking async code
// ❌ DON'T write new code like this
function fetchUserData(userId, callback) {
setTimeout(() => {
const userData = { id: userId, name: "John" };
callback(userData);
}, 1000);
}
fetchUserData(1, (data) => {
console.log("Received:", data);
});
// ❌ This is "callback hell" - avoid it!
fetchUserData(1, (user) => {
fetchUserPosts(user.id, (posts) => {
fetchPostComments(posts[0].id, (comments) => {
console.log("Deep nesting is impossible to maintain!");
});
});
});// Creating a Promise
function fetchUser(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId > 0) {
resolve({ id: userId, name: "John Doe" });
} else {
reject(new Error("Invalid user ID"));
}
}, 1000);
});
}
// Using Promises
fetchUser(1)
.then(user => {
console.log("User:", user);
return fetchUser(2); // Chain another promise
})
.then(user2 => {
console.log("User 2:", user2);
})
.catch(error => {
console.error("Error:", error.message);
})
.finally(() => {
console.log("Operation completed");
});
// Promise utilities (ES2021+)
const promises = [fetchUser(1), fetchUser(2), fetchUser(3)];
// All must succeed
Promise.all(promises)
.then(users => console.log("All users:", users));
// First to resolve wins
Promise.race(promises)
.then(firstUser => console.log("First user:", firstUser));
// All settled (success or failure)
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`User ${index + 1}:`, result.value);
} else {
console.log(`User ${index + 1} failed:`, result.reason);
}
});
});
// First to resolve (ignores rejections until all fail)
Promise.any(promises)
.then(user => console.log("First successful user:", user))
.catch(error => console.log("All failed:", error));// Async function
async function getUserData(userId) {
try {
const user = await fetchUser(userId);
console.log("User:", user);
// You can use await multiple times
const user2 = await fetchUser(2);
console.log("User 2:", user2);
return user;
} catch (error) {
console.error("Error:", error.message);
throw error; // Re-throw if needed
}
}
// Call async function
getUserData(1);
// Parallel execution with Promise.all
async function getMultipleUsers() {
try {
const [user1, user2, user3] = await Promise.all([
fetchUser(1),
fetchUser(2),
fetchUser(3)
]);
console.log("All users:", user1, user2, user3);
} catch (error) {
console.error("One or more requests failed:", error);
}
}
// Top-level await (ES2022) - only in modules
const mainUser = await fetchUser(1);
console.log("Main user:", mainUser);// GET request
async function getUsers() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const users = await response.json();
console.log(users);
return users;
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
}
// POST request with error handling
async function createUser(userData) {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Server error: ${errorData.message}`);
}
const newUser = await response.json();
console.log('Created user:', newUser);
return newUser;
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
}
// Fetch with timeout
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
}
}TypeScript is a superset of JavaScript that adds static type checking. It compiles to plain JavaScript and helps catch errors during development.
Benefits:
- Catch errors at compile time
- Better IDE support (autocomplete, refactoring)
- Self-documenting code
- Easier refactoring in large codebases
{
"compilerOptions": {
"target": "ES2025",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2025", "DOM"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"sourceMap": true,
"allowImportingTsExtensions": true,
"noEmit": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}Key strict mode options:
noImplicitAny: Error on variables with implicitanytypestrictNullChecks:nullandundefinedmust be explicitly handledstrictFunctionTypes: Stricter checking of function typesnoImplicitReturns: Error when not all code paths return a value
// Basic type annotations
let userName: string = "John";
let userAge: number = 30;
let isActive: boolean = true;
let hobbies: string[] = ["reading", "coding"];
let scores: Array<number> = [1, 2, 3];
// Object type
let person: { name: string; age: number } = {
name: "Alice",
age: 25
};
// Function types
function greet(name: string): string {
return `Hello, ${name}!`;
}
// Arrow function with types
const add = (a: number, b: number): number => a + b;
// Optional parameters
function introduce(name: string, age?: number): string {
return age ? `I'm ${name}, ${age} years old` : `I'm ${name}`;
}
// Union types
let id: string | number = "user123";
id = 42; // Also valid
// Literal types
let status: "pending" | "approved" | "rejected" = "pending";
// Arrays with union types
let mixedArray: (string | number)[] = ["hello", 42, "world"];// Interface definition
interface User {
id: number;
name: string;
email: string;
age?: number; // Optional property
readonly createdAt: Date; // Read-only property
}
// Using interface
const user: User = {
id: 1,
name: "John Doe",
email: "[email protected]",
createdAt: new Date()
};
// Interface for functions
interface Calculator {
(a: number, b: number): number;
}
const multiply: Calculator = (a, b) => a * b;
// Extending interfaces
interface Employee extends User {
department: string;
salary: number;
}
// Type aliases
type Status = "pending" | "approved" | "rejected";
type ID = string | number;
// Advanced type operations
type UserKeys = keyof User; // "id" | "name" | "email" | "age" | "createdAt"
type UserName = User['name']; // string
type RequiredUser = Required<User>; // Makes all properties required
type PartialUser = Partial<User>; // Makes all properties optional
type UserWithoutId = Omit<User, 'id'>; // Removes 'id' property
type UserIdAndName = Pick<User, 'id' | 'name'>; // Only 'id' and 'name'
// Satisfies operator (TypeScript 4.9+)
const userConfig = {
name: "John",
age: 30,
isActive: true
} satisfies Record<string, string | number | boolean>;
// userConfig.name is inferred as "John", not string
// but still must satisfy the Record constraint// Generic function
function identity<T>(arg: T): T {
return arg;
}
// Usage
const stringResult = identity<string>("hello"); // string
const numberResult = identity(42); // number (inferred)
// Generic interface
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// Generic constraints
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("hello"); // Works, string has length
logLength([1, 2, 3]); // Works, array has length
// logLength(42); // Error, number doesn't have length
// Conditional types
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
// Mapped types
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type ReadonlyUser = Readonly<User>;abstract class Animal {
protected name: string; // Protected: accessible in this class and subclasses
private age: number; // Private: only accessible in this class
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public speak(): string { // Public: accessible everywhere
return `${this.name} makes a sound`;
}
abstract makeSound(): string; // Must be implemented by subclasses
getAge(): number {
return this.age;
}
}
class Dog extends Animal {
private breed: string;
constructor(name: string, age: number, breed: string) {
super(name, age);
this.breed = breed;
}
makeSound(): string {
return "Woof!";
}
speak(): string {
return `${this.name} barks: ${this.makeSound()}`;
}
getBreed(): string {
return this.breed;
}
}
// Interface implementation
interface Flyable {
fly(): void;
altitude: number;
}
class Bird implements Flyable {
altitude: number = 0;
fly(): void {
this.altitude += 100;
console.log(`Flying at ${this.altitude} feet`);
}
}# Check versions (Node 22+ recommended)
node --version
npm --version
# Initialize a new project
npm init -y
# Install packages
npm install express # Production dependency
npm install -D typescript @types/node jest # Development dependencies
npm install -g pnpm # Global installation (alternative package manager)
# Run scripts (defined in package.json)
npm start
npm test
npm run build
npm run dev
# Update packages
npm update
npm outdated # Check for outdated packagesModern package.json (2025):
{
"name": "my-project",
"version": "1.0.0",
"type": "module",
"engines": {
"node": ">=22.0.0"
},
"scripts": {
"start": "node dist/index.js",
"dev": "tsx watch src/index.ts",
"build": "tsc",
"test": "jest",
"lint": "eslint src/**/*.ts",
"format": "prettier --write src/**/*.ts"
},
"dependencies": {
"express": "^4.19.0",
"zod": "^3.22.0"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^20.11.0",
"eslint": "^9.0.0",
"jest": "^31.0.0",
"prettier": "^3.2.0",
"tsx": "^4.7.0",
"typescript": "^5.4.0"
}
}React (UI Library)
import React, { useState, useEffect } from 'react';
interface CounterProps {
initialValue?: number;
}
function Counter({ initialValue = 0 }: CounterProps) {
const [count, setCount] = useState(initialValue);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
</div>
);
}
export default Counter;Express.js (Backend Framework)
import express from 'express';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';
const app = express();
const prisma = new PrismaClient();
app.use(express.json());
// Validation schemas
const CreateUserSchema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Invalid email format')
});
const UpdateUserSchema = CreateUserSchema.partial();
// Routes
app.get('/users', async (req, res) => {
try {
const users = await prisma.user.findMany({
orderBy: { createdAt: 'desc' }
});
res.json(users);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch users' });
}
});
app.get('/users/:id', async (req, res) => {
try {
const id = parseInt(req.params.id);
const user = await prisma.user.findUnique({
where: { id }
});
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch user' });
}
});
app.post('/users', async (req, res) => {
try {
const userData = CreateUserSchema.parse(req.body);
const newUser = await prisma.user.create({
data: userData
});
res.status(201).json(newUser);
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
error: 'Validation failed',
details: error.errors
});
}
// Prisma unique constraint error
if ((error as any).code === 'P2002') {
return res.status(400).json({ error: 'Email already exists' });
}
res.status(500).json({ error: 'Failed to create user' });
}
});
app.put('/users/:id', async (req, res) => {
try {
const id = parseInt(req.params.id);
const updateData = UpdateUserSchema.parse(req.body);
const updatedUser = await prisma.user.update({
where: { id },
data: updateData
});
res.json(updatedUser);
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
error: 'Validation failed',
details: error.errors
});
}
if ((error as any).code === 'P2025') {
return res.status(404).json({ error: 'User not found' });
}
res.status(500).json({ error: 'Failed to update user' });
}
});
app.delete('/users/:id', async (req, res) => {
try {
const id = parseInt(req.params.id);
await prisma.user.delete({
where: { id }
});
res.status(204).send();
} catch (error) {
if ((error as any).code === 'P2025') {
return res.status(404).json({ error: 'User not found' });
}
res.status(500).json({ error: 'Failed to delete user' });
}
});
// Graceful shutdown
process.on('SIGINT', async () => {
await prisma.$disconnect();
process.exit();
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
export default app;Run the server:
npx tsx src/app.tspackage.json:
{
"name": "react-todo-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --ext ts,tsx --fix",
"format": "prettier --write \"src/**/*.{ts,tsx,css}\""
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^9.0.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"prettier": "^3.2.5",
"typescript": "^5.4.0",
"vite": "^5.1.0"
}
}eslint.config.js:
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from '@typescript-eslint/eslint-plugin'
import tsParser from '@typescript-eslint/parser'
export default [
{ ignores: ['dist'] },
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parser: tsParser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
'@typescript-eslint': tseslint,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'@typescript-eslint/no-unused-vars': 'error',
'prefer-const': 'error',
'no-console': 'warn',
},
},
]prettier.config.js:
export default {
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
printWidth: 80,
bracketSpacing: true,
arrowParens: 'avoid',
};src/types.ts:
export interface Todo {
id: number;
text: string;
completed: boolean;
createdAt: Date;
}
export type TodoFilter = 'all' | 'active' | 'completed';src/TodoApp.tsx:
import React, { useState, useCallback, useMemo } from 'react';
import { Todo, TodoFilter } from './types';
const TodoApp: React.FC = () => {
const [todos, setTodos] = useState<Todo[]>([]);
const [filter, setFilter] = useState<TodoFilter>('all');
const [newTodoText, setNewTodoText] = useState('');
const addTodo = useCallback((text: string) => {
if (text.trim()) {
const newTodo: Todo = {
id: Date.now(),
text: text.trim(),
completed: false,
createdAt: new Date()
};
setTodos(prev => [...prev, newTodo]);
setNewTodoText('');
}
}, []);
const toggleTodo = useCallback((id: number) => {
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
}, []);
const deleteTodo = useCallback((id: number) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
}, []);
const filteredTodos = useMemo(() => {
switch (filter) {
case 'active':
return todos.filter(todo => !todo.completed);
case 'completed':
return todos.filter(todo => todo.completed);
default:
return todos;
}
}, [todos, filter]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
addTodo(newTodoText);
};
return (
<div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
<h1>Todo App</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
value={newTodoText}
onChange={(e) => setNewTodoText(e.target.value)}
placeholder="Add a new todo..."
style={{ width: '70%', padding: '10px', marginRight: '10px' }}
/>
<button type="submit" disabled={!newTodoText.trim()}>
Add Todo
</button>
</form>
<div style={{ margin: '20px 0' }}>
{(['all', 'active', 'completed'] as TodoFilter[]).map(filterOption => (
<button
key={filterOption}
onClick={() => setFilter(filterOption)}
style={{
marginRight: '10px',
padding: '5px 10px',
backgroundColor: filter === filterOption ? '#007bff' : '#f8f9fa',
color: filter === filterOption ? 'white' : 'black',
border: '1px solid #ccc',
borderRadius: '4px'
}}
>
{filterOption.charAt(0).toUpperCase() + filterOption.slice(1)}
</button>
))}
</div>
<ul style={{ listStyle: 'none', padding: 0 }}>
{filteredTodos.map(todo => (
<li
key={todo.id}
style={{
display: 'flex',
alignItems: 'center',
padding: '10px',
borderBottom: '1px solid #eee'
}}
>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
style={{ marginRight: '10px' }}
/>
<span
style={{
flex: 1,
textDecoration: todo.completed ? 'line-through' : 'none',
color: todo.completed ? '#999' : 'black'
}}
>
{todo.text}
</span>
<button
onClick={() => deleteTodo(todo.id)}
style={{
backgroundColor: '#dc3545',
color: 'white',
border: 'none',
padding: '5px 10px',
borderRadius: '4px'
}}
>
Delete
</button>
</li>
))}
</ul>
{filteredTodos.length === 0 && (
<p style={{ textAlign: 'center', color: '#999', marginTop: '20px' }}>
No todos to show
</p>
)}
</div>
);
};
export default TodoApp;src/calculator.ts:
export class Calculator {
private history: string[] = [];
add(a: number, b: number): number {
const result = a + b;
this.history.push(`${a} + ${b} = ${result}`);
return result;
}
subtract(a: number, b: number): number {
const result = a - b;
this.history.push(`${a} - ${b} = ${result}`);
return result;
}
multiply(a: number, b: number): number {
const result = a * b;
this.history.push(`${a} * ${b} = ${result}`);
return result;
}
divide(a: number, b: number): number {
if (b === 0) {
throw new Error('Division by zero is not allowed');
}
const result = a / b;
this.history.push(`${a} / ${b} = ${result}`);
return result;
}
getHistory(): string[] {
return [...this.history];
}
clearHistory(): void {
this.history = [];
}
}src/api.ts:
export interface User {
id: number;
name: string;
email: string;
}
export class UserApi {
constructor(private baseUrl: string) {}
async getUser(id: number): Promise<User> {
const response = await fetch(`${this.baseUrl}/users/${id}`);
if (!response.ok) {
if (response.status === 404) {
throw new Error(`User with id ${id} not found`);
}
throw new Error(`API error: ${response.status}`);
}
return response.json();
}
async createUser(userData: Omit<User, 'id'>): Promise<User> {
const response = await fetch(`${this.baseUrl}/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
if (!response.ok) {
throw new Error(`Failed to create user: ${response.status}`);
}
return response.json();
}
}src/tests/calculator.test.ts:
import { describe, test, expect, beforeEach } from 'vitest';
import { Calculator } from '../calculator';
describe('Calculator', () => {
let calculator: Calculator;
beforeEach(() => {
calculator = new Calculator();
});
describe('basic operations', () => {
test('should add two numbers correctly', () => {
expect(calculator.add(2, 3)).toBe(5);
expect(calculator.add(-1, 1)).toBe(0);
expect(calculator.add(0.1, 0.2)).toBeCloseTo(0.3);
});
test('should subtract two numbers correctly', () => {
expect(calculator.subtract(5, 3)).toBe(2);
expect(calculator.subtract(1, 1)).toBe(0);
expect(calculator.subtract(-1, -1)).toBe(0);
});
test('should multiply two numbers correctly', () => {
expect(calculator.multiply(3, 4)).toBe(12);
expect(calculator.multiply(-2, 3)).toBe(-6);
expect(calculator.multiply(0, 5)).toBe(0);
});
test('should divide two numbers correctly', () => {
expect(calculator.divide(10, 2)).toBe(5);
expect(calculator.divide(7, 2)).toBe(3.5);
expect(calculator.divide(-6, 3)).toBe(-2);
});
test('should throw error when dividing by zero', () => {
expect(() => calculator.divide(5, 0)).toThrow('Division by zero is not allowed');
});
});
describe('history functionality', () => {
test('should record operations in history', () => {
calculator.add(2, 3);
calculator.multiply(4, 5);
const history = calculator.getHistory();
expect(history).toHaveLength(2);
expect(history[0]).toBe('2 + 3 = 5');
expect(history[1]).toBe('4 * 5 = 20');
});
test('should clear history', () => {
calculator.add(1, 1);
calculator.clearHistory();
expect(calculator.getHistory()).toHaveLength(0);
});
test('should not modify original history array', () => {
calculator.add(1, 1);
const history1 = calculator.getHistory();
const history2 = calculator.getHistory();
expect(history1).toEqual(history2);
expect(history1).not.toBe(history2); // Different references
});
});
});src/tests/api.test.ts:
import { describe, test, expect, beforeEach, vi } from 'vitest';
import { UserApi, User } from '../api';
// Mock fetch globally
global.fetch = vi.fn();
describe('UserApi', () => {
let api: UserApi;
beforeEach(() => {
api = new UserApi('https://api.example.com');
vi.clearAllMocks();
});
describe('getUser', () => {
test('should fetch user successfully', async () => {
const mockUser: User = {
id: 1,
name: 'John Doe',
email: '[email protected]'
};
(fetch as any).mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValueOnce(mockUser),
});
const result = await api.getUser(1);
expect(result).toEqual(mockUser);
expect(fetch).toHaveBeenCalledWith('https://api.example.com/users/1');
});
test('should throw error when user not found', async () => {
(fetch as any).mockResolvedValueOnce({
ok: false,
status: 404,
});
await expect(api.getUser(999)).rejects.toThrow('User with id 999 not found');
});
test('should throw error for other API errors', async () => {
(fetch as any).mockResolvedValueOnce({
ok: false,
status: 500,
});
await expect(api.getUser(1)).rejects.toThrow('API error: 500');
});
});
describe('createUser', () => {
test('should create user successfully', async () => {
const newUser = { name: 'Jane Doe', email: '[email protected]' };
const createdUser: User = { id: 2, ...newUser };
(fetch as any).mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValueOnce(createdUser),
});
const result = await api.createUser(newUser);
expect(result).toEqual(createdUser);
expect(fetch).toHaveBeenCalledWith('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newUser),
});
});
test('should throw error when creation fails', async () => {
(fetch as any).mockResolvedValueOnce({
ok: false,
status: 400,
});
const newUser = { name: 'Jane Doe', email: 'invalid-email' };
await expect(api.createUser(newUser)).rejects.toThrow('Failed to create user: 400');
});
});
});Integration testing with supertest:
import { describe, test, expect } from 'vitest';
import request from 'supertest';
import app from '../app'; // Your Express app
describe('API endpoints', () => {
test('GET / returns hello message', async () => {
const response = await request(app)
.get('/')
.expect(200);
expect(response.body).toEqual({ message: 'Hello World!' });
});
test('POST /users creates user with valid data', async () => {
const userData = {
name: 'John Doe',
email: '[email protected]',
age: 30
};
const response = await request(app)
.post('/users')
.send(userData)
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.user).toMatchObject(userData);
});
});Run tests:
npm testRepository Links for Complete Projects:
- Express API Starter: github.com/microsoft/TypeScript-Node-Starter
- React TypeScript Template: github.com/facebook/create-react-app with
--template typescript - Full-Stack Examples: github.com/sindresorhus/awesome-nodejs
-
Advanced Async Patterns
- AbortController for cancellable operations
- Streaming APIs and async iterators
- Worker threads for CPU-intensive tasks
- Service Workers for background processing
-
Advanced TypeScript
- Template literal types
- Conditional types and mapped types
- Decorators (experimental)
- Type guards and assertion functions
-
Modern Framework Deep Dives
- React: Suspense, Concurrent features, Server Components
- Next.js: App Router, Server Actions
- Vue 3: Composition API, Pinia
- SvelteKit: Full-stack framework
-
Backend Architecture
- GraphQL with Apollo or Yoga
- Database integration (Prisma, TypeORM)
- Microservices patterns
- Event-driven architecture
-
Testing & Quality
- End-to-end testing with Playwright
- Visual regression testing
- Property-based testing
- Performance testing
-
Build Tools & DevOps
- Advanced Webpack/Vite configuration
- Docker containerization
- CI/CD with GitHub Actions
- Deployment strategies
-
Performance & Monitoring
- Web Vitals optimization
- Application monitoring (Sentry, DataDog)
- Bundle analysis and optimization
- Memory leak detection
- Master ES2025 features (Set methods, Promise.try, import attributes)
- Learn TypeScript 5.5 (inferred type predicates, template literal improvements)
- Set up modern tooling (Vite 6, ESLint 9 flat config, Vitest)
- Build 2-3 small projects with modern patterns
- React 19 (Server Components, Actions API, useOptimistic)
- Next.js 15 App Router (stable patterns)
- Modern state management (Zustand, Jotai, or built-in React state)
- Component architecture patterns
- Node.js 22 LTS features (native WebSocket, enhanced ESM)
- Database integration (Prisma 5+, tRPC)
- Authentication systems (NextAuth.js, Clerk)
- Deploy to modern platforms (Vercel, Railway, Fly.io)
- Performance optimization (React Compiler, bundle analysis)
- Advanced TypeScript patterns (template literal types, conditional types)
- Testing strategies (Vitest, Playwright)
- Open source contributions
- MDN Web Docs - The gold standard for web APIs
- TypeScript Handbook - Official TS documentation
- Node.js Documentation - Server-side JavaScript
- React Documentation - Modern React patterns
- freeCodeCamp - Comprehensive, free curriculum
- JavaScript.info - In-depth JS concepts
- TypeScript Exercises - Hands-on TS practice
- Node.js Best Practices - Production-ready patterns
- Codewars - Algorithmic challenges
- Frontend Mentor - Real-world UI challenges
- LeetCode - Algorithm practice
- Advent of Code - Annual coding puzzles
- JavaScript Weekly - Weekly JS news
- TypeScript Blog - Official TS updates
- Web.dev - Google's web development resources
- Can I Use - Browser compatibility reference
- Stack Overflow - Q&A community
- Reddit r/javascript - Community discussions
- Discord: Reactiflux - Real-time help
- GitHub - Explore open source projects
- Build in public - share your learning journey
- Contribute to open source projects
- Attend meetups and conferences (virtual or in-person)
- Keep a learning journal to track progress
- Help others learn - teaching reinforces your own understanding
Essential Projects to Showcase:
-
Todo App with Advanced Features (bit boring though... everyone has done it, but if you're a beginner sure give it a try I guess)
- TypeScript, React/Vue, drag-and-drop, local storage
- Live demo + clean code on GitHub
-
Full-Stack Application
- Express API, database, authentication, deployment
- Real-world problem solver (weather app, expense tracker, etc.)
-
Open Source Contribution
- Bug fixes or feature additions to existing projects
- Shows collaboration skills and code quality
-
Performance-Focused Project
- Demonstrates optimization techniques
- Bundle analysis, lazy loading, caching strategies
-
Testing Showcase
- Well-tested codebase with multiple testing strategies
- Shows professional development practices
Development Mindset:
- Write code every day - Consistency beats intensity
- Build projects, not just tutorials - Apply what you learn immediately
- Read other people's code - GitHub is a treasure trove of learning material
- Embrace debugging - It's where real learning happens
- Stay curious - Technology evolves rapidly; adaptability is key
Code Quality Habits:
- Use TypeScript from day one on new projects
- Set up ESLint and Prettier immediately
- Write tests for critical functionality
- Use meaningful commit messages
- Review your own code before pushing
Learning Efficiency:
- Focus on understanding concepts, not memorizing syntax
- Practice explaining concepts to others (rubber duck debugging)
- Join developer communities for support and networking
- Set up a learning schedule and stick to it
- Don't try to learn everything at once - master fundamentals first
Career Development:
- Build in public - share your learning journey
- Contribute to open source projects
- Attend meetups and conferences (virtual or in-person)
- Keep a learning journal to track progress
- Help others learn - teaching reinforces your own understanding
Remember: Every expert was once a beginner. The JavaScript ecosystem can feel overwhelming, but with consistent practice and the right resources, you'll build the skills needed to create amazing applications.
The journey from beginner to proficient JavaScript developer typically takes 6-12 months of consistent practice. Focus on building real projects, understanding core concepts deeply, and staying current with modern best practices.
Most importantly: Start coding today, keep building, and never stop learning!
Last updated: July 2025 - Reflects ES2025 features, TypeScript 5.5, Node.js 22 LTS, React 19, and current 2025 best practices.