Skip to main content

Module 32: Node.js Fundamentals

Node.js allows you to run JavaScript on the server. It's built on Chrome's V8 engine and uses an event-driven, non-blocking I/O model.


1. Node.js Basics

1.1 Getting Started

// hello.js
console.log('Hello, Node.js!');

// Run: node hello.js

// CommonJS modules (traditional)
// math.js
function add(a, b) {
return a + b;
}

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

module.exports = { add, subtract };

// main.js
const math = require('./math');
console.log(math.add(5, 3)); // 8

// ES modules (modern - package.json needs "type": "module")
// math.mjs or math.js with type: module
export function add(a, b) {
return a + b;
}

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

// main.mjs
import { add, subtract } from './math.mjs';
console.log(add(5, 3)); // 8

1.2 Global Objects

// __dirname and __filename (CommonJS only)
console.log(__dirname); // Current directory path
console.log(__filename); // Current file path

// process object
console.log(process.version); // Node.js version
console.log(process.platform); // Operating system
console.log(process.argv); // Command line arguments
console.log(process.env); // Environment variables
console.log(process.cwd()); // Current working directory

// Exit process
process.exit(0); // Success
process.exit(1); // Error

// Handle process events
process.on('exit', (code) => {
console.log(`Exiting with code: ${code}`);
});

process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
process.exit(1);
});

// Command line arguments
// node app.js arg1 arg2
const args = process.argv.slice(2);
console.log(args); // ['arg1', 'arg2']

// Environment variables
// PORT=3000 node app.js
const port = process.env.PORT || 3000;
console.log(`Server port: ${port}`);

// global object (like window in browser)
global.myGlobalVar = 'Hello';
console.log(global.myGlobalVar);

// Timers
setTimeout(() => console.log('After 1 second'), 1000);
setInterval(() => console.log('Every 2 seconds'), 2000);
setImmediate(() => console.log('Immediate'));

const timeoutId = setTimeout(() => {}, 1000);
clearTimeout(timeoutId);

2. Core Modules

2.1 File System (fs)

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

// Synchronous (blocking)
try {
const data = fs.readFileSync('file.txt', 'utf8');
console.log(data);
} catch (error) {
console.error('Error reading file:', error);
}

// Asynchronous (non-blocking)
fs.readFile('file.txt', 'utf8', (error, data) => {
if (error) {
console.error('Error:', error);
return;
}
console.log(data);
});

// Promises API
const fsPromises = require('fs').promises;

async function readFileAsync() {
try {
const data = await fsPromises.readFile('file.txt', 'utf8');
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}

// Writing files
fs.writeFile('output.txt', 'Hello, World!', (error) => {
if (error) throw error;
console.log('File written successfully');
});

// Appending to files
fs.appendFile('log.txt', 'New log entry\n', (error) => {
if (error) throw error;
console.log('Data appended');
});

// Check if file exists
fs.access('file.txt', fs.constants.F_OK, (error) => {
if (error) {
console.log('File does not exist');
} else {
console.log('File exists');
}
});

// Get file stats
fs.stat('file.txt', (error, stats) => {
if (error) throw error;

console.log('File size:', stats.size);
console.log('Is file:', stats.isFile());
console.log('Is directory:', stats.isDirectory());
console.log('Created:', stats.birthtime);
console.log('Modified:', stats.mtime);
});

// Reading directories
fs.readdir('.', (error, files) => {
if (error) throw error;
files.forEach(file => console.log(file));
});

// Creating directories
fs.mkdir('newDir', { recursive: true }, (error) => {
if (error) throw error;
console.log('Directory created');
});

// Deleting files and directories
fs.unlink('file.txt', (error) => {
if (error) throw error;
console.log('File deleted');
});

fs.rmdir('emptyDir', (error) => {
if (error) throw error;
console.log('Directory deleted');
});

// Rename/move files
fs.rename('oldName.txt', 'newName.txt', (error) => {
if (error) throw error;
console.log('File renamed');
});

// Watch for file changes
fs.watch('file.txt', (eventType, filename) => {
console.log(`Event: ${eventType}, File: ${filename}`);
});

// Streams for large files
const readStream = fs.createReadStream('large-file.txt', 'utf8');
const writeStream = fs.createWriteStream('copy.txt');

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

readStream.on('end', () => {
console.log('Finished reading');
});

// Pipe stream
readStream.pipe(writeStream);

2.2 Path Module

const path = require('path');

// Join paths
const filePath = path.join(__dirname, 'files', 'document.txt');
console.log(filePath); // /current/dir/files/document.txt

// Resolve absolute path
const absolutePath = path.resolve('files', 'document.txt');
console.log(absolutePath);

// Parse path
const parsed = path.parse('/home/user/file.txt');
console.log(parsed);
/*
{
root: '/',
dir: '/home/user',
base: 'file.txt',
ext: '.txt',
name: 'file'
}
*/

// Format path
const formatted = path.format({
dir: '/home/user',
base: 'file.txt'
});
console.log(formatted); // /home/user/file.txt

// Get directory name
console.log(path.dirname('/home/user/file.txt')); // /home/user

// Get file name
console.log(path.basename('/home/user/file.txt')); // file.txt
console.log(path.basename('/home/user/file.txt', '.txt')); // file

// Get extension
console.log(path.extname('/home/user/file.txt')); // .txt

// Normalize path
console.log(path.normalize('/home/user/../user/./file.txt')); // /home/user/file.txt

// Check if path is absolute
console.log(path.isAbsolute('/home/user')); // true
console.log(path.isAbsolute('user/file')); // false

// Get relative path
const from = '/home/user/files';
const to = '/home/user/pictures/image.jpg';
console.log(path.relative(from, to)); // ../pictures/image.jpg

2.3 HTTP Module

const http = require('http');

// Create server
const server = http.createServer((req, res) => {
console.log(`${req.method} ${req.url}`);

// Set response headers
res.setHeader('Content-Type', 'text/html');

// Send response
res.statusCode = 200;
res.end('<h1>Hello, World!</h1>');
});

// Start server
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});

// Routing
const server = http.createServer((req, res) => {
const { url, method } = req;

res.setHeader('Content-Type', 'application/json');

if (url === '/' && method === 'GET') {
res.statusCode = 200;
res.end(JSON.stringify({ message: 'Home page' }));
} else if (url === '/api/users' && method === 'GET') {
res.statusCode = 200;
res.end(JSON.stringify([
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]));
} else if (url === '/api/users' && method === 'POST') {
let body = '';

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

req.on('end', () => {
const user = JSON.parse(body);
res.statusCode = 201;
res.end(JSON.stringify({ id: 3, ...user }));
});
} else {
res.statusCode = 404;
res.end(JSON.stringify({ error: 'Not found' }));
}
});

// HTTP client
const options = {
hostname: 'api.example.com',
port: 80,
path: '/users',
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
};

const req = http.request(options, (res) => {
let data = '';

res.on('data', (chunk) => {
data += chunk;
});

res.on('end', () => {
console.log(JSON.parse(data));
});
});

req.on('error', (error) => {
console.error('Error:', error);
});

req.end();

2.4 Events Module

const EventEmitter = require('events');

// Create event emitter
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();

// Listen to events
myEmitter.on('event', (arg) => {
console.log('Event occurred:', arg);
});

// Emit events
myEmitter.emit('event', 'Hello');

// One-time listener
myEmitter.once('oneTime', () => {
console.log('This will only run once');
});

myEmitter.emit('oneTime');
myEmitter.emit('oneTime'); // Won't trigger

// Multiple listeners
myEmitter.on('data', (data) => {
console.log('Listener 1:', data);
});

myEmitter.on('data', (data) => {
console.log('Listener 2:', data);
});

myEmitter.emit('data', 'Test');

// Remove listener
const listener = (data) => console.log(data);
myEmitter.on('event', listener);
myEmitter.off('event', listener); // Remove

// Error handling
myEmitter.on('error', (error) => {
console.error('Error occurred:', error);
});

myEmitter.emit('error', new Error('Something went wrong'));

// Real-world example: User registration
class UserService extends EventEmitter {
register(user) {
console.log('Registering user:', user.name);

// Emit events
this.emit('userRegistered', user);
this.emit('sendWelcomeEmail', user.email);
this.emit('logActivity', { action: 'register', user });
}
}

const userService = new UserService();

userService.on('userRegistered', (user) => {
console.log('User registered:', user.name);
});

userService.on('sendWelcomeEmail', (email) => {
console.log('Sending welcome email to:', email);
});

userService.on('logActivity', (log) => {
console.log('Activity logged:', log);
});

userService.register({ name: 'John', email: 'john@example.com' });

3. Express.js Framework

3.1 Basic Express Server

const express = require('express');
const app = express();

// Middleware
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies
app.use(express.static('public')); // Serve static files

// Routes
app.get('/', (req, res) => {
res.send('Hello, Express!');
});

app.get('/api/users', (req, res) => {
res.json([
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]);
});

app.get('/api/users/:id', (req, res) => {
const { id } = req.params;
res.json({ id, name: 'User ' + id });
});

app.post('/api/users', (req, res) => {
const user = req.body;
console.log('Creating user:', user);
res.status(201).json({ id: 3, ...user });
});

app.put('/api/users/:id', (req, res) => {
const { id } = req.params;
const updates = req.body;
res.json({ id, ...updates });
});

app.delete('/api/users/:id', (req, res) => {
const { id } = req.params;
res.status(204).send();
});

// Query parameters
app.get('/search', (req, res) => {
const { q, page, limit } = req.query;
res.json({ query: q, page, limit });
});

// Error handling middleware
app.use((error, req, res, next) => {
console.error(error.stack);
res.status(500).json({ error: 'Something went wrong!' });
});

// 404 handler
app.use((req, res) => {
res.status(404).json({ error: 'Not found' });
});

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

3.2 Express Middleware

// Custom middleware
function logger(req, res, next) {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next(); // Pass control to next middleware
}

app.use(logger);

// Authentication middleware
function authenticate(req, res, next) {
const token = req.headers.authorization;

if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}

// Verify token
try {
req.user = verifyToken(token);
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
}

// Protected route
app.get('/api/protected', authenticate, (req, res) => {
res.json({ message: 'Protected data', user: req.user });
});

// Error handling middleware
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}

app.get('/api/data', asyncHandler(async (req, res) => {
const data = await fetchData();
res.json(data);
}));

// Validation middleware
function validateUser(req, res, next) {
const { name, email } = req.body;

if (!name || !email) {
return res.status(400).json({ error: 'Name and email are required' });
}

if (!email.includes('@')) {
return res.status(400).json({ error: 'Invalid email' });
}

next();
}

app.post('/api/users', validateUser, (req, res) => {
// User data is validated
res.json({ success: true });
});

// Router
const userRouter = express.Router();

userRouter.get('/', (req, res) => {
res.json({ message: 'Get all users' });
});

userRouter.get('/:id', (req, res) => {
res.json({ message: `Get user ${req.params.id}` });
});

userRouter.post('/', (req, res) => {
res.json({ message: 'Create user' });
});

app.use('/api/users', userRouter);

3.3 REST API Example

const express = require('express');
const app = express();

app.use(express.json());

// In-memory database
let todos = [
{ id: 1, title: 'Learn Node.js', completed: false },
{ id: 2, title: 'Build API', completed: false }
];

let nextId = 3;

// GET /api/todos - Get all todos
app.get('/api/todos', (req, res) => {
res.json(todos);
});

// GET /api/todos/:id - Get single todo
app.get('/api/todos/:id', (req, res) => {
const id = parseInt(req.params.id);
const todo = todos.find(t => t.id === id);

if (!todo) {
return res.status(404).json({ error: 'Todo not found' });
}

res.json(todo);
});

// POST /api/todos - Create todo
app.post('/api/todos', (req, res) => {
const { title } = req.body;

if (!title) {
return res.status(400).json({ error: 'Title is required' });
}

const todo = {
id: nextId++,
title,
completed: false
};

todos.push(todo);
res.status(201).json(todo);
});

// PUT /api/todos/:id - Update todo
app.put('/api/todos/:id', (req, res) => {
const id = parseInt(req.params.id);
const todoIndex = todos.findIndex(t => t.id === id);

if (todoIndex === -1) {
return res.status(404).json({ error: 'Todo not found' });
}

todos[todoIndex] = {
...todos[todoIndex],
...req.body,
id // Prevent ID change
};

res.json(todos[todoIndex]);
});

// DELETE /api/todos/:id - Delete todo
app.delete('/api/todos/:id', (req, res) => {
const id = parseInt(req.params.id);
const todoIndex = todos.findIndex(t => t.id === id);

if (todoIndex === -1) {
return res.status(404).json({ error: 'Todo not found' });
}

todos.splice(todoIndex, 1);
res.status(204).send();
});

app.listen(3000, () => {
console.log('API server running on port 3000');
});

4. Streams and Buffers

4.1 Buffers

// Create buffers
const buf1 = Buffer.alloc(10); // 10 bytes, initialized to 0
const buf2 = Buffer.allocUnsafe(10); // 10 bytes, not initialized
const buf3 = Buffer.from('Hello', 'utf8'); // From string
const buf4 = Buffer.from([1, 2, 3, 4]); // From array

// Reading buffers
console.log(buf3.toString()); // 'Hello'
console.log(buf3.toString('hex')); // '48656c6c6f'
console.log(buf3.toString('base64')); // 'SGVsbG8='

// Writing to buffers
const buf = Buffer.alloc(10);
buf.write('Hello');
console.log(buf.toString()); // 'Hello'

// Buffer methods
console.log(buf.length); // 10
console.log(buf.slice(0, 5)); // First 5 bytes
console.log(Buffer.concat([buf1, buf2])); // Concatenate

// JSON
const buf = Buffer.from('Hello');
console.log(buf.toJSON());
// { type: 'Buffer', data: [72, 101, 108, 108, 111] }

4.2 Streams

const fs = require('fs');

// Readable stream
const readStream = fs.createReadStream('large-file.txt', {
encoding: 'utf8',
highWaterMark: 64 * 1024 // 64KB chunks
});

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

readStream.on('end', () => {
console.log('Finished reading');
});

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

// Writable stream
const writeStream = fs.createWriteStream('output.txt');

writeStream.write('Line 1\n');
writeStream.write('Line 2\n');
writeStream.end('Final line\n');

writeStream.on('finish', () => {
console.log('Finished writing');
});

// Piping streams
const readStream = fs.createReadStream('input.txt');
const writeStream = fs.createWriteStream('output.txt');

readStream.pipe(writeStream);

// 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 streaming
const http = require('http');

const server = http.createServer((req, res) => {
if (req.url === '/video') {
const stream = fs.createReadStream('video.mp4');
res.writeHead(200, { 'Content-Type': 'video/mp4' });
stream.pipe(res);
}
});
Streams for Large Files

Use streams for large files to avoid loading everything into memory. Streams process data chunk by chunk.


5. Async Patterns in Node.js

5.1 Callbacks, Promises, Async/Await

// Callback pattern (traditional)
fs.readFile('file.txt', 'utf8', (error, data) => {
if (error) {
console.error(error);
return;
}
console.log(data);
});

// Callback hell
fs.readFile('file1.txt', (err, data1) => {
if (err) throw err;
fs.readFile('file2.txt', (err, data2) => {
if (err) throw err;
fs.readFile('file3.txt', (err, data3) => {
if (err) throw err;
console.log(data1, data2, data3);
});
});
});

// Promises
const fsPromises = require('fs').promises;

fsPromises.readFile('file.txt', 'utf8')
.then(data => console.log(data))
.catch(error => console.error(error));

// Promise chain
fsPromises.readFile('file1.txt', 'utf8')
.then(data1 => {
console.log(data1);
return fsPromises.readFile('file2.txt', 'utf8');
})
.then(data2 => {
console.log(data2);
return fsPromises.readFile('file3.txt', 'utf8');
})
.then(data3 => {
console.log(data3);
})
.catch(error => console.error(error));

// Async/await (modern)
async function readFiles() {
try {
const data1 = await fsPromises.readFile('file1.txt', 'utf8');
const data2 = await fsPromises.readFile('file2.txt', 'utf8');
const data3 = await fsPromises.readFile('file3.txt', 'utf8');
console.log(data1, data2, data3);
} catch (error) {
console.error(error);
}
}

// Parallel execution
async function readFilesParallel() {
try {
const [data1, data2, data3] = await Promise.all([
fsPromises.readFile('file1.txt', 'utf8'),
fsPromises.readFile('file2.txt', 'utf8'),
fsPromises.readFile('file3.txt', 'utf8')
]);
console.log(data1, data2, data3);
} catch (error) {
console.error(error);
}
}

Summary

In this module, you learned:

  • ✅ Node.js basics and global objects
  • ✅ Core modules: fs, path, http, events
  • ✅ Express.js framework
  • ✅ Building REST APIs
  • ✅ Middleware and routing
  • ✅ Streams and buffers
  • ✅ Async patterns in Node.js
  • ✅ Event-driven architecture
Next Steps

In Module 33, you'll learn about npm and Package Management, managing dependencies and publishing packages.


Practice Exercises

  1. Build a file-based todo API
  2. Create a static file server
  3. Implement user authentication with JWT
  4. Build a real-time chat server with WebSockets
  5. Create a file upload service
  6. Build a logging middleware
  7. Implement rate limiting
  8. Create a command-line tool

Additional Resources