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
- Build a file-based todo API
- Create a static file server
- Implement user authentication with JWT
- Build a real-time chat server with WebSockets
- Create a file upload service
- Build a logging middleware
- Implement rate limiting
- Create a command-line tool