Episode 3 — NodeJS MongoDB Backend Architecture / 3.1 — Starting with NodeJS

3.1.c — Running Scripts with Node.js

In one sentence: Node.js scripts are plain JavaScript files executed with the node command, giving you access to powerful globals like process, __dirname, and console methods that let you interact with the operating system, command line, and file system.


Navigation: ← 3.1 Overview · Next → 3.1.d NPM Basics


Table of Contents


1. Creating and Running Your First Script

Step by step

# Step 1: Create a file
touch hello.js
// hello.js
console.log('Hello from Node.js!');
console.log('This is running OUTSIDE the browser.');
console.log('No DOM, no window — pure server-side JavaScript.');
# Step 2: Run it
node hello.js
# Hello from Node.js!
# This is running OUTSIDE the browser.
# No DOM, no window — pure server-side JavaScript.

Key points

  • Any valid JavaScript file can be run with node filename.js
  • The file extension .js is convention but Node.js does not require it
  • You can also use .mjs for ES Modules or .cjs for explicit CommonJS
  • Node.js executes the file top to bottom, then exits (unless something keeps it alive, like a server or timer)

What keeps a Node.js process alive?

// This exits immediately after printing
console.log('Done!');
// Process exits — nothing else to do

// This keeps running — the server listens for connections
const http = require('http');
http.createServer((req, res) => res.end('Hi')).listen(3000);
// Process stays alive — event loop has work (listening socket)

// This keeps running — timer is active
setInterval(() => console.log('tick'), 1000);
// Process stays alive — event loop has a timer

2. Console Methods — Beyond console.log

Node.js provides a rich console object. Most developers only use console.log(), but there is much more.

console.log() — Standard output

// Basic output
console.log('Hello, world!');

// String interpolation
const name = 'Arjun';
console.log(`Hello, ${name}!`);              // Template literal
console.log('Hello, %s!', name);              // C-style format string

// Multiple arguments
console.log('Name:', name, 'Age:', 25);       // Name: Arjun Age: 25

// Object output
const user = { name: 'Arjun', role: 'developer' };
console.log(user);                            // { name: 'Arjun', role: 'developer' }
console.log('User:', user);

console.error() and console.warn() — stderr

// These write to STDERR (not STDOUT) — important for piping/logging
console.error('ERROR: Database connection failed');
console.warn('WARNING: Deprecated function used');

// In the terminal they look the same, but you can redirect them separately:
// node app.js > output.log 2> errors.log
// STDOUT (console.log) → output.log
// STDERR (console.error, console.warn) → errors.log

console.table() — Tabular display

const users = [
  { name: 'Arjun', age: 25, city: 'Mumbai' },
  { name: 'Priya', age: 28, city: 'Delhi' },
  { name: 'Rahul', age: 22, city: 'Bangalore' },
];

console.table(users);
// ┌─────────┬──────────┬─────┬─────────────┐
// │ (index) │   name   │ age │    city      │
// ├─────────┼──────────┼─────┼─────────────┤
// │    0    │ 'Arjun'  │  25 │  'Mumbai'   │
// │    1    │ 'Priya'  │  28 │  'Delhi'    │
// │    2    │ 'Rahul'  │  22 │ 'Bangalore' │
// └─────────┴──────────┴─────┴─────────────┘

// Select specific columns
console.table(users, ['name', 'city']);

console.time() and console.timeEnd() — Performance measurement

console.time('array-creation');

const arr = [];
for (let i = 0; i < 1000000; i++) {
  arr.push(i * 2);
}

console.timeEnd('array-creation');
// array-creation: 42.567ms

// You can have multiple timers running simultaneously
console.time('fetch-users');
console.time('fetch-posts');
// ... do async work ...
console.timeEnd('fetch-users');   // fetch-users: 120.4ms
console.timeEnd('fetch-posts');   // fetch-posts: 85.2ms

console.count() — Invocation counter

function processItem(type) {
  console.count(type);
}

processItem('fruit');    // fruit: 1
processItem('vegetable');// vegetable: 1
processItem('fruit');    // fruit: 2
processItem('fruit');    // fruit: 3
processItem('vegetable');// vegetable: 2

console.countReset('fruit');
processItem('fruit');    // fruit: 1   (reset!)

console.dir() — Deep object inspection

const nested = {
  level1: {
    level2: {
      level3: {
        level4: { deep: 'value' }
      }
    }
  }
};

// console.log truncates deep objects
console.log(nested);
// { level1: { level2: { level3: [Object] } } }

// console.dir with depth control
console.dir(nested, { depth: null });   // Shows ALL levels
console.dir(nested, { depth: 2 });     // Shows 2 levels deep
console.dir(nested, { colors: true }); // Syntax highlighting

console.group() — Grouped output

console.group('User Details');
console.log('Name: Arjun');
console.log('Age: 25');
  console.group('Address');
  console.log('City: Mumbai');
  console.log('State: Maharashtra');
  console.groupEnd();
console.groupEnd();

// Output:
// User Details
//   Name: Arjun
//   Age: 25
//   Address
//     City: Mumbai
//     State: Maharashtra

console.clear() — Clear the terminal

console.clear();  // Clears the terminal screen

All console methods summary

MethodOutput StreamPurpose
console.log()stdoutGeneral output
console.error()stderrError messages
console.warn()stderrWarning messages
console.info()stdoutInformational (same as log)
console.table()stdoutTabular data display
console.time() / timeEnd()stdoutPerformance timing
console.count() / countReset()stdoutCall counting
console.dir()stdoutDeep object inspection
console.group() / groupEnd()stdoutGrouped/nested output
console.trace()stderrPrint stack trace
console.assert()stderrLog only if assertion fails
console.clear()Clear terminal

3. The Process Object

process is a global object in Node.js (no require needed) that provides information about and control over the current Node.js process.

process.argv — Command-line arguments

// args.js
console.log(process.argv);
node args.js hello world 42
# [
#   '/Users/yourname/.nvm/versions/node/v20.11.0/bin/node',  ← [0] path to node
#   '/Users/yourname/project/args.js',                        ← [1] path to script
#   'hello',                                                  ← [2] first argument
#   'world',                                                  ← [3] second argument
#   '42'                                                      ← [4] third argument
# ]

process.env — Environment variables

// env.js
console.log('NODE_ENV:', process.env.NODE_ENV);
console.log('HOME:', process.env.HOME);
console.log('PATH:', process.env.PATH);

// Custom environment variable
console.log('API_KEY:', process.env.API_KEY || 'not set');
# Set an environment variable when running
NODE_ENV=production API_KEY=secret123 node env.js
# NODE_ENV: production
# HOME: /Users/yourname
# PATH: /usr/local/bin:/usr/bin:...
# API_KEY: secret123

process.exit() — Exit the process

// exit-codes.js
const config = process.env.DATABASE_URL;

if (!config) {
  console.error('ERROR: DATABASE_URL is required');
  process.exit(1);   // Exit with error code 1 (non-zero = error)
}

// ... rest of app ...
console.log('App started successfully');
process.exit(0);     // Exit with code 0 (success) — rarely needed explicitly
Exit code conventions:
  0  → Success (everything is fine)
  1  → General error
  2  → Misuse of command
  Any non-zero → Something went wrong

process.cwd() — Current working directory

console.log('Current directory:', process.cwd());
// /Users/yourname/my-project

// Note: process.cwd() is WHERE you ran the command
// __dirname is WHERE the file lives (they can be different!)

process.pid and process.uptime()

console.log('Process ID:', process.pid);           // 12345
console.log('Uptime:', process.uptime(), 'seconds'); // 0.045
console.log('Memory:', process.memoryUsage());
// {
//   rss: 30932992,         ← Resident Set Size (total memory)
//   heapTotal: 6537216,    ← V8 heap allocated
//   heapUsed: 4275832,     ← V8 heap actually used
//   external: 810632,      ← C++ objects bound to JS
//   arrayBuffers: 10507    ← ArrayBuffers and SharedArrayBuffers
// }

process.nextTick() — Microtask scheduling

console.log('Start');

process.nextTick(() => {
  console.log('Next tick callback');   // Runs before any I/O or timers
});

setTimeout(() => {
  console.log('setTimeout callback');  // Runs in timers phase
}, 0);

console.log('End');

// Output:
// Start
// End
// Next tick callback       ← nextTick fires before setTimeout
// setTimeout callback

process events

// Handle uncaught exceptions (last resort — log and exit gracefully)
process.on('uncaughtException', (err) => {
  console.error('Uncaught Exception:', err.message);
  process.exit(1);
});

// Handle unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});

// Handle graceful shutdown (Ctrl+C)
process.on('SIGINT', () => {
  console.log('\nGracefully shutting down...');
  // Close database connections, finish pending requests, etc.
  process.exit(0);
});

// Runs when the process is about to exit
process.on('exit', (code) => {
  console.log('Process exiting with code:', code);
  // Only synchronous code works here — no async!
});

4. Command-Line Arguments

Basic parsing

// greet.js — A simple CLI tool
const args = process.argv.slice(2);  // Remove node path and script path

const name = args[0] || 'World';
const greeting = args[1] || 'Hello';

console.log(`${greeting}, ${name}!`);
node greet.js Arjun Namaste
# Namaste, Arjun!

node greet.js
# Hello, World!

Parsing flags manually

// flags.js — Parse --key=value style flags
const args = process.argv.slice(2);
const flags = {};

args.forEach(arg => {
  if (arg.startsWith('--')) {
    const [key, value] = arg.slice(2).split('=');
    flags[key] = value || true;
  }
});

console.log('Flags:', flags);
node flags.js --name=Arjun --verbose --port=3000
# Flags: { name: 'Arjun', verbose: true, port: '3000' }

Built-in parseArgs (Node 18.3+)

// modern-args.js — Using Node.js built-in argument parser
const { parseArgs } = require('node:util');

const { values, positionals } = parseArgs({
  options: {
    name:    { type: 'string', short: 'n' },
    port:    { type: 'string', short: 'p', default: '3000' },
    verbose: { type: 'boolean', short: 'v', default: false },
  },
  allowPositionals: true,
});

console.log('Values:', values);
console.log('Positionals:', positionals);
node modern-args.js -n Arjun -p 8080 -v file1.txt file2.txt
# Values: { name: 'Arjun', port: '8080', verbose: true }
# Positionals: [ 'file1.txt', 'file2.txt' ]

5. Reading User Input with readline

Basic prompt

// ask.js — Read user input from the terminal
const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

rl.question('What is your name? ', (name) => {
  rl.question('What is your age? ', (age) => {
    console.log(`\nHello, ${name}! You are ${age} years old.`);
    rl.close();
  });
});
node ask.js
# What is your name? Arjun
# What is your age? 25
#
# Hello, Arjun! You are 25 years old.

Promise-based readline (Node 17+)

// ask-modern.js — Using promises (cleaner, no callback nesting)
const readline = require('readline/promises');
const { stdin: input, stdout: output } = require('process');

async function main() {
  const rl = readline.createInterface({ input, output });

  const name = await rl.question('What is your name? ');
  const city = await rl.question('Where are you from? ');
  const lang = await rl.question('Favorite language? ');

  console.log(`\n${name} from ${city} loves ${lang}!`);
  rl.close();
}

main();

Building a simple quiz

// quiz.js
const readline = require('readline/promises');
const { stdin: input, stdout: output } = require('process');

const questions = [
  { q: 'What does "npm" stand for?', a: 'node package manager' },
  { q: 'Who created Node.js?', a: 'ryan dahl' },
  { q: 'What engine does Node.js use?', a: 'v8' },
];

async function quiz() {
  const rl = readline.createInterface({ input, output });
  let score = 0;

  for (const { q, a } of questions) {
    const answer = await rl.question(`\n${q}\n> `);
    if (answer.toLowerCase().trim() === a) {
      console.log('Correct!');
      score++;
    } else {
      console.log(`Wrong! The answer was: ${a}`);
    }
  }

  console.log(`\nYour score: ${score}/${questions.length}`);
  rl.close();
}

quiz();

6. __dirname and __filename

These are module-level variables available in every CommonJS file (not true globals).

// paths.js
console.log('__filename:', __filename);
// /Users/yourname/project/src/paths.js    ← Full absolute path to THIS file

console.log('__dirname:', __dirname);
// /Users/yourname/project/src             ← Directory containing THIS file

__dirname vs process.cwd()

// File is at: /Users/yourname/project/src/utils/helper.js

console.log('__dirname:', __dirname);
// /Users/yourname/project/src/utils       ← Where the FILE is

console.log('process.cwd():', process.cwd());
// Depends on WHERE you ran the command:
//   If you ran: cd /Users/yourname/project && node src/utils/helper.js
//   → /Users/yourname/project
//   If you ran: cd / && node /Users/yourname/project/src/utils/helper.js
//   → /

Building file paths with __dirname

const path = require('path');

// Always use path.join with __dirname for reliable file paths
const configPath = path.join(__dirname, '..', 'config', 'database.json');
console.log(configPath);
// /Users/yourname/project/src/config/database.json

// Reading a file relative to the current script
const fs = require('fs');
const data = fs.readFileSync(path.join(__dirname, 'data.json'), 'utf8');

ES Modules equivalent

In ES Modules (.mjs or "type": "module"), __dirname and __filename are not available. Use this instead:

// helper.mjs (ES Module)
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

console.log(__dirname);
console.log(__filename);

7. Global Objects — Node.js vs Browser

Browser global: window

// In the browser:
console.log(window);          // The Window object
console.log(window.document); // The DOM
console.log(window.location); // Current URL
window.alert('Hello!');       // Show dialog

Node.js global: global

// In Node.js:
console.log(global);           // The global object
// global.document → undefined (no DOM!)
// global.alert → undefined (no UI!)

// Things on global:
console.log(global.console);    // console object
console.log(global.setTimeout); // setTimeout function
console.log(global.process);    // process object
console.log(global.Buffer);     // Buffer class

globalThis — The universal answer

// Works in BOTH browser and Node.js (ES2020)
console.log(globalThis);
// In browser: Window object
// In Node.js: global object

Comparison table

GlobalBrowserNode.js
Global objectwindowglobal
Universal globalglobalThisglobalThis
DOMdocument, window.documentNot available
Locationwindow.locationNot available
TimerssetTimeout, setIntervalSame + setImmediate
Consoleconsoleconsole (to stdout/stderr)
Fetchfetch() (built-in)fetch() (Node 18+ built-in)
URLURL, URLSearchParamsSame (built-in)
ProcessNot availableprocess
BufferNot availableBuffer
requireNot available (use import)Available (CommonJS)

Do NOT pollute the global scope

// BAD — attaching things to global
global.myConfig = { port: 3000 };
// Accessible anywhere, but untraceable and dangerous

// GOOD — use modules
// config.js
module.exports = { port: 3000 };

// app.js
const config = require('./config');
console.log(config.port);  // 3000

8. Modules Preview — Every File Is a Module

In Node.js, every .js file is automatically a module. Variables and functions defined in one file are private to that file unless explicitly exported.

// --- math.js ---
const PI = 3.14159;

function add(a, b) {
  return a + b;
}

function multiply(a, b) {
  return a * b;
}

// Export what you want to share
module.exports = { add, multiply, PI };
// 'multiply' is not exported — it's private to this file
// --- app.js ---
const math = require('./math');   // Import the module

console.log(math.add(2, 3));     // 5
console.log(math.PI);            // 3.14159

Why modules matter

Without modules:              With modules:
┌─────────────────────┐      ┌─────────────────────┐
│ Everything in one    │      │ auth.js — login logic│
│ giant file           │      │ db.js   — database   │
│ (1000+ lines)        │      │ api.js  — routes     │
│ Variables collide    │      │ utils.js — helpers   │
│ Hard to test         │      │ Each file is isolated│
│ Hard to maintain     │      │ Easy to test & reuse │
└─────────────────────┘      └─────────────────────┘

We will dive deep into CommonJS and ES Modules in the npm and package.json sections. For now, just know: every file is a module, and require() is how you use other modules.


9. Namaste Duniya — Your First Real Program

Let us put everything together into a meaningful first program.

// namaste-duniya.js — Your first complete Node.js program

// ===== 1. Greeting =====
const greeting = 'Namaste Duniya!';
console.log('='.repeat(50));
console.log(`  ${greeting}`);
console.log(`  Welcome to Node.js ${process.version}`);
console.log('='.repeat(50));

// ===== 2. System Information =====
const os = require('os');

console.log('\n--- System Info ---');
console.table({
  'Platform': os.platform(),
  'Architecture': os.arch(),
  'Hostname': os.hostname(),
  'CPUs': os.cpus().length + ' cores',
  'Total Memory': (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB',
  'Free Memory': (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB',
  'Uptime': (os.uptime() / 3600).toFixed(1) + ' hours',
});

// ===== 3. Process Information =====
console.log('\n--- Process Info ---');
console.log('PID:', process.pid);
console.log('Node version:', process.version);
console.log('Current directory:', process.cwd());
console.log('Script location:', __dirname);
console.log('Script file:', __filename);

// ===== 4. Environment Check =====
console.log('\n--- Environment ---');
const env = process.env.NODE_ENV || 'development';
console.log(`NODE_ENV: ${env}`);
console.log(`Home directory: ${process.env.HOME || process.env.USERPROFILE}`);

// ===== 5. Command-Line Arguments =====
const args = process.argv.slice(2);
if (args.length > 0) {
  console.log(`\n--- Your Arguments ---`);
  args.forEach((arg, i) => console.log(`  [${i}] ${arg}`));
} else {
  console.log('\nTip: Try running with arguments:');
  console.log('  node namaste-duniya.js hello world 42');
}

// ===== 6. Performance =====
console.time('\nArray benchmark');
const bigArray = Array.from({ length: 1_000_000 }, (_, i) => i * 2);
const sum = bigArray.reduce((a, b) => a + b, 0);
console.timeEnd('\nArray benchmark');
console.log(`Sum of 1M elements: ${sum.toLocaleString()}`);

console.log('\n' + '='.repeat(50));
console.log('  Program complete. Namaste!');
console.log('='.repeat(50));
# Run it
node namaste-duniya.js
# ==================================================
#   Namaste Duniya!
#   Welcome to Node.js v20.11.0
# ==================================================
#
# --- System Info ---
# ┌─────────────────┬───────────────────────┐
# │     (index)      │        Values         │
# ├─────────────────┼───────────────────────┤
# │   'Platform'     │       'darwin'        │
# │  'Architecture'  │       'arm64'         │
# ...

# Run with arguments
node namaste-duniya.js Arjun Developer Mumbai

10. Practical Script Examples

Example 1: File counter

// count-files.js — Count files in a directory
const fs = require('fs');
const path = require('path');

const targetDir = process.argv[2] || '.';

const items = fs.readdirSync(targetDir);
const files = items.filter(item => {
  const fullPath = path.join(targetDir, item);
  return fs.statSync(fullPath).isFile();
});
const dirs = items.filter(item => {
  const fullPath = path.join(targetDir, item);
  return fs.statSync(fullPath).isDirectory();
});

console.log(`Directory: ${path.resolve(targetDir)}`);
console.log(`Files: ${files.length}`);
console.log(`Subdirectories: ${dirs.length}`);
console.log(`Total items: ${items.length}`);

Example 2: Simple timer

// timer.js — Countdown timer
const seconds = parseInt(process.argv[2]) || 10;
let remaining = seconds;

console.log(`Timer started: ${seconds} seconds`);

const interval = setInterval(() => {
  remaining--;
  process.stdout.write(`\r  ${remaining} seconds remaining...`);

  if (remaining <= 0) {
    clearInterval(interval);
    console.log('\n  Time is up!');
  }
}, 1000);
node timer.js 5
# Timer started: 5 seconds
#   3 seconds remaining...  (updates in place)
#   Time is up!

Example 3: JSON pretty printer

// pretty-json.js — Read and pretty-print a JSON file
const fs = require('fs');
const path = require('path');

const filePath = process.argv[2];

if (!filePath) {
  console.error('Usage: node pretty-json.js <file.json>');
  process.exit(1);
}

try {
  const raw = fs.readFileSync(path.resolve(filePath), 'utf8');
  const parsed = JSON.parse(raw);
  console.log(JSON.stringify(parsed, null, 2));
} catch (err) {
  if (err.code === 'ENOENT') {
    console.error(`File not found: ${filePath}`);
  } else if (err instanceof SyntaxError) {
    console.error(`Invalid JSON: ${err.message}`);
  } else {
    console.error(`Error: ${err.message}`);
  }
  process.exit(1);
}

Key Takeaways

  1. Run any .js file with node filename.js — the file executes top to bottom and exits unless kept alive by a server, timer, or listener.
  2. console.table() and console.time() are underused gems — use them for debugging data and profiling performance.
  3. process.argv gives you command-line arguments — slice(2) to skip the node and script paths.
  4. process.env accesses environment variables — use NODE_ENV, PORT, DATABASE_URL for configuration.
  5. process.exit(1) terminates with an error code — use it for validation failures in scripts and CLI tools.
  6. __dirname is the directory of the current file; process.cwd() is where the command was run from — they are often different.
  7. global is Node.js's equivalent of the browser's window — but use globalThis for cross-environment code.
  8. Every file is a module — variables are private by default, use module.exports and require() to share code between files.

Explain-It Challenge

Can you write a Node.js script from memory that: (1) reads a name from command-line arguments, (2) prints the current time, platform, and Node version, and (3) exits with code 1 if no name is provided? Aim for 10 lines or fewer.


Navigation: ← 3.1 Overview · Next → 3.1.d NPM Basics