diff --git a/01-node-tutorial/01-intro.md b/01-node-tutorial/01-intro.md new file mode 100644 index 0000000000..b72717ec30 --- /dev/null +++ b/01-node-tutorial/01-intro.md @@ -0,0 +1,215 @@ +## Introduction + +This tutorial guides you through your first Node.js application. Node.js allows you to run JavaScript outside of a web browser, on servers and command-line tools. + +## What is Node.js? + +Node.js is a runtime environment that lets you execute JavaScript code on the server-side. It's built on top of Chrome's V8 JavaScript engine and is perfect for building scalable applications. + +## Your First Node.js App + +Let's examine the code from `01-intro.js`: + +```javascript +const amount = 9 + +if (amount < 10) { + console.log('small number') +} else { + console.log('large number') +} + +console.log(`hey it's my first node app!!!`) +``` + +### Breaking Down the Code + +#### 1. **Variable Declaration** + +```javascript +const amount = 9 +``` + +- **`const`**: Declares a constant variable that cannot be reassigned after initialization +- **`amount`**: The variable name +- **`9`**: The initial value (a number) + +**Key Points:** +- Use `const` by default (it's safer than `let` or `var`) +- Use `let` if you need to reassign the variable +- Avoid `var` in modern JavaScript + +#### 2. **Conditional Logic (if/else statement)** + +```javascript +if (amount < 10) { + console.log('small number') +} else { + console.log('large number') +} +``` + +**What happens here:** +- The `if` statement checks if `amount` is less than 10 +- If the condition is `true`, it executes the code in the first block +- Otherwise (when condition is `false`), it executes the `else` block + +**In this example:** +- Since `amount = 9`, and `9 < 10` is `true` +- The output will be: `small number` + +#### 3. **Template Literals (String Interpolation)** + +```javascript +console.log(`hey it's my first node app!!!`) +``` + +**Key Features:** +- Uses backticks (`` ` ``) instead of quotes +- Allows you to embed variables using `${}` syntax +- Makes multi-line strings easier to work with + +**Example with variable interpolation:** +```javascript +const name = "Node.js" +console.log(`Welcome to ${name}!`) // Output: Welcome to Node.js! +``` + +#### 4. **Console Output** + +```javascript +console.log() +``` + +- Prints messages to the terminal/console +- Essential for debugging and monitoring your application + +## Running the Code + +### Prerequisites +- Node.js installed on your computer +- A terminal or command prompt + +### Steps + +1. **Create a file** named `01-intro.js` with the code above + +2. **Open your terminal** and navigate to the file's directory: + ```bash + cd path/to/your/directory + ``` + +3. **Run the file** using Node.js: + ```bash + node 01-intro.js + ``` + +4. **Expected Output:** + ``` + small number + hey it's my first node app!!! + ``` + +## Key Concepts Learned + +| Concept | Explanation | +|---------|-------------| +| **const** | Declares an immutable variable | +| **if/else** | Conditional statement for decision-making | +| **Comparison Operator (<)** | Checks if a value is less than another | +| **console.log()** | Prints output to the console | +| **Template Literals** | String literals that allow embedded expressions | + +## Practice Exercises + +### Exercise 1: Change the Condition +Modify the code to check if `amount` is greater than 10: + +```javascript +const amount = 9 + +if (amount > 10) { + console.log('large number') +} else { + console.log('small number') +} +``` + +**Question:** What will be the output? + +### Exercise 2: Use Different Operators +Try these comparison operators: +- `>` (greater than) +- `>=` (greater than or equal) +- `<=` (less than or equal) +- `===` (strictly equal) +- `!==` (not equal) + +### Exercise 3: Multiple Conditions with else if +```javascript +const amount = 10 + +if (amount < 10) { + console.log('small number') +} else if (amount === 10) { + console.log('exactly ten') +} else { + console.log('large number') +} +``` + +### Exercise 4: Using Variables in Template Literals +```javascript +const amount = 9 + +if (amount < 10) { + console.log(`${amount} is a small number`) +} else { + console.log(`${amount} is a large number`) +} +``` + +## Next Steps + +Now that you understand the basics, explore: + +1. **Loops** - Repeat code multiple times +2. **Functions** - Create reusable code blocks +3. **Objects & Arrays** - Store and manage data +4. **File System** - Read and write files +5. **Modules** - Import and export code +6. **npm Packages** - Use third-party libraries + +## Common Mistakes to Avoid + +❌ **Using `var` instead of `const`/`let`** +```javascript +var amount = 9 // Avoid this +const amount = 9 // Better +``` + +❌ **Forgetting quotes in console.log()** +```javascript +console.log(hey it's my first node app) // Error! +console.log('hey it\'s my first node app') // Correct +console.log(`hey it's my first node app`) // Best (template literal) +``` + +❌ **Using = instead of === for comparison** +```javascript +if (amount = 10) { } // Wrong! This assigns, not compares +if (amount === 10) { } // Correct! Compares values +``` + +## Summary + +Congratulations! You've created your first Node.js application. You've learned about: +- ✅ Variable declaration with `const` +- ✅ Conditional logic with `if/else` +- ✅ String output with `console.log()` +- ✅ Template literals for dynamic strings + +Continue building on these fundamentals to master Node.js! +``` + +This tutorial provides a comprehensive guide to your first Node.js application, covering the concepts, execution steps, and practical exercises to reinforce learning. diff --git a/01-node-tutorial/02-globals.md b/01-node-tutorial/02-globals.md new file mode 100644 index 0000000000..76f13d5f38 --- /dev/null +++ b/01-node-tutorial/02-globals.md @@ -0,0 +1,151 @@ +## Overview + +In Node.js, there are global objects and variables that are available in every module without needing to import them. Unlike browser JavaScript which has the `window` object, Node.js provides its own set of globals designed for server-side development. + +## Key Node.js Globals + +### 1. **`__dirname`** - Current Directory Path +```javascript +console.log(__dirname) +``` + +**What it does:** Returns the absolute path of the current directory containing the currently executing file. + +**Example output:** +``` +/Users/username/projects/node-express-course/01-node-tutorial +``` + +**Common use cases:** +- Reading files relative to the current directory +- Setting up file paths for asset serving +- Constructing dynamic paths for logging + +**Example:** +```javascript +const path = require('path'); +const filePath = path.join(__dirname, 'data', 'users.json'); +``` + +--- + +### 2. **`__filename`** - Current File Name +**What it does:** Returns the absolute file path of the currently executing file. + +**Example output:** +``` +/Users/username/projects/node-express-course/01-node-tutorial/02-globals.js +``` + +--- + +### 3. **`require()`** - Module System Function +**What it does:** Allows you to import modules using the CommonJS module system. + +**Example:** +```javascript +const express = require('express'); +const fs = require('fs'); +const path = require('path'); +``` + +--- + +### 4. **`module`** - Current Module Information +**What it does:** Contains information about the current module, including exports and other metadata. + +**Example:** +```javascript +console.log(module) +// Shows: { exports: {}, ...other properties } + +module.exports = { name: 'My Module' }; +``` + +--- + +### 5. **`process`** - Environment Information +**What it does:** Provides information about the Node.js process and environment variables. + +**Common properties:** +```javascript +process.env // Environment variables +process.argv // Command-line arguments +process.cwd() // Current working directory +process.exit() // Exit the process +process.version // Node.js version +``` + +**Example:** +```javascript +console.log(process.env.NODE_ENV); // 'development' or 'production' +console.log(process.argv); // Array of command-line arguments +``` + +--- + +## Practical Example: setInterval + +The code also demonstrates **`setInterval()`**, a global function for running code repeatedly: + +```javascript +setInterval(() => { + console.log('hello world') +}, 1000) +``` + +**What it does:** Runs the callback function every 1000 milliseconds (1 second). + +**Output:** +``` +hello world +hello world +hello world +... (repeats indefinitely) +``` + +--- + +## Complete Working Example + +```javascript +// Display current directory and file +console.log('Current directory:', __dirname); +console.log('Current file:', __filename); + +// Run a function every 1 second +let count = 0; +setInterval(() => { + count++; + console.log(`[${count}] hello world`); + + // Stop after 5 iterations + if (count === 5) { + process.exit(0); // Use process.exit() to terminate + } +}, 1000); +``` + +--- + +## Key Differences from Browser JavaScript + +| Browser | Node.js | +|---------|---------| +| `window` global | `global` global | +| `document` available | No DOM | +| Can't access file system directly | `fs` module for file access | +| No `__dirname` or `__filename` | Both available automatically | +| `process` not available | `process` always available | + +--- + +## Summary + +Node.js globals are essential for: +- ✅ Working with file paths (`__dirname`, `__filename`) +- ✅ Managing modules (`require`, `module`) +- ✅ Accessing environment configuration (`process`) +- ✅ Creating timers and intervals (`setInterval`, `setTimeout`) + +These globals make it easy to build server-side applications without needing to import everything explicitly! diff --git a/01-node-tutorial/03-modules.md b/01-node-tutorial/03-modules.md new file mode 100644 index 0000000000..ef0496d8d7 --- /dev/null +++ b/01-node-tutorial/03-modules.md @@ -0,0 +1,233 @@ +# Node.js Modules Tutorial: Understanding CommonJS + +This tutorial explains the Node.js module system using CommonJS, based on the code pattern you provided. We'll explore how modules work, how to share code between files, and the different ways to export and import functionality. + +## Prerequisites +- Node.js installed on your system +- Basic understanding of JavaScript +- A code editor + +## Project Structure +Create a folder for this tutorial with the following files: +``` +node-modules-tutorial/ +├── 04-names.js +├── 05-utils.js +├── 06-alternative-flavor.js +├── 07-mind-grenade.js +└── app.js +``` + +## 1. Understanding Modules in Node.js + +In Node.js, every file is a module by default. This means variables and functions defined in a file are scoped to that module and not automatically available to other files. + +### Key Concepts: +- **Module**: Each JavaScript file is treated as a separate module +- **Encapsulation**: Code within a module is private by default +- **Sharing**: Use `module.exports` or `exports` to share code +- **Importing**: Use `require()` to import code from other modules + +## 2. Creating Modules with Exports + +### File: `04-names.js` +This module demonstrates how to export multiple variables: + +```javascript +// Local variable (not shared) +const secret = 'SUPER SECRET' + +// Variables to share +const john = 'john' +const peter = 'peter' + +module.exports = { john, peter } +``` + +**Explanation:** +- `secret` is a local variable - it's not exported, so it can't be accessed from other files +- `john` and `peter` are exported as an object +- `module.exports` specifies what should be available when this file is required + +### File: `05-utils.js` +This module exports a single function: + +```javascript +const sayHi = (name) => { + console.log(`Hello there ${name}`) +} + +module.exports = sayHi +``` + +**Explanation:** +- A function is defined and then assigned to `module.exports` +- This exports the function directly (not inside an object) + +### File: `06-alternative-flavor.js` +This shows different ways to export values: + +```javascript +// Method 1: Direct export as property +module.exports.items = ['item1', 'item2'] + +// Method 2: Export object +const person = { + name: 'bob', +} + +module.exports.singlePerson = person +``` + +**Explanation:** +- You can add properties to `module.exports` directly +- This demonstrates that you can export multiple things without creating a single export object at the end + +## 3. Module Execution on Import + +### File: `07-mind-grenade.js` +This file demonstrates that code runs when a module is required: + +```javascript +const num1 = 5 +const num2 = 10 + +function addValues() { + console.log(`the sum is : ${num1 + num2}`) +} + +addValues() +``` + +**Explanation:** +- When this module is required, the code executes immediately +- No exports are provided, but the function runs during import +- This is useful for initialization code or setup tasks + +## 4. Bringing It All Together + +### File: `app.js` +This is the main file that imports and uses all modules: + +```javascript +// Import names module +const names = require('./04-names') +// Import sayHi function +const sayHi = require('./05-utils') +// Import alternative flavor module +const data = require('./06-alternative-flavor') +// Import mind grenade (executes code automatically) +require('./07-mind-grenade') + +// Use the imported modules +sayHi('susan') +sayHi(names.john) +sayHi(names.peter) + +// Uncomment to see the data structure +// console.log(data) +``` + +## 5. Running the Application + +To run the application, execute: +```bash +node app.js +``` + +Expected output: +``` +the sum is : 15 (from 07-mind-grenade.js) +Hello there susan +Hello there john +Hello there peter +``` + +## Complete Code Examples + +### 04-names.js +```javascript +// local +const secret = 'SUPER SECRET' +// share +const john = 'john' +const peter = 'peter' + +module.exports = { john, peter } +``` + +### 05-utils.js +```javascript +const sayHi = (name) => { + console.log(`Hello there ${name}`) +} +// export default +module.exports = sayHi +``` + +### 06-alternative-flavor.js +```javascript +module.exports.items = ['item1', 'item2'] +const person = { + name: 'bob', +} + +module.exports.singlePerson = person +``` + +### 07-mind-grenade.js +```javascript +const num1 = 5 +const num2 = 10 + +function addValues() { + console.log(`the sum is : ${num1 + num2}`) +} + +addValues() +``` + +### app.js +```javascript +// CommonJS, every file is module (by default) +// Modules - Encapsulated Code (only share minimum) + +const names = require('./04-names') +const sayHi = require('./05-utils') +const data = require('./06-alternative-flavor') +require('./07-mind-grenade') + +sayHi('susan') +sayHi(names.john) +sayHi(names.peter) +``` + +## Key Takeaways + +1. **Module Encapsulation**: Each file has its own scope - variables aren't shared unless explicitly exported + +2. **Export Patterns**: + - Export objects: `module.exports = { key: value }` + - Export functions: `module.exports = function` + - Add properties: `module.exports.property = value` + +3. **Import Patterns**: + - Use `require('./path')` to import + - The `.js` extension is optional + - Imported modules are cached after first require + +4. **Module Execution**: Code in required modules runs immediately when imported, even without exports + +5. **Destructuring**: You can destructure imports to access specific exports: + ```javascript + const { john, peter } = require('./04-names') + ``` + +## Practice Exercises + +1. Create a new module that exports multiple utility functions +2. Import only specific items using destructuring +3. Create a module that doesn't export anything but performs a setup task +4. Experiment with circular dependencies (and learn why to avoid them) + +This module pattern is fundamental to Node.js development and helps create maintainable, organized applications by separating concerns into different files. diff --git a/01-node-tutorial/08-os-module.md b/01-node-tutorial/08-os-module.md new file mode 100644 index 0000000000..9c0357f1ed --- /dev/null +++ b/01-node-tutorial/08-os-module.md @@ -0,0 +1,161 @@ +# Node.js OS Module Tutorial + +This tutorial will guide you through using the built-in `os` module in Node.js to get information about your operating system. + +## Prerequisites +- Node.js installed on your system +- Basic knowledge of JavaScript + +## Step 1: Setting Up + +Create a new file called `os-demo.js` and start by importing the OS module: + +```javascript +const os = require('os') +``` + +The `os` module is built into Node.js, so you don't need to install any additional packages. + +## Step 2: Getting User Information + +The `os.userInfo()` method returns information about the current system user: + +```javascript +// Get information about the current user +const user = os.userInfo() +console.log('User Information:') +console.log(user) +``` + +This will output something like: +``` +User Information: +{ + uid: 501, + gid: 20, + username: 'yourusername', + homedir: '/Users/yourusername', + shell: '/bin/zsh' +} +``` + +## Step 3: Checking System Uptime + +The `os.uptime()` method returns the system uptime in seconds: + +```javascript +// Get system uptime in seconds +console.log(`The System Uptime is ${os.uptime()} seconds`) +``` + +To make this more readable, you can convert it to hours: + +```javascript +const uptimeSeconds = os.uptime() +const uptimeHours = Math.floor(uptimeSeconds / 3600) +const uptimeMinutes = Math.floor((uptimeSeconds % 3600) / 60) +const remainingSeconds = uptimeSeconds % 60 + +console.log(`System Uptime: ${uptimeHours} hours, ${uptimeMinutes} minutes, ${remainingSeconds} seconds`) +``` + +## Step 4: Getting System Information + +Create an object to store various system information: + +```javascript +// Create an object with system information +const currentOS = { + name: os.type(), // Operating system name + release: os.release(), // Operating system release version + totalMem: os.totalmem(), // Total system memory in bytes + freeMem: os.freemem(), // Free system memory in bytes +} + +console.log('System Information:') +console.log(currentOS) +``` + +## Step 5: Complete Code + +Here's the complete code with all the examples above: + +```javascript +const os = require('os') + +// User information +const user = os.userInfo() +console.log('=== USER INFORMATION ===') +console.log(user) +console.log('\n') + +// System uptime +console.log('=== SYSTEM UPTIME ===') +const uptimeSeconds = os.uptime() +const uptimeHours = Math.floor(uptimeSeconds / 3600) +const uptimeMinutes = Math.floor((uptimeSeconds % 3600) / 60) +const remainingSeconds = uptimeSeconds % 60 + +console.log(`Raw uptime: ${uptimeSeconds} seconds`) +console.log(`Formatted uptime: ${uptimeHours} hours, ${uptimeMinutes} minutes, ${remainingSeconds} seconds`) +console.log('\n') + +// System information +const currentOS = { + name: os.type(), + release: os.release(), + totalMem: `${(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)} GB`, + freeMem: `${(os.freemem() / 1024 / 1024 / 1024).toFixed(2)} GB`, + platform: os.platform(), + architecture: os.arch(), + cpus: os.cpus().length, // Number of CPU cores +} + +console.log('=== SYSTEM INFORMATION ===') +console.log(currentOS) +``` + +## Step 6: Running the Code + +Run your script using Node.js: + +```bash +node os-demo.js +``` + +## Additional OS Module Methods + +Here are some other useful methods from the `os` module you can explore: + +```javascript +// Get network interfaces +console.log('Network Interfaces:', os.networkInterfaces()) + +// Get the hostname +console.log('Hostname:', os.hostname()) + +// Get the platform +console.log('Platform:', os.platform()) + +// Get CPU information +console.log('CPU Info:', os.cpus()) + +// Get the system's temporary directory +console.log('Temp Directory:', os.tmpdir()) +``` + +## Summary + +In this tutorial, you learned how to: +- Import and use the Node.js `os` module +- Get user information with `os.userInfo()` +- Check system uptime with `os.uptime()` +- Gather system information like OS type, release, and memory usage +- Format memory values from bytes to gigabytes +- Explore additional OS module methods + +The `os` module is particularly useful for: +- System monitoring applications +- Creating platform-specific code +- Getting system resources information +- Understanding the environment your Node.js application is running in diff --git a/01-node-tutorial/09-path-module.md b/01-node-tutorial/09-path-module.md new file mode 100644 index 0000000000..81492646df --- /dev/null +++ b/01-node-tutorial/09-path-module.md @@ -0,0 +1,158 @@ +## Node.js Path Module Tutorial + +The path module is a built-in Node.js utility that provides tools for working with file and directory paths. This tutorial will guide you through its basic usage. + +### Prerequisites +- Node.js installed on your system +- Basic knowledge of JavaScript + +### Step 1: Setting Up the Project + +Create a new directory for your project and navigate into it: +```bash +mkdir node-path-tutorial +cd node-path-tutorial +``` + +Create a new file called `path-demo.js`: +```bash +touch path-demo.js +``` + +### Step 2: Importing the Path Module + +Open `path-demo.js` and start by importing the path module: + +```javascript +const path = require('path'); +``` + +The path module is built into Node.js, so no additional installation is needed. + +### Step 3: Understanding Path Separators + +Different operating systems use different path separators: +- Windows uses backslashes (`\`) +- Unix-based systems (Linux, macOS) use forward slashes (`/`) + +The path module handles these differences automatically. Add this code to see your system's path separator: + +```javascript +console.log('Path separator:', path.sep); +// Output on Windows: '\' +// Output on Unix: '/' +``` + +### Step 4: Joining Path Segments + +The `path.join()` method joins multiple path segments into one path. Run this code: + +```javascript +const filePath = path.join('/content/', 'subfolder', 'test.txt'); +console.log('Joined path:', filePath); +// Output: '/content/subfolder/test.txt' (Unix) or '\content\subfolder\test.txt' (Windows) +``` + +Note how `path.join()`: +- Normalizes the path (removes extra slashes) +- Uses the correct separator for your OS +- Creates a valid path string + +### Step 5: Extracting Filename from a Path + +The `path.basename()` method returns the last portion of a path (usually the filename): + +```javascript +const base = path.basename(filePath); +console.log('Base name (filename):', base); +// Output: 'test.txt' +``` + +You can also specify an extension to remove it: +```javascript +const baseWithoutExt = path.basename(filePath, '.txt'); +console.log('Base name without extension:', baseWithoutExt); +// Output: 'test' +``` + +### Step 6: Getting the Absolute Path + +The `path.resolve()` method resolves a sequence of paths into an absolute path: + +```javascript +const absolute = path.resolve(__dirname, 'content', 'subfolder', 'test.txt'); +console.log('Absolute path:', absolute); +// Output: /your/current/directory/content/subfolder/test.txt +``` + +`__dirname` is a global variable in Node.js that gives the absolute path of the current directory. + +### Complete Code Example + +Here's the complete code with additional examples: + +```javascript +const path = require('path'); + +// 1. Get the path separator +console.log('1. Path separator:', path.sep); + +// 2. Join path segments +const filePath = path.join('/content/', 'subfolder', 'test.txt'); +console.log('2. Joined path:', filePath); + +// 3. Get the filename from a path +const base = path.basename(filePath); +console.log('3. Base name:', base); + +// 4. Get filename without extension +const baseWithoutExt = path.basename(filePath, '.txt'); +console.log('4. Base name without extension:', baseWithoutExt); + +// 5. Get the directory name +const dirName = path.dirname(filePath); +console.log('5. Directory name:', dirName); + +// 6. Get the file extension +const extName = path.extname(filePath); +console.log('6. File extension:', extName); + +// 7. Resolve to absolute path +const absolute = path.resolve(__dirname, 'content', 'subfolder', 'test.txt'); +console.log('7. Absolute path:', absolute); + +// 8. Parse a path into its components +const parsed = path.parse(absolute); +console.log('8. Parsed path:', parsed); +``` + +### Running the Code + +Execute the script with Node.js: +```bash +node path-demo.js +``` + +### Common Use Cases for the Path Module + +1. **Building file paths** for reading/writing files +2. **Getting file extensions** for file type validation +3. **Extracting filenames** from full paths +4. **Creating cross-platform compatible paths** in your applications + +### Practice Exercises + +Try these exercises to reinforce your learning: + +1. Create a function that takes a filename and returns its extension +2. Write code to get the parent directory of a given file +3. Build a path to a configuration file in the user's home directory +4. Parse a URL-like path and extract different components + +### Next Steps + +- Read the [official Node.js path module documentation](https://nodejs.org/api/path.html) +- Learn about other useful Node.js core modules like `fs` (file system) +- Practice combining path operations with file system operations + +This tutorial covers the basics of the Node.js path module. It's an essential tool for any Node.js developer, especially when working with file operations and building cross-platform applications. diff --git a/01-node-tutorial/10-fs-sync.md b/01-node-tutorial/10-fs-sync.md new file mode 100644 index 0000000000..094dad64a1 --- /dev/null +++ b/01-node-tutorial/10-fs-sync.md @@ -0,0 +1,235 @@ +# Node.js Synchronous File Operations Tutorial + +This tutorial explains how to use synchronous file operations in Node.js, based on the provided code example. + +## Prerequisites +- Node.js installed on your system +- Basic understanding of JavaScript +- A code editor + +## Step 1: Understanding Synchronous vs Asynchronous Operations + +Synchronous operations in Node.js execute line by line. Each operation must complete before the next one begins. This is also known as "blocking" code because it blocks the execution of subsequent code until the current operation finishes. + +## Step 2: Setting Up the Project + +Create a new directory for your project and navigate into it: + +```bash +mkdir node-sync-tutorial +cd node-sync-tutorial +``` + +## Step 3: Creating the Required Files + +First, let's create a `content` directory and the text files we'll be reading: + +```bash +mkdir content +``` + +Create `first.txt` in the content folder: +``` +Hello, this is the first text file. +``` + +Create `second.txt` in the content folder: +``` +This is the second text file with some content. +``` + +## Step 4: The Synchronous File Operations Code + +Create a new file called `sync-demo.js` with the following code: + +```javascript +// Import the required methods from the fs (file system) module +const { readFileSync, writeFileSync } = require('fs') + +console.log('start') + +// Synchronously read the contents of first.txt +const first = readFileSync('./content/first.txt', 'utf8') +console.log('First file read completed') + +// Synchronously read the contents of second.txt +const second = readFileSync('./content/second.txt', 'utf8') +console.log('Second file read completed') + +// Synchronously write to a new file +writeFileSync( + './content/result-sync.txt', + `Here is the result : ${first}, ${second}`, + { flag: 'a' } // 'a' flag means append to the file if it exists +) +console.log('Write operation completed') + +console.log('done with this task') +console.log('starting the next one') +``` + +## Step 5: Understanding Each Line + +### Importing the Module +```javascript +const { readFileSync, writeFileSync } = require('fs') +``` +This line uses destructuring to import only the synchronous methods we need from Node.js's built-in `fs` (file system) module. + +### Reading Files Synchronously +```javascript +const first = readFileSync('./content/first.txt', 'utf8') +const second = readFileSync('./content/second.txt', 'utf8') +``` +- `readFileSync()` reads the entire contents of a file +- First parameter: the file path +- Second parameter: the encoding (utf8 ensures we get text, not a buffer) +- The function blocks execution until the file is completely read + +### Writing Files Synchronously +```javascript +writeFileSync( + './content/result-sync.txt', + `Here is the result : ${first}, ${second}`, + { flag: 'a' } +) +``` +- `writeFileSync()` writes data to a file +- First parameter: the file path (creates the file if it doesn't exist) +- Second parameter: the content to write (using template literals) +- Third parameter: options object - `{ flag: 'a' }` means append to existing content + +## Step 6: Running the Code + +Execute the program: + +```bash +node sync-demo.js +``` + +Expected output: +``` +start +First file read completed +Second file read completed +Write operation completed +done with this task +starting the next one +``` + +After running, check the `content/result-sync.txt` file - it should contain the combined content from both files. + +## Step 7: Key Concepts Explained + +### The Blocking Nature +Notice how the console logs appear in perfect order. Each operation waits for the previous one: +1. "start" appears immediately +2. Then the first file is read completely +3. Then the second file is read completely +4. Then the write operation happens +5. Only after everything is done, we see "done with this task" + +### The Flag Option +The `{ flag: 'a' }` option is important: +- Without it (or with `{ flag: 'w' }`), the file would be overwritten each time +- With `'a'`, content is appended to the existing file +- Run the program multiple times to see the content grow + +## Step 8: Error Handling + +Synchronous operations can throw errors. Here's how to handle them: + +```javascript +const { readFileSync, writeFileSync } = require('fs') + +console.log('start') + +try { + const first = readFileSync('./content/first.txt', 'utf8') + console.log('First file read completed') + + const second = readFileSync('./content/second.txt', 'utf8') + console.log('Second file read completed') + + writeFileSync( + './content/result-sync.txt', + `Here is the result : ${first}, ${second}\n`, + { flag: 'a' } + ) + console.log('Write operation completed') +} catch (error) { + console.error('An error occurred:', error.message) +} + +console.log('done with this task') +``` + +## Step 9: When to Use Synchronous Operations + +### ✅ Good Use Cases: +- Initialization scripts that run once at startup +- Simple command-line tools +- Learning and debugging +- When you need guaranteed sequential execution + +### ❌ Avoid In: +- Web servers handling multiple requests +- Any application requiring high concurrency +- Performance-critical applications + +## Complete Code Example with Comments + +```javascript +/** + * Node.js Synchronous File Operations Demo + * This demonstrates blocking I/O operations + */ + +// Import only the synchronous methods we need +const { readFileSync, writeFileSync } = require('fs') +const path = require('path') // For better path handling + +console.log('🚀 Application started') + +// Use path.join for cross-platform compatibility +const firstPath = path.join(__dirname, 'content', 'first.txt') +const secondPath = path.join(__dirname, 'content', 'second.txt') +const resultPath = path.join(__dirname, 'content', 'result-sync.txt') + +try { + // Each operation waits for the previous one to complete + console.log('📖 Reading first file...') + const first = readFileSync(firstPath, 'utf8') + console.log('✅ First file read complete') + + console.log('📖 Reading second file...') + const second = readFileSync(secondPath, 'utf8') + console.log('✅ Second file read complete') + + console.log('✍️ Writing combined content...') + writeFileSync( + resultPath, + `Here is the result : ${first}, ${second}\n`, + { flag: 'a' } // Append mode + ) + console.log('✅ Write operation complete') + +} catch (error) { + console.error('❌ Error:', error.message) +} + +console.log('✨ Task completed') +console.log('➡️ Starting next task...') +``` + +## Summary + +This tutorial demonstrated: +- How synchronous file operations work in Node.js +- Reading files with `readFileSync()` +- Writing files with `writeFileSync()` +- The blocking nature of synchronous code +- Error handling with try-catch +- When to use (and avoid) synchronous operations + +The key takeaway is that synchronous operations are simple and predictable but can hurt performance in applications that need to handle multiple operations concurrently. diff --git a/01-node-tutorial/11-fs-async.md b/01-node-tutorial/11-fs-async.md new file mode 100644 index 0000000000..945be4b00b --- /dev/null +++ b/01-node-tutorial/11-fs-async.md @@ -0,0 +1,171 @@ +# Node.js Async Operations Tutorial + +## Understanding Asynchronous Programming in Node.js + +This tutorial explains asynchronous operations in Node.js using the file system (fs) module as an example. We'll analyze the provided code and understand how async operations work. + +## Code Analysis + +```javascript +const { readFile, writeFile } = require('fs') + +console.log('start') +readFile('./content/first.txt', 'utf8', (err, result) => { + if (err) { + console.log(err) + return + } + const first = result + readFile('./content/second.txt', 'utf8', (err, result) => { + if (err) { + console.log(err) + return + } + const second = result + writeFile( + './content/result-async.txt', + `Here is the result : ${first}, ${second}`, + (err, result) => { + if (err) { + console.log(err) + return + } + console.log('done with this task') + } + ) + }) +}) +console.log('starting next task') +``` + +## Key Concepts + +### 1. **Non-blocking I/O** +Node.js uses non-blocking I/O operations, meaning the program doesn't wait for file operations to complete before moving to the next line. + +### 2. **Callback Functions** +Functions passed as arguments to asynchronous operations that execute when the operation completes. + +### 3. **Event Loop** +Node.js's mechanism that handles asynchronous operations and executes callbacks when operations are complete. + +## Step-by-Step Execution + +### Step 1: Program Starts +```javascript +console.log('start') // This executes immediately +// Output: start +``` + +### Step 2: Initiate First Read Operation +```javascript +readFile('./content/first.txt', 'utf8', callback) +// Node.js starts reading the file in the background +// Program continues to next line without waiting +``` + +### Step 3: Continue to Next Task +```javascript +console.log('starting next task') +// This executes immediately after initiating the read +// Output: starting next task +``` + +### Step 4: Event Loop Handles Completed Operations +When the first file is read: +1. The callback executes +2. Second file read initiates +3. When second file is read, its callback executes +4. Write operation initiates +5. When write completes, its callback executes + +### Expected Output Order: +``` +start +starting next task +done with this task +``` + +## Common Async Patterns + +### 1. **Callback Hell** (Current Example) +Nested callbacks become difficult to manage with multiple operations. + +### 2. **Promises** (Better Alternative) +```javascript +const { readFile, writeFile } = require('fs').promises; + +async function processFiles() { + try { + console.log('start'); + const first = await readFile('./content/first.txt', 'utf8'); + const second = await readFile('./content/second.txt', 'utf8'); + await writeFile('./content/result-async.txt', + `Here is the result : ${first}, ${second}`); + console.log('done with this task'); + console.log('starting next task'); + } catch (err) { + console.log(err); + } +} + +processFiles(); +``` + +### 3. **Promise Chaining** +```javascript +const { readFile, writeFile } = require('fs').promises; + +console.log('start'); +readFile('./content/first.txt', 'utf8') + .then(first => { + return readFile('./content/second.txt', 'utf8') + .then(second => ({ first, second })); + }) + .then(({ first, second }) => { + return writeFile('./content/result-async.txt', + `Here is the result : ${first}, ${second}`); + }) + .then(() => { + console.log('done with this task'); + console.log('starting next task'); + }) + .catch(err => console.log(err)); +``` + +## Practice Exercises + +### Exercise 1: Modify the Code +Add error handling for missing files and log appropriate messages. + +### Exercise 2: Create a Promise-based Version +Convert the callback-based code to use Promises with async/await. + +### Exercise 3: Add Multiple Operations +Extend the code to read three files and combine their contents. + +## Key Takeaways + +1. **Async operations don't block** the main thread +2. **Callbacks execute later** when operations complete +3. **Order of execution** isn't linear in async code +4. **Error handling** is crucial in async operations +5. **Modern alternatives** (Promises, async/await) make code more readable + +## Common Pitfalls to Avoid + +1. ❌ Assuming code executes line by line +2. ❌ Forgetting error handling in callbacks +3. ❌ Creating deeply nested callbacks +4. ❌ Not understanding the event loop +5. ❌ Mixing sync and async operations incorrectly + +## Best Practices + +✅ Always handle errors in callbacks +✅ Use Promises or async/await for better readability +✅ Keep callback nesting shallow +✅ Understand the event loop before writing complex async code +✅ Test async code thoroughly with different scenarios + +This foundational understanding of async operations is crucial for Node.js development, as most real-world applications involve asynchronous operations like file I/O, database queries, and API calls. diff --git a/01-node-tutorial/12-http.md b/01-node-tutorial/12-http.md new file mode 100644 index 0000000000..6b3f70f948 --- /dev/null +++ b/01-node-tutorial/12-http.md @@ -0,0 +1,218 @@ +# Node.js HTTP Package Tutorial + +## Introduction to the HTTP Module + +The `http` module is a built-in Node.js module that allows you to create web servers and handle HTTP requests and responses. In this tutorial, we'll explore how to build a simple web server using this core module. + +## Prerequisites +- Node.js installed on your system +- Basic JavaScript knowledge +- A code editor + +## Setting Up Your First HTTP Server + +Let's break down the provided code and understand each part: + +```javascript +const http = require('http') +``` + +This line imports Node.js's built-in `http` module, giving us access to methods for creating HTTP servers and clients. + +## Creating a Server + +```javascript +const server = http.createServer((req, res) => { + // Request handling logic goes here +}) +``` + +The `http.createServer()` method creates an HTTP server object. It takes a callback function that executes whenever a request is made to the server. This callback receives two important objects: + +- **req (Request object)**: Contains information about the incoming request (URL, headers, method, etc.) +- **res (Response object)**: Used to send data back to the client + +## Understanding the Request Object + +The `req.url` property tells us which page the client is trying to access. Based on this, we can serve different content: + +### Home Page Route (/) +```javascript +if (req.url === '/') { + res.end('Welcome to our home page') +} +``` + +When a user visits the root URL (`http://localhost:5000/`), we send back a simple text response. + +### About Page Route (/about) +```javascript +else if (req.url === '/about') { + res.end('Here is our short history') +} +``` + +When a user visits `/about`, we send a different text response. + +### 404 Page - Not Found +```javascript +else { + res.end(` +
We can't seem to find the page you are looking for
+ back home + `) +} +``` + +For any other URL that doesn't match our defined routes, we send back an HTML error page with a link back to the home page. + +## The Response Object Methods + +### `res.end()` +This method signals that the response is complete. It can optionally send data back to the client. Notice we can send: +- Plain text: `res.end('Welcome to our home page')` +- HTML: `res.end('We can't seem to find the page you are looking for
+ back home + `) + } +}) + +// Start the server on port 5000 +server.listen(5000, () => { + console.log('Server is running on http://localhost:5000') +}) +``` + +## Running the Server + +1. Save the code in a file (e.g., `server.js`) +2. Open terminal in the file directory +3. Run: `node server.js` +4. Open browser and visit: + - `http://localhost:5000/` - Shows home page + - `http://localhost:5000/about` - Shows about page + - `http://localhost:5000/anything-else` - Shows 404 page + +## Key Concepts Explained + +### 1. **HTTP Methods** +While our example doesn't check for HTTP methods, the `req.method` property would show if it's a GET, POST, PUT, etc. request. + +### 2. **MIME Types** +When sending different types of content, you should set the appropriate `Content-Type` header: +```javascript +res.setHeader('Content-Type', 'text/html') +res.setHeader('Content-Type', 'text/plain') +``` + +### 3. **Status Codes** +You can set HTTP status codes to indicate the result of the request: +```javascript +res.statusCode = 404 // Page not found +res.statusCode = 200 // OK (this is default) +``` + +### 4. **Improved Version with Headers** + +Here's an enhanced version of the same server with proper headers: + +```javascript +const http = require('http') + +const server = http.createServer((req, res) => { + + if (req.url === '/') { + res.writeHead(200, { 'Content-Type': 'text/plain' }) + res.end('Welcome to our home page') + } + + else if (req.url === '/about') { + res.writeHead(200, { 'Content-Type': 'text/plain' }) + res.end('Here is our short history') + } + + else { + res.writeHead(404, { 'Content-Type': 'text/html' }) + res.end(` +We can't seem to find the page you are looking for
+ back home + `) + } +}) + +server.listen(5000, () => { + console.log('Server running at http://localhost:5000/') +}) +``` + +## Common HTTP Status Codes + +- **200 OK**: Request succeeded +- **201 Created**: Resource created successfully +- **301 Moved Permanently**: Resource has moved +- **400 Bad Request**: Server couldn't understand the request +- **404 Not Found**: Resource doesn't exist +- **500 Internal Server Error**: Server error + +## Practice Exercises + +1. **Add a contact page** at `/contact` that returns "Contact us at email@example.com" +2. **Add JSON response**: Create an `/api` route that returns a JSON object with some data +3. **Add query parameters**: Parse `req.url` to handle query strings like `?name=John` +4. **Serve HTML files**: Instead of writing HTML in strings, read actual HTML files from disk + +## Limitations and Next Steps + +This basic HTTP server is great for learning, but for production applications, you might want to explore: + +- **Express.js** - A more feature-rich framework for building web applications +- **Routing libraries** - For handling complex routing requirements +- **Middleware** - For handling common tasks like logging, authentication, etc. +- **Template engines** - For dynamic HTML rendering + +## Summary + +You've learned: +- How to create an HTTP server with Node.js +- How to handle different routes (URLs) +- How to send different types of responses +- How to set status codes and headers +- The basics of request/response cycle + +This foundation with the `http` module will help you understand how web servers work under the hood before moving to higher-level frameworks. diff --git a/01-node-tutorial/13-event-emitter.md b/01-node-tutorial/13-event-emitter.md new file mode 100644 index 0000000000..b35143661a --- /dev/null +++ b/01-node-tutorial/13-event-emitter.md @@ -0,0 +1,260 @@ +# Node.js Event Emitter Tutorial + +The Event Emitter is a core module in Node.js that allows objects to communicate with each other through events. It's a fundamental pattern in Node.js that many built-in modules use. Let's explore it step by step. + +## What is an Event Emitter? + +Think of an event emitter like a radio station: +- The station **emits** (broadcasts) signals +- Radios that are tuned in **listen** (subscribe) to those signals +- When the signal is received, the radio **responds** by playing music + +In Node.js, we have the same concept: +- An event emitter object **emits** named events +- Listener functions **subscribe** to these events +- When an event occurs, all subscribed listeners are **called** + +## Getting Started + +First, let's import the EventEmitter class from Node.js's built-in `events` module: + +```javascript +const EventEmitter = require('events') +``` + +## Creating an Event Emitter Instance + +To use events, we need to create an instance of EventEmitter: + +```javascript +const customEmitter = new EventEmitter() +``` + +## The Two Main Methods: `on()` and `emit()` + +The two most important methods are: +- **`on()`** - Used to listen for (subscribe to) an event +- **`emit()`** - Used to trigger (broadcast) an event + +## Basic Example + +Here's our working example that demonstrates the core concepts: + +```javascript +const EventEmitter = require('events') + +const customEmitter = new EventEmitter() + +// Listen for the 'response' event +customEmitter.on('response', (name, id) => { + console.log(`data received user ${name} with id:${id}`) +}) + +// Another listener for the same event +customEmitter.on('response', () => { + console.log('some other logic here') +}) + +// Emit the 'response' event with data +customEmitter.emit('response', 'john', 34) +``` + +When you run this code, you'll see: +``` +data received user john with id:34 +some other logic here +``` + +## Key Concepts Explained + +### 1. Order Matters +The order in which you set up your listeners (`on`) and emit events (`emit`) is crucial: + +```javascript +// This works - listeners are set up before emitting +customEmitter.on('event', () => console.log('Listener 1')) +customEmitter.on('event', () => console.log('Listener 2')) +customEmitter.emit('event') // Both listeners will fire + +// This won't work - emitting before listening +customEmitter.emit('event') // No listeners yet! +customEmitter.on('event', () => console.log('Too late!')) // This never runs +``` + +### 2. Passing Additional Arguments +You can pass any number of arguments from `emit()` to your listeners: + +```javascript +customEmitter.on('userLogin', (username, timestamp, ip, userAgent) => { + console.log(`${username} logged in at ${timestamp}`) + console.log(`IP: ${ip}, Browser: ${userAgent}`) +}) + +customEmitter.emit( + 'userLogin', + 'alice', + new Date().toISOString(), + '192.168.1.100', + 'Mozilla/5.0...' +) +``` + +### 3. Multiple Listeners +You can attach multiple listeners to the same event. They will execute in the order they were registered: + +```javascript +customEmitter.on('data', () => console.log('Logging data access')) +customEmitter.on('data', () => console.log('Validating data')) +customEmitter.on('data', () => console.log('Processing data')) + +customEmitter.emit('data') +// Output: +// Logging data access +// Validating data +// Processing data +``` + +## Built-in Modules Use Event Emitter + +Many Node.js built-in modules extend EventEmitter. Here's a practical example with an HTTP server: + +```javascript +const http = require('http') + +const server = http.createServer() + +// The server object is an EventEmitter! +server.on('request', (req, res) => { + console.log('Request received!') + res.end('Hello World') +}) + +server.on('listening', () => { + console.log('Server is listening on port 3000') +}) + +server.on('close', () => { + console.log('Server closed') +}) + +server.listen(3000) +``` + +Another example with streams: + +```javascript +const fs = require('fs') + +const readStream = fs.createReadStream('./file.txt') + +// Streams are EventEmitters too! +readStream.on('open', () => { + console.log('File opened') +}) + +readStream.on('data', (chunk) => { + console.log(`Received ${chunk.length} bytes of data`) +}) + +readStream.on('end', () => { + console.log('Finished reading file') +}) + +readStream.on('error', (err) => { + console.error('Error:', err.message) +}) +``` + +## Additional Useful Methods + +### `once()` - Listen for an event only once +```javascript +customEmitter.once('oneTime', () => { + console.log('This will only run once!') +}) + +customEmitter.emit('oneTime') // Runs +customEmitter.emit('oneTime') // Does nothing +``` + +### `off()` or `removeListener()` - Stop listening +```javascript +function handler() { + console.log('Event handled') +} + +customEmitter.on('event', handler) + +// Later, remove the listener +customEmitter.off('event', handler) +// or +customEmitter.removeListener('event', handler) +``` + +### `removeAllListeners()` - Remove all listeners for an event +```javascript +customEmitter.removeAllListeners('response') +``` + +## Common Patterns + +### 1. Creating Custom Classes with EventEmitter +```javascript +const EventEmitter = require('events') + +class User extends EventEmitter { + constructor(name) { + super() + this.name = name + } + + login() { + console.log(`${this.name} logged in`) + this.emit('login', this.name, Date.now()) + } + + logout() { + console.log(`${this.name} logged out`) + this.emit('logout', this.name) + } +} + +const user = new User('Alice') + +user.on('login', (name, time) => { + console.log(`Event: ${name} logged in at ${time}`) +}) + +user.on('logout', (name) => { + console.log(`Event: ${name} logged out`) +}) + +user.login() +// Wait 1 second, then logout +setTimeout(() => user.logout(), 1000) +``` + +### 2. Error Handling +It's a good practice to always listen for 'error' events: + +```javascript +customEmitter.on('error', (err) => { + console.error('Something went wrong:', err.message) +}) + +// If you emit an error without listening, Node.js will throw +customEmitter.emit('error', new Error('Database connection failed')) +``` + +## Summary + +Key takeaways: +1. **`on()`** subscribes to events, **`emit()`** publishes them +2. Listeners execute in the order they were registered +3. You can pass multiple arguments when emitting events +4. Many built-in modules (HTTP servers, streams, etc.) use EventEmitter +5. Always set up listeners before emitting events +6. Use `once()` for one-time listeners +7. Always handle error events to prevent crashes + +Event Emitters are a powerful pattern for creating decoupled, event-driven architectures in Node.js applications! diff --git a/01-node-tutorial/15-create-big-file.md b/01-node-tutorial/15-create-big-file.md new file mode 100644 index 0000000000..c450d98af0 --- /dev/null +++ b/01-node-tutorial/15-create-big-file.md @@ -0,0 +1,410 @@ +# Node.js File Writing Tutorial: Creating Files (Especially Large Ones) & Best Practices + +## Introduction + +Working with files is a fundamental task in Node.js applications. Whether you're logging data, generating reports, or creating backups, understanding how to efficiently write files—particularly large ones—is crucial for building performant applications. + +In this tutorial, we'll analyze a common approach to file writing, discuss its limitations, and explore best practices for creating files of all sizes. + +## The Starting Code + +```javascript +const { writeFileSync } = require('fs') + +for (let i = 0; i < 10000; i++) { + writeFileSync('./content/big.txt', `hello world ${i}\n`, { flag: 'a' }) +} +``` + +This code creates a file with 10,000 lines, each containing "hello world" followed by a line number. While this works, it has several issues that become apparent when working with larger files. + +## Understanding the Code + +### What's Happening: + +1. **`writeFileSync`** - The synchronous version of `writeFile` +2. **Loop 10,000 times** - Each iteration writes one line +3. **`{ flag: 'a' }`** - Opens file in append mode (adds to existing content) + +### The Problems: + +- **Performance**: Opening and closing the file 10,000 times +- **Blocking**: Synchronous operations block the event loop +- **Memory**: Not an issue here, but with different approaches could be +- **Error Handling**: None implemented + +## Let's Analyze the Performance + +Let's create a benchmark to see how this code performs: + +```javascript +// benchmark-sync.js +const { writeFileSync } = require('fs'); +const { performance } = require('perf_hooks'); + +const start = performance.now(); + +try { + for (let i = 0; i < 10000; i++) { + writeFileSync('./content/big.txt', `hello world ${i}\n`, { flag: 'a' }); + } + + const end = performance.now(); + console.log(`Time taken: ${(end - start).toFixed(2)}ms`); +} catch (error) { + console.error('Error:', error.message); +} +``` + +Run this with `node benchmark-sync.js` - you'll notice it's surprisingly slow for such a simple operation! + +## Better Approaches for File Writing + +### 1. Using Write Streams (Best for Large Files) + +```javascript +// stream-write.js +const { createWriteStream } = require('fs'); + +const start = performance.now(); +const stream = createWriteStream('./content/big.txt'); + +for (let i = 0; i < 10000; i++) { + stream.write(`hello world ${i}\n`); +} + +stream.end(); + +stream.on('finish', () => { + const end = performance.now(); + console.log(`Stream finished in ${(end - start).toFixed(2)}ms`); +}); + +stream.on('error', (error) => { + console.error('Stream error:', error); +}); +``` + +**Why this is better:** +- Opens the file once +- Buffers writes internally +- Non-blocking (asynchronous) +- Much faster for large files + +### 2. Building Content in Memory (Good for Small to Medium Files) + +```javascript +// memory-build.js +const { writeFile } = require('fs').promises; +const { performance } = require('perf_hooks'); + +async function writeFileEfficiently() { + const start = performance.now(); + + try { + // Build content in memory first + let content = ''; + for (let i = 0; i < 10000; i++) { + content += `hello world ${i}\n`; + } + + // Write once + await writeFile('./content/big.txt', content); + + const end = performance.now(); + console.log(`Time taken: ${(end - start).toFixed(2)}ms`); + } catch (error) { + console.error('Error:', error.message); + } +} + +writeFileEfficiently(); +``` + +**Pros**: Simple, single write operation +**Cons**: Memory intensive for very large files + +### 3. Batch Writing with Append (Compromise Approach) + +```javascript +// batch-write.js +const { writeFile } = require('fs').promises; +const { performance } = require('perf_hooks'); + +async function writeInBatches() { + const start = performance.now(); + const batchSize = 1000; + const totalLines = 10000; + + try { + for (let i = 0; i < totalLines; i += batchSize) { + let batch = ''; + const end = Math.min(i + batchSize, totalLines); + + for (let j = i; j < end; j++) { + batch += `hello world ${j}\n`; + } + + await writeFile('./content/big.txt', batch, { + flag: i === 0 ? 'w' : 'a' // 'w' for first batch, 'a' for subsequent + }); + } + + const end = performance.now(); + console.log(`Time taken: ${(end - start).toFixed(2)}ms`); + } catch (error) { + console.error('Error:', error.message); + } +} + +writeInBatches(); +``` + +## Best Practices for File Writing + +### 1. Always Handle Errors + +```javascript +const { createWriteStream } = require('fs'); + +function safeFileWrite(filename, data) { + return new Promise((resolve, reject) => { + const stream = createWriteStream(filename); + + stream.write(data); + stream.end(); + + stream.on('finish', resolve); + stream.on('error', reject); + }); +} + +// Usage with async/await +async function writeSafely() { + try { + await safeFileWrite('./content/safe.txt', 'Hello World\n'); + console.log('File written successfully'); + } catch (error) { + console.error('Failed to write file:', error.message); + } +} +``` + +### 2. Consider Memory Usage + +For extremely large files (GBs), never load everything into memory: + +```javascript +// large-file-generator.js +const { createWriteStream } = require('fs'); + +function generateLargeFile(filename, totalLines) { + return new Promise((resolve, reject) => { + const stream = createWriteStream(filename); + + let i = 0; + + function writeNext() { + let ok = true; + + while (i < totalLines && ok) { + // Write one line at a time but let the stream buffer + ok = stream.write(`Line ${i}: ${'x'.repeat(100)}\n`); + i++; + + // If buffer is full, wait for drain event + if (!ok) { + stream.once('drain', writeNext); + return; + } + } + + if (i >= totalLines) { + stream.end(); + resolve(); + } + } + + writeNext(); + + stream.on('error', reject); + }); +} + +// Generate a 1GB file (approximately) +generateLargeFile('./content/1gb-file.txt', 10_000_000) + .then(() => console.log('File generated successfully')) + .catch(console.error); +``` + +### 3. Use the Promises API for Better Code Organization + +```javascript +// promises-example.js +const fs = require('fs').promises; + +async function writeWithPromises() { + try { + // Check if directory exists + try { + await fs.access('./content'); + } catch { + await fs.mkdir('./content'); + } + + // Write file + await fs.writeFile('./content/example.txt', 'Hello World\n'); + + // Append to file + await fs.appendFile('./content/example.txt', 'Another line\n'); + + // Read back the file + const data = await fs.readFile('./content/example.txt', 'utf8'); + console.log('File contents:', data); + + } catch (error) { + console.error('Operation failed:', error); + } +} +``` + +### 4. Progress Tracking for Long Operations + +```javascript +// progress-tracker.js +const { createWriteStream } = require('fs'); + +async function writeWithProgress(filename, totalLines) { + const stream = createWriteStream(filename); + const startTime = Date.now(); + + return new Promise((resolve, reject) => { + let written = 0; + + function writeNext() { + while (written < totalLines) { + const line = `Progress line ${written}\n`; + const shouldContinue = stream.write(line); + + written++; + + // Report progress every 10% + if (written % Math.floor(totalLines / 10) === 0) { + const percent = Math.round((written / totalLines) * 100); + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); + console.log(`Progress: ${percent}% (${elapsed}s elapsed)`); + } + + if (!shouldContinue) { + stream.once('drain', writeNext); + return; + } + } + + stream.end(); + const totalTime = ((Date.now() - startTime) / 1000).toFixed(1); + console.log(`Complete! Total time: ${totalTime}s`); + resolve(); + } + + writeNext(); + stream.on('error', reject); + }); +} + +// Write 1 million lines with progress tracking +writeWithProgress('./content/progress.txt', 1_000_000) + .catch(console.error); +``` + +## Performance Comparison + +Let's create a comprehensive benchmark to compare all methods: + +```javascript +// benchmark-all.js +const { writeFileSync } = require('fs'); +const { writeFile } = require('fs').promises; +const { createWriteStream } = require('fs'); +const { performance } = require('perf_hooks'); + +const LINES = 100_000; +const FILE = './content/benchmark.txt'; + +async function runBenchmarks() { + console.log(`Writing ${LINES.toLocaleString()} lines...\n`); + + // Method 1: Original sync with append + console.log('1. Original Sync Method:'); + const start1 = performance.now(); + for (let i = 0; i < LINES; i++) { + writeFileSync(FILE, `line ${i}\n`, { flag: 'a' }); + } + const time1 = performance.now() - start1; + console.log(` Time: ${time1.toFixed(2)}ms\n`); + + // Method 2: Build in memory then write + console.log('2. Memory Build Method:'); + const start2 = performance.now(); + let content = ''; + for (let i = 0; i < LINES; i++) { + content += `line ${i}\n`; + } + await writeFile(FILE, content); + const time2 = performance.now() - start2; + console.log(` Time: ${time2.toFixed(2)}ms\n`); + + // Method 3: Stream Method + console.log('3. Stream Method:'); + const start3 = performance.now(); + await new Promise((resolve, reject) => { + const stream = createWriteStream(FILE); + for (let i = 0; i < LINES; i++) { + stream.write(`line ${i}\n`); + } + stream.end(); + stream.on('finish', resolve); + stream.on('error', reject); + }); + const time3 = performance.now() - start3; + console.log(` Time: ${time3.toFixed(2)}ms\n`); + + // Results + console.log('=== Summary ==='); + console.log(`Sync (append): ${time1.toFixed(2)}ms`); + console.log(`Memory Build: ${time2.toFixed(2)}ms`); + console.log(`Stream: ${time3.toFixed(2)}ms`); + + const fastest = Math.min(time1, time2, time3); + console.log(`\nFastest method is ${fastest === time1 ? 'Sync' : fastest === time2 ? 'Memory' : 'Stream'} (${fastest.toFixed(2)}ms)`); +} + +// Run the benchmark +runBenchmarks().catch(console.error); +``` + +## Key Takeaways + +1. **Never use `writeFileSync` in a loop** for large files - it's extremely inefficient +2. **Use streams** for large files to manage memory efficiently +3. **Use the promises API** for cleaner, more maintainable code +4. **Always implement error handling** - file operations can fail for many reasons +5. **Consider your use case**: + - Small files (< 50MB): Building in memory is simple and fast + - Large files: Use streams with proper backpressure handling + - Very large files (> 1GB): Implement progress tracking and consider chunking + +## When to Use Each Approach + +| File Size | Recommended Method | Why | +|-----------|-------------------|-----| +| < 50MB | Build in memory + writeFile | Simple, fast, readable | +| 50MB - 500MB | Write stream | Good balance of speed and memory | +| > 500MB | Stream with backpressure | Prevents memory issues | +| Unknown size | Stream | Most flexible approach | + +## Conclusion + +The original code with `writeFileSync` in a loop is a common beginner pattern, but it's not suitable for production use, especially with larger files. By understanding the alternatives and best practices outlined in this tutorial, you can write more efficient, scalable, and robust file operations in your Node.js applications. + +Remember: The right approach depends on your specific use case, file sizes, and performance requirements. Always benchmark with realistic data sizes to make informed decisions! diff --git a/01-node-tutorial/16-streams.md b/01-node-tutorial/16-streams.md new file mode 100644 index 0000000000..3c174ef1d6 --- /dev/null +++ b/01-node-tutorial/16-streams.md @@ -0,0 +1,177 @@ +# Node.js Streams Tutorial: Understanding Readable Streams + +In this tutorial, we'll explore Node.js readable streams using the `fs.createReadStream()` method. Streams are a fundamental concept in Node.js that allow you to handle data efficiently, especially when working with large files. + +## Prerequisites +- Basic knowledge of Node.js +- Node.js installed on your system + +## What are Streams? + +Streams are objects that let you read data from a source or write data to a destination in continuous fashion. Instead of reading a file entirely into memory, streams process data in chunks, making them memory-efficient and ideal for handling large files. + +## The Code Explained + +Let's break down the example code: + +```javascript +const { createReadStream } = require('fs') + +// Create a readable stream from a file +const stream = createReadStream('./content/big.txt') + +// Listen for data events +stream.on('data', (result) => { + console.log(result) +}) + +// Handle errors +stream.on('error', (err) => console.log(err)) +``` + +### How It Works + +1. **Import the `createReadStream` method** from the built-in `fs` (file system) module. + +2. **Create a readable stream** by calling `createReadStream()` with the path to your file. + +3. **Listen for the 'data' event** - this event is emitted whenever a new chunk of data is available from the stream. + +4. **Handle errors** by listening for the 'error' event. + +## Understanding Buffer Size + +By default, `createReadStream()` reads files in chunks of **64 kilobytes**. This is controlled by the `highWaterMark` option. + +```javascript +// Read in 90KB chunks instead of the default 64KB +const stream = createReadStream('./content/big.txt', { + highWaterMark: 90000 +}) +``` + +The last chunk will be smaller than the specified size (the remainder of the file). + +## Working with Text Encoding + +By default, the stream returns data as buffers. To get strings instead, specify an encoding: + +```javascript +const stream = createReadStream('./content/big.txt', { + encoding: 'utf8' +}) +``` + +## Complete Example with All Options + +```javascript +const { createReadStream } = require('fs') +const path = require('path') + +const filePath = path.join(__dirname, './content/big.txt') + +const stream = createReadStream(filePath, { + highWaterMark: 90000, // Read 90KB chunks + encoding: 'utf8' // Return strings instead of buffers +}) + +let chunkCount = 0 + +stream.on('data', (chunk) => { + chunkCount++ + console.log(`Received chunk ${chunkCount}: ${chunk.length} characters`) + // console.log(chunk) // Uncomment to see the actual data +}) + +stream.on('end', () => { + console.log(`Finished reading file. Total chunks: ${chunkCount}`) +}) + +stream.on('error', (err) => { + console.error('Error reading file:', err) +}) +``` + +## Creating a Test File + +To test the stream with a larger file, create one first: + +```javascript +const { writeFileSync } = require('fs') + +// Create a 1MB test file +for (let i = 0; i < 10000; i++) { + writeFileSync('./content/big.txt', `Line ${i}: Hello world\n`, { flag: 'a' }) +} +``` + +## Key Concepts + +### 1. **Events** +- `data` - emitted when a new chunk is available +- `end` - emitted when all data has been read +- `error` - emitted when an error occurs +- `close` - emitted when the stream is closed + +### 2. **Stream States** +- **Paused Mode** - Stream doesn't emit 'data' events +- **Flowing Mode** - Stream actively emits 'data' events (default with 'data' listener) + +### 3. **Benefits of Streams** +- **Memory Efficiency**: Process files without loading them entirely into memory +- **Time Efficiency**: Start processing data immediately instead of waiting for the whole file +- **Composability**: Pipe streams together for powerful data processing + +## Practical Example: File Copy with Streams + +```javascript +const { createReadStream, createWriteStream } = require('fs') + +const readStream = createReadStream('./content/big.txt', { encoding: 'utf8' }) +const writeStream = createWriteStream('./content/copy.txt') + +// Pipe data from read stream to write stream +readStream.pipe(writeStream) + +readStream.on('end', () => { + console.log('File copied successfully!') +}) + +readStream.on('error', (err) => { + console.error('Error:', err) +}) +``` + +## Common Use Cases for Streams + +1. **Reading large files** (logs, videos, databases) +2. **Network communications** (HTTP requests/responses) +3. **Data compression** (zlib streams) +4. **File uploads/downloads** +5. **Real-time data processing** + +## Error Handling Best Practices + +Always handle stream errors to prevent crashes: + +```javascript +const stream = createReadStream('./nonexistent-file.txt') + +stream.on('error', (err) => { + console.error('Stream error:', err.message) + // Perform cleanup or fallback operations +}) +``` + +## Conclusion + +Streams are a powerful feature in Node.js that enable efficient data handling. By processing data in chunks rather than loading entire files into memory, you can build applications that handle large files gracefully and perform well under memory constraints. + +## Practice Exercises + +1. Try modifying the `highWaterMark` value and observe how it affects chunk sizes +2. Create a large CSV file and process it line by line using streams +3. Build a simple HTTP server that streams a video file to clients +4. Implement a file upload endpoint using streams + +Remember: Streams are everywhere in Node.js - understanding them is key to building scalable applications! diff --git a/01-node-tutorial/17-http-stream.md b/01-node-tutorial/17-http-stream.md new file mode 100644 index 0000000000..228df35e4f --- /dev/null +++ b/01-node-tutorial/17-http-stream.md @@ -0,0 +1,273 @@ +# Node.js HTTP Streams Tutorial + +## Understanding Streams in HTTP Responses + +This tutorial explores how to use Node.js streams to efficiently serve large files over HTTP, comparing the traditional approach with stream-based solutions. + +## The Code Explained + +```javascript +var http = require('http') +var fs = require('fs') + +http + .createServer(function (req, res) { + // Traditional approach - loading entire file into memory + // const text = fs.readFileSync('./content/big.txt', 'utf8') + // res.end(text) + + // Stream-based approach - chunks of data + const fileStream = fs.createReadStream('./content/big.txt', 'utf8') + fileStream.on('open', () => { + fileStream.pipe(res) + }) + fileStream.on('error', (err) => { + res.end(err) + }) + }) + .listen(5000) +``` + +## Key Concepts + +### 1. **What are Streams?** +Streams are objects that let you read data from a source or write data to a destination continuously. Instead of reading a file entirely into memory, streams process data in chunks. + +### 2. **Why Use Streams?** +- **Memory Efficiency**: Process large files without loading them entirely into RAM +- **Time Efficiency**: Start processing data immediately without waiting for the entire payload +- **Better User Experience**: Clients receive data progressively + +## Comparison: Without Streams vs With Streams + +### Without Streams (Traditional Approach) +```javascript +// This loads the ENTIRE file into memory before sending +const text = fs.readFileSync('./content/big.txt', 'utf8') +res.end(text) +``` +**Problems:** +- High memory usage for large files +- Client waits for complete file read before receiving anything +- Can crash your server with very large files + +### With Streams (Our Example) +```javascript +// This reads and sends data in CHUNKS +const fileStream = fs.createReadStream('./content/big.txt', 'utf8') +fileStream.pipe(res) +``` +**Benefits:** +- Low memory footprint +- Client receives data as it's being read +- Handles files of any size + +## Step-by-Step Breakdown + +### 1. **Creating the Server** +```javascript +http.createServer(function (req, res) { + // Request handling logic +}).listen(5000) +``` +Creates an HTTP server listening on port 5000. + +### 2. **Creating a Read Stream** +```javascript +const fileStream = fs.createReadStream('./content/big.txt', 'utf8') +``` +- Opens the file as a readable stream +- `'utf8'` encoding ensures we get text data +- File is read in chunks (default: 64KB) + +### 3. **The Magic of `.pipe()`** +```javascript +fileStream.on('open', () => { + fileStream.pipe(res) +}) +``` +- `pipe()` automatically handles: + - Reading data chunks from the file + - Writing chunks to the HTTP response + - Backpressure management + - Ending the response when done + +### 4. **Error Handling** +```javascript +fileStream.on('error', (err) => { + res.end(err) +}) +``` +Catches and handles any stream errors (like file not found). + +## Practical Examples + +### Example 1: Creating a Test File +First, create a large text file to test with: + +```javascript +// create-big-file.js +const fs = require('fs') +const file = fs.createWriteStream('./content/big.txt') + +for(let i = 0; i <= 10000; i++) { + file.write('Lorem ipsum dolor sit amet, consectetur adipiscing elit. ') +} +file.end() +``` + +### Example 2: Monitoring Memory Usage +Compare memory usage between approaches: + +```javascript +// memory-test.js +const http = require('http') +const fs = require('fs') + +http.createServer((req, res) => { + if (req.url === '/stream') { + // Stream approach + console.log('Memory before stream:', process.memoryUsage().rss / 1024 / 1024, 'MB') + const stream = fs.createReadStream('./content/big.txt') + stream.pipe(res) + stream.on('end', () => { + console.log('Memory after stream:', process.memoryUsage().rss / 1024 / 1024, 'MB') + }) + } else if (req.url === '/nostream') { + // Traditional approach + console.log('Memory before read:', process.memoryUsage().rss / 1024 / 1024, 'MB') + fs.readFile('./content/big.txt', (err, data) => { + res.end(data) + console.log('Memory after read:', process.memoryUsage().rss / 1024 / 1024, 'MB') + }) + } +}).listen(5000) +``` + +### Example 3: Adding Progress Indicator +```javascript +const http = require('http') +const fs = require('fs') + +http.createServer((req, res) => { + const fileStream = fs.createReadStream('./content/big.txt', 'utf8') + let bytesRead = 0 + + fileStream.on('data', (chunk) => { + bytesRead += chunk.length + console.log(`Read ${bytesRead} bytes so far...`) + }) + + fileStream.on('open', () => { + fileStream.pipe(res) + }) + + fileStream.on('end', () => { + console.log(`Completed! Total: ${bytesRead} bytes`) + }) + + fileStream.on('error', (err) => { + console.error('Stream error:', err) + res.statusCode = 500 + res.end('File not found') + }) +}).listen(5000) +``` + +## Testing Your Server + +1. **Start the server:** +```bash +node server.js +``` + +2. **Test in browser:** +``` +http://localhost:5000 +``` + +3. **Test with curl:** +```bash +curl http://localhost:5000 +``` + +4. **Monitor memory usage:** +```bash +# In another terminal while server is running +top -p $(pgrep node) +``` + +## Advanced Stream Techniques + +### 1. **Transforming Data on the Fly** +```javascript +const { Transform } = require('stream') + +const upperCaseTransform = new Transform({ + transform(chunk, encoding, callback) { + this.push(chunk.toString().toUpperCase()) + callback() + } +}) + +const fileStream = fs.createReadStream('./content/big.txt') +fileStream + .pipe(upperCaseTransform) + .pipe(res) +``` + +### 2. **Handling Multiple File Types** +```javascript +const path = require('path') + +http.createServer((req, res) => { + const filePath = './content' + req.url + const extname = path.extname(filePath) + + // Set appropriate content type + const contentTypes = { + '.txt': 'text/plain', + '.html': 'text/html', + '.json': 'application/json' + } + + res.setHeader('Content-Type', contentTypes[extname] || 'application/octet-stream') + + const stream = fs.createReadStream(filePath) + stream.pipe(res) + + stream.on('error', () => { + res.statusCode = 404 + res.end('File not found') + }) +}).listen(5000) +``` + +## Best Practices + +1. **Always handle stream errors** to prevent server crashes +2. **Set appropriate MIME types** for different file types +3. **Consider compression** for text files: +```javascript +const zlib = require('zlib') +fileStream.pipe(zlib.createGzip()).pipe(res) +``` + +4. **Implement range requests** for video/audio streaming +5. **Use `pipe()`** instead of manual event handling when possible + +## Common Pitfalls to Avoid + +❌ **Don't forget error handling** - Unhandled stream errors crash the server +❌ **Avoid mixing streams with synchronous operations** - This defeats the purpose +❌ **Don't modify the response after piping** - The stream controls the response + +## Summary + +Streams in Node.js are powerful tools for handling data efficiently. By using `fs.createReadStream()` and `.pipe()`, you can: +- Serve large files without memory issues +- Provide faster response times to clients +- Build scalable applications +- Handle multiple concurrent requests efficiently + +The example code demonstrates the elegant simplicity of streams: just a few lines of code transform your server from memory-intensive to highly efficient! diff --git a/1-node-tutorial/01-intro.js b/1-node-tutorial/01-intro.js new file mode 100644 index 0000000000..8545876fc1 --- /dev/null +++ b/1-node-tutorial/01-intro.js @@ -0,0 +1,9 @@ +const amount = 9 + +if (amount < 10) { + console.log('small number') +} else { + console.log('large number') +} + +console.log(`hey it's my first node app!!!`) diff --git a/1-node-tutorial/02-globals.js b/1-node-tutorial/02-globals.js new file mode 100644 index 0000000000..dea40dad42 --- /dev/null +++ b/1-node-tutorial/02-globals.js @@ -0,0 +1,12 @@ +// GLOBALS - NO WINDOW !!!! + +// __dirname - path to current directory +// __filename - file name +// require - function to use modules (CommonJS) +// module - info about current module (file) +// process - info about env where the program is being executed + +console.log(__dirname) +setInterval(() => { + console.log('hello world') +}, 1000) diff --git a/1-node-tutorial/03-modules.js b/1-node-tutorial/03-modules.js new file mode 100644 index 0000000000..83e8971a32 --- /dev/null +++ b/1-node-tutorial/03-modules.js @@ -0,0 +1,9 @@ +// CommonJS, every file is module (by default) +// Modules - Encapsulated Code (only share minimum) +const names = require('./4-names') +const sayHi = require('./5-utils') +const data = require('./6-alternative-flavor') +require('./7-mind-grenade') +sayHi('susan') +sayHi(names.john) +sayHi(names.peter) diff --git a/1-node-tutorial/04-names.js b/1-node-tutorial/04-names.js new file mode 100644 index 0000000000..8372b91bee --- /dev/null +++ b/1-node-tutorial/04-names.js @@ -0,0 +1,7 @@ +// local +const secret = 'SUPER SECRET' +// share +const john = 'john' +const peter = 'peter' + +module.exports = { john, peter } diff --git a/1-node-tutorial/05-utils.js b/1-node-tutorial/05-utils.js new file mode 100644 index 0000000000..e77c806c4c --- /dev/null +++ b/1-node-tutorial/05-utils.js @@ -0,0 +1,5 @@ +const sayHi = (name) => { + console.log(`Hello there ${name}`) +} +// export default +module.exports = sayHi diff --git a/1-node-tutorial/06-alternative-flavor.js b/1-node-tutorial/06-alternative-flavor.js new file mode 100644 index 0000000000..370bf959b6 --- /dev/null +++ b/1-node-tutorial/06-alternative-flavor.js @@ -0,0 +1,6 @@ +module.exports.items = ['item1', 'item2'] +const person = { + name: 'bob', +} + +module.exports.singlePerson = person diff --git a/1-node-tutorial/07-mind-grenade.js b/1-node-tutorial/07-mind-grenade.js new file mode 100644 index 0000000000..ca5e16fe59 --- /dev/null +++ b/1-node-tutorial/07-mind-grenade.js @@ -0,0 +1,8 @@ +const num1 = 5 +const num2 = 10 + +function addValues() { + console.log(`the sum is : ${num1 + num2}`) +} + +addValues() diff --git a/1-node-tutorial/08-os-module.js b/1-node-tutorial/08-os-module.js new file mode 100644 index 0000000000..04ef601c6d --- /dev/null +++ b/1-node-tutorial/08-os-module.js @@ -0,0 +1,16 @@ +const os = require('os') + +// info about current user +const user = os.userInfo() +console.log(user) + +// method returns the system uptime in seconds +console.log(`The System Uptime is ${os.uptime()} seconds`) + +const currentOS = { + name: os.type(), + release: os.release(), + totalMem: os.totalmem(), + freeMem: os.freemem(), +} +console.log(currentOS) diff --git a/1-node-tutorial/09-path-module.js b/1-node-tutorial/09-path-module.js new file mode 100644 index 0000000000..a62dad398e --- /dev/null +++ b/1-node-tutorial/09-path-module.js @@ -0,0 +1,12 @@ +const path = require('path') + +console.log(path.sep) + +const filePath = path.join('/content/', 'subfolder', 'test.txt') +console.log(filePath) + +const base = path.basename(filePath) +console.log(base) + +const absolute = path.resolve(__dirname, 'content', 'subfolder', 'test.txt') +console.log(absolute)