Skip to main content
โšก Calmops

Node.js: Runtime and Fundamentals

Node.js: Runtime and Fundamentals

Node.js is a JavaScript runtime for building server-side applications. This article covers Node.js fundamentals.

Introduction

Node.js provides:

  • Server-side JavaScript execution
  • Non-blocking I/O
  • Event-driven architecture
  • Module system
  • Rich ecosystem

Understanding Node.js helps you:

  • Build server applications
  • Handle asynchronous operations
  • Work with file systems
  • Create APIs
  • Scale applications

Node.js Installation and Setup

Installation

# โœ… Good: Install Node.js
# Download from https://nodejs.org/

# โœ… Good: Verify installation
node --version
npm --version

# โœ… Good: Create new project
mkdir my-app
cd my-app
npm init -y

# โœ… Good: Install dependencies
npm install express
npm install --save-dev nodemon

Package.json

{
  "name": "my-app",
  "version": "1.0.0",
  "description": "My Node.js application",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js",
    "test": "jest"
  },
  "keywords": ["node", "javascript"],
  "author": "Your Name",
  "license": "MIT",
  "dependencies": {
    "express": "^4.18.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.0",
    "jest": "^29.0.0"
  }
}

Node.js Modules

CommonJS Modules

// math.js
// โœ… Good: Export functions
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

module.exports = {
  add,
  subtract
};

// app.js
// โœ… Good: Import modules
const math = require('./math');

console.log(math.add(5, 3));      // 8
console.log(math.subtract(5, 3)); // 2

// โœ… Good: Destructuring imports
const { add, subtract } = require('./math');

console.log(add(5, 3));      // 8
console.log(subtract(5, 3)); // 2

// โœ… Good: Built-in modules
const fs = require('fs');
const path = require('path');
const http = require('http');

const filePath = path.join(__dirname, 'file.txt');
const content = fs.readFileSync(filePath, 'utf-8');
console.log(content);

ES Modules

// math.js
// โœ… Good: Export with ES modules
export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

// app.js
// โœ… Good: Import ES modules
import { add, subtract } from './math.js';

console.log(add(5, 3));      // 8
console.log(subtract(5, 3)); // 2

// โœ… Good: Default export
// utils.js
export default function greet(name) {
  return `Hello, ${name}!`;
}

// app.js
import greet from './utils.js';

console.log(greet('John')); // Hello, John!

File System Operations

Reading and Writing Files

const fs = require('fs');
const path = require('path');

// โœ… Good: Read file synchronously
const content = fs.readFileSync('file.txt', 'utf-8');
console.log(content);

// โœ… Good: Read file asynchronously
fs.readFile('file.txt', 'utf-8', (err, data) => {
  if (err) {
    console.error('Error reading file:', err);
    return;
  }
  console.log(data);
});

// โœ… Good: Read file with promises
fs.promises.readFile('file.txt', 'utf-8')
  .then(data => console.log(data))
  .catch(err => console.error('Error:', err));

// โœ… Good: Write file
fs.writeFileSync('output.txt', 'Hello, World!');

// โœ… Good: Write file asynchronously
fs.writeFile('output.txt', 'Hello, World!', (err) => {
  if (err) {
    console.error('Error writing file:', err);
    return;
  }
  console.log('File written successfully');
});

// โœ… Good: Append to file
fs.appendFileSync('log.txt', 'New log entry\n');

// โœ… Good: Delete file
fs.unlinkSync('file.txt');

// โœ… Good: List directory contents
const files = fs.readdirSync('./');
console.log(files);

// โœ… Good: Check if file exists
if (fs.existsSync('file.txt')) {
  console.log('File exists');
}

Event Loop and Asynchronous Operations

Understanding the Event Loop

// โœ… Good: Event loop demonstration
console.log('Start');

setTimeout(() => {
  console.log('Timeout 1');
}, 0);

Promise.resolve()
  .then(() => {
    console.log('Promise 1');
  });

console.log('End');

// Output:
// Start
// End
// Promise 1
// Timeout 1

// โœ… Good: Microtasks vs Macrotasks
console.log('Script start');

setTimeout(() => {
  console.log('setTimeout');
}, 0);

Promise.resolve()
  .then(() => {
    console.log('Promise 1');
  })
  .then(() => {
    console.log('Promise 2');
  });

console.log('Script end');

// Output:
// Script start
// Script end
// Promise 1
// Promise 2
// setTimeout

Callbacks and Promises

// โœ… Good: Callback pattern
function readUserData(userId, callback) {
  setTimeout(() => {
    const user = { id: userId, name: 'John' };
    callback(null, user);
  }, 1000);
}

readUserData(1, (err, user) => {
  if (err) {
    console.error('Error:', err);
    return;
  }
  console.log('User:', user);
});

// โœ… Good: Promise pattern
function readUserDataPromise(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const user = { id: userId, name: 'John' };
      resolve(user);
    }, 1000);
  });
}

readUserDataPromise(1)
  .then(user => console.log('User:', user))
  .catch(err => console.error('Error:', err));

// โœ… Good: Async/await pattern
async function getUserData(userId) {
  try {
    const user = await readUserDataPromise(userId);
    console.log('User:', user);
  } catch (err) {
    console.error('Error:', err);
  }
}

getUserData(1);

Streams

Working with Streams

const fs = require('fs');

// โœ… Good: Read stream
const readStream = fs.createReadStream('large-file.txt', {
  encoding: 'utf-8',
  highWaterMark: 16 * 1024 // 16KB chunks
});

readStream.on('data', (chunk) => {
  console.log('Received chunk:', chunk.length);
});

readStream.on('end', () => {
  console.log('Stream ended');
});

readStream.on('error', (err) => {
  console.error('Stream error:', err);
});

// โœ… Good: Write stream
const writeStream = fs.createWriteStream('output.txt');

writeStream.write('Hello, ');
writeStream.write('World!');
writeStream.end();

writeStream.on('finish', () => {
  console.log('Write stream finished');
});

// โœ… Good: Pipe streams
fs.createReadStream('input.txt')
  .pipe(fs.createWriteStream('output.txt'));

// โœ… Good: Transform stream
const { Transform } = require('stream');

const upperCaseTransform = new Transform({
  transform(chunk, encoding, callback) {
    this.push(chunk.toString().toUpperCase());
    callback();
  }
});

fs.createReadStream('input.txt')
  .pipe(upperCaseTransform)
  .pipe(fs.createWriteStream('output.txt'));

HTTP Server

Creating a Basic Server

const http = require('http');

// โœ… Good: Create HTTP server
const server = http.createServer((req, res) => {
  if (req.url === '/' && req.method === 'GET') {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end('<h1>Hello, World!</h1>');
  } else if (req.url === '/api/users' && req.method === 'GET') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify([
      { id: 1, name: 'John' },
      { id: 2, name: 'Jane' }
    ]));
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('Not Found');
  }
});

server.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

// โœ… Good: Handle POST requests
const server = http.createServer((req, res) => {
  if (req.url === '/api/users' && req.method === 'POST') {
    let body = '';

    req.on('data', (chunk) => {
      body += chunk.toString();
    });

    req.on('end', () => {
      const user = JSON.parse(body);
      console.log('Received user:', user);
      res.writeHead(201, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ id: 1, ...user }));
    });
  }
});

server.listen(3000);

Environment Variables

Working with Environment Variables

// โœ… Good: Access environment variables
console.log(process.env.NODE_ENV);
console.log(process.env.PORT);

// โœ… Good: Use dotenv package
// .env
NODE_ENV=development
PORT=3000
DATABASE_URL=mongodb://localhost:27017/mydb

// app.js
require('dotenv').config();

const port = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;

console.log(`Server running on port ${port}`);
console.log(`Database URL: ${dbUrl}`);

// โœ… Good: Set default values
const nodeEnv = process.env.NODE_ENV || 'development';
const port = process.env.PORT || 3000;
const debug = process.env.DEBUG === 'true';

console.log(`Environment: ${nodeEnv}`);
console.log(`Port: ${port}`);
console.log(`Debug: ${debug}`);

Best Practices

  1. Use async/await:

    // โœ… Good: Async/await
    async function getData() {
      try {
        const data = await fetchData();
        return data;
      } catch (err) {
        console.error('Error:', err);
      }
    }
    
    // โŒ Bad: Callback hell
    function getData(callback) {
      fetchData((err, data) => {
        if (err) callback(err);
        else callback(null, data);
      });
    }
    
  2. Handle errors properly:

    // โœ… Good: Error handling
    try {
      const data = fs.readFileSync('file.txt', 'utf-8');
    } catch (err) {
      console.error('Error reading file:', err);
    }
    
    // โŒ Bad: No error handling
    const data = fs.readFileSync('file.txt', 'utf-8');
    
  3. Use environment variables:

    // โœ… Good: Environment variables
    const port = process.env.PORT || 3000;
    
    // โŒ Bad: Hardcoded values
    const port = 3000;
    

Summary

Node.js is powerful. Key takeaways:

  • Understand the event loop
  • Use async/await for asynchronous operations
  • Work with file systems
  • Create HTTP servers
  • Use streams for large data
  • Handle errors properly
  • Use environment variables
  • Build scalable applications

Next Steps

Comments