Dev Logs
/Node.js & Express/ Node.js Fundamentals: Understanding the Runtime
Chapters
  • 01Node.js Fundamentals: Understanding the Runtime
    • Overview
    • Key Concepts
    • What is Node.js?
    • The V8 Engine
    • The Event Loop
    • Non-blocking I/O
    • Example Code
    • Basic Node.js Script
    • Demonstrating the Event Loop
    • Blocking vs Non-blocking Example
    • Real-World Use Case
    • CPU-Intensive vs I/O-Intensive Tasks
    • Best Practices
    • . Always Use Asynchronous Operations
    • . Handle Errors Properly
    • . Use Environment Variables
    • . Understand the Event Loop
    • Summary
  • 02Node.js Installation and Running Basic Scripts
  • 03NPM: Node Package Manager Fundamentals
  • 04Node.js Core Modules: Built-in Functionality
  • 05Creating HTTP Servers with Node.js
  • 06Introduction to Express.js: Web Framework for Node.js
  • 07Handling Request and Response Objects in Express.js
  • 08Serving Static Files in Express.js
  • 09Environment Variables with dotenv
  • 10Express Router and Modular Route Management
  • 11Template Engines: EJS and Pug for Server-Side Rendering
  • 12RESTful API Design Principles
  • 13CRUD Operations with Express.js
  • 14Middleware in Express.js
  • 15Database Integration: MongoDB and Mongoose
  • 16Authentication and Authorization in Express.js
All chapters

Node.js Fundamentals: Understanding the Runtime

Overview

Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine that allows you to run JavaScript on the server side. Unlike traditional server-side languages, Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient for building scalable network applications.

Key Concepts

What is Node.js?

Node.js is not a programming language or a framework – it's a runtime environment that executes JavaScript code outside of a web browser. It was created by Ryan Dahl in 2009 to enable JavaScript to be used for server-side development.

The V8 Engine

Node.js uses Google's V8 JavaScript engine, the same engine that powers Google Chrome. V8 compiles JavaScript directly to native machine code, making it extremely fast. This engine:

  • Compiles JavaScript to machine code
  • Handles memory allocation and garbage collection
  • Provides the JavaScript runtime environment

The Event Loop

The event loop is the heart of Node.js's non-blocking I/O model. It's a single-threaded loop that handles all asynchronous operations.

┌───────────────────────────┐
┌─>│           timers          │  ← setTimeout, setInterval
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │  ← I/O callbacks
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │  ← internal use
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           poll            │  ← new I/O events
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           check           │  ← setImmediate
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │  ← close events
   └───────────────────────────┘

Non-blocking I/O

Traditional server models create a new thread for each request, which can be resource-intensive. Node.js uses a single thread with an event loop to handle multiple concurrent operations without blocking.

Example Code

Basic Node.js Script

Create a file called hello.js:

javascript
// hello.js
console.log("Hello, Node.js!");
console.log("Current directory:", process.cwd());
console.log("Node.js version:", process.version);
console.log("Platform:", process.platform);

Run it with:

bash
node hello.js

Demonstrating the Event Loop

javascript
// event-loop-demo.js
console.log("Start");

// Immediate execution
setImmediate(() => {
  console.log("setImmediate");
});

// Timer
setTimeout(() => {
  console.log("setTimeout");
}, 0);

// Process next tick (highest priority)
process.nextTick(() => {
  console.log("nextTick");
});

// Promise (microtask)
Promise.resolve().then(() => {
  console.log("Promise");
});

console.log("End");

// Output order:
// Start
// End
// nextTick
// Promise
// setTimeout
// setImmediate

Blocking vs Non-blocking Example

javascript
// blocking-example.js
const fs = require("fs");

console.log("Start");

// Blocking (synchronous) - avoid in production
try {
  const data = fs.readFileSync("package.json", "utf8");
  console.log("File read synchronously");
} catch (err) {
  console.log("File not found (sync)");
}

// Non-blocking (asynchronous) - preferred
fs.readFile("package.json", "utf8", (err, data) => {
  if (err) {
    console.log("File not found (async)");
  } else {
    console.log("File read asynchronously");
  }
});

console.log("End");

// Output:
// Start
// File read synchronously (or error)
// End
// File read asynchronously (or error)

Real-World Use Case

CPU-Intensive vs I/O-Intensive Tasks

Node.js excels at I/O-intensive applications but struggles with CPU-intensive tasks:

javascript
// io-intensive.js - Good for Node.js
const fs = require("fs");
const http = require("http");

const server = http.createServer((req, res) => {
  // Multiple file operations can happen concurrently
  fs.readFile("data1.txt", (err, data1) => {
    fs.readFile("data2.txt", (err, data2) => {
      fs.readFile("data3.txt", (err, data3) => {
        res.end("All files processed");
      });
    });
  });
});

server.listen(3000);
javascript
// cpu-intensive.js - Not ideal for Node.js
function fibonacci(n) {
  if (n < 2) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

console.log("Start");
const result = fibonacci(40); // This blocks everything
console.log("Result:", result);
console.log("End");

Best Practices

1. Always Use Asynchronous Operations

javascript
// ❌ Bad - blocks the event loop
const data = fs.readFileSync("file.txt");

// ✅ Good - non-blocking
fs.readFile("file.txt", (err, data) => {
  // Handle the data
});

// ✅ Even better - using promises/async-await
const data = await fs.promises.readFile("file.txt");

2. Handle Errors Properly

javascript
// ❌ Bad - unhandled errors can crash the app
fs.readFile("file.txt", (err, data) => {
  console.log(data.toString());
});

// ✅ Good - always handle errors
fs.readFile("file.txt", (err, data) => {
  if (err) {
    console.error("Error reading file:", err.message);
    return;
  }
  console.log(data.toString());
});

3. Use Environment Variables

javascript
// config.js
module.exports = {
  port: process.env.PORT || 3000,
  nodeEnv: process.env.NODE_ENV || "development",
  dbUrl: process.env.DATABASE_URL || "mongodb://localhost:27017/myapp",
};

4. Understand the Event Loop

  • Don't block the event loop with synchronous operations
  • Use setImmediate() for deferring execution
  • Use process.nextTick() sparingly (it has the highest priority)

Summary

Node.js revolutionizes server-side development by bringing JavaScript to the backend with its event-driven, non-blocking I/O model. Key takeaways:

  • V8 Engine: Compiles JavaScript to machine code for high performance
  • Event Loop: Single-threaded loop that handles asynchronous operations
  • Non-blocking I/O: Allows handling multiple operations concurrently
  • Best Use Cases: I/O-intensive applications like web servers, APIs, real-time apps
  • Avoid: CPU-intensive tasks that can block the event loop

Understanding these fundamentals is crucial before diving into Express.js and building web applications. The event-driven nature of Node.js makes it perfect for building scalable network applications, but it requires a different mindset compared to traditional multi-threaded server environments.

Next, we'll explore how to install Node.js and start building our first applications.

NextNode.js Installation and Running Basic Scripts

Open source, free forever. Built by iammhador.

Contribute on GitHub