Node.js Fundamentals: Server-Side JavaScript Development
Master Node.js fundamentals for backend development. Learn modules, file system, HTTP servers, streams, process management, and building scalable applications.
Node.js enables JavaScript to run on the server, opening up full-stack development possibilities. This comprehensive guide covers Node.js fundamentals, core modules, and patterns for building scalable backend applications.
Node.js Basics
Understanding Node.js Architecture
// Node.js is built on Chrome's V8 JavaScript engine
// Single-threaded event loop with non-blocking I/O
// Event loop demonstration
console.log('Start');
setTimeout(() => {
console.log('Timeout callback');
}, 0);
setImmediate(() => {
console.log('Immediate callback');
});
process.nextTick(() => {
console.log('Next tick callback');
});
console.log('End');
// Output order:
// Start
// End
// Next tick callback
// Immediate callback
// Timeout callback
// Global objects in Node.js
console.log('Current directory:', __dirname);
console.log('Current file:', __filename);
console.log('Process ID:', process.pid);
console.log('Node version:', process.version);
console.log('Platform:', process.platform);
// Environment variables
console.log('Environment:', process.env.NODE_ENV);
console.log('Port:', process.env.PORT || 3000);
// Command line arguments
console.log('Arguments:', process.argv);
// Process events
process.on('exit', (code) => {
console.log(`Process exiting with code: ${code}`);
});
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection at:', promise, 'reason:', reason);
});
Module System
CommonJS Modules
// math.js - Module exports
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
// Named exports
exports.add = add;
exports.subtract = subtract;
// Default export
module.exports = {
add,
subtract,
multiply,
divide,
PI: Math.PI,
calculateCircleArea: (radius) => Math.PI * radius * radius,
};
// Alternative export syntax
module.exports.VERSION = '1.0.0';
// app.js - Module imports
const math = require('./math');
const { add, subtract } = require('./math');
console.log(math.add(5, 3)); // 8
console.log(add(5, 3)); // 8
console.log(math.PI); // 3.141592653589793
// Built-in modules
const fs = require('fs');
const path = require('path');
const http = require('http');
const url = require('url');
const querystring = require('querystring');
// Module caching
console.log('First require:', require('./math'));
console.log('Second require (cached):', require('./math'));
// require.cache contains cached modules
console.log('Cached modules:', Object.keys(require.cache));
// Clear module cache (for testing)
delete require.cache[require.resolve('./math')];
// Module search paths
console.log('Module paths:', require.main.paths);
ES Modules in Node.js
// package.json - Enable ES modules
{
"type": "module",
"name": "my-app",
"version": "1.0.0"
}
// math.mjs - ES module exports
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export const PI = Math.PI;
export default class Calculator {
constructor() {
this.history = [];
}
add(a, b) {
const result = a + b;
this.history.push(`${a} + ${b} = ${result}`);
return result;
}
getHistory() {
return this.history;
}
}
// app.mjs - ES module imports
import Calculator, { add, subtract, PI } from './math.mjs';
import { readFile } from 'fs/promises';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
// ES module equivalents of __dirname and __filename
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log('Current directory:', __dirname);
console.log('Current file:', __filename);
// Dynamic imports
async function loadMathModule() {
const mathModule = await import('./math.mjs');
return mathModule;
}
// Top-level await (Node.js 14.8+)
const data = await readFile('config.json', 'utf8');
console.log('Config:', JSON.parse(data));
// Mixed CommonJS and ES modules
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const lodash = require('lodash'); // CommonJS module
File System Operations
Synchronous and Asynchronous File Operations
const fs = require('fs');
const fsPromises = require('fs/promises');
const path = require('path');
// Synchronous file operations (blocking)
try {
const data = fs.readFileSync('config.json', 'utf8');
console.log('Config:', JSON.parse(data));
} catch (error) {
console.error('Error reading file:', error.message);
}
// Asynchronous file operations (callback-based)
fs.readFile('config.json', 'utf8', (error, data) => {
if (error) {
console.error('Error reading file:', error.message);
return;
}
try {
const config = JSON.parse(data);
console.log('Config:', config);
} catch (parseError) {
console.error('Error parsing JSON:', parseError.message);
}
});
// Promise-based file operations
async function readConfigFile() {
try {
const data = await fsPromises.readFile('config.json', 'utf8');
const config = JSON.parse(data);
return config;
} catch (error) {
console.error('Error reading config:', error.message);
throw error;
}
}
// File operations class
class FileManager {
constructor(baseDir = './data') {
this.baseDir = baseDir;
this.ensureDirectory();
}
async ensureDirectory() {
try {
await fsPromises.mkdir(this.baseDir, { recursive: true });
} catch (error) {
console.error('Error creating directory:', error.message);
}
}
async writeFile(filename, data) {
const filePath = path.join(this.baseDir, filename);
try {
await fsPromises.writeFile(filePath, data, 'utf8');
console.log(`File written: ${filePath}`);
} catch (error) {
console.error(`Error writing file ${filename}:`, error.message);
throw error;
}
}
async readFile(filename) {
const filePath = path.join(this.baseDir, filename);
try {
const data = await fsPromises.readFile(filePath, 'utf8');
return data;
} catch (error) {
if (error.code === 'ENOENT') {
throw new Error(`File not found: ${filename}`);
}
throw error;
}
}
async deleteFile(filename) {
const filePath = path.join(this.baseDir, filename);
try {
await fsPromises.unlink(filePath);
console.log(`File deleted: ${filePath}`);
} catch (error) {
if (error.code === 'ENOENT') {
console.log(`File does not exist: ${filename}`);
return;
}
throw error;
}
}
async listFiles() {
try {
const files = await fsPromises.readdir(this.baseDir);
const fileStats = await Promise.all(
files.map(async (file) => {
const filePath = path.join(this.baseDir, file);
const stats = await fsPromises.stat(filePath);
return {
name: file,
size: stats.size,
isDirectory: stats.isDirectory(),
modified: stats.mtime,
created: stats.birthtime,
};
})
);
return fileStats;
} catch (error) {
console.error('Error listing files:', error.message);
throw error;
}
}
async copyFile(source, destination) {
const sourcePath = path.join(this.baseDir, source);
const destPath = path.join(this.baseDir, destination);
try {
await fsPromises.copyFile(sourcePath, destPath);
console.log(`File copied: ${source} -> ${destination}`);
} catch (error) {
console.error(`Error copying file:`, error.message);
throw error;
}
}
async watchFile(filename, callback) {
const filePath = path.join(this.baseDir, filename);
const watcher = fs.watch(filePath, (eventType, changedFilename) => {
console.log(`File ${changedFilename} changed: ${eventType}`);
callback(eventType, changedFilename);
});
return watcher;
}
}
// Usage example
async function demonstrateFileOperations() {
const fileManager = new FileManager('./data');
try {
// Write a file
await fileManager.writeFile('test.txt', 'Hello, Node.js!');
// Read the file
const content = await fileManager.readFile('test.txt');
console.log('File content:', content);
// List all files
const files = await fileManager.listFiles();
console.log('Files:', files);
// Copy the file
await fileManager.copyFile('test.txt', 'test-copy.txt');
// Watch for changes
const watcher = await fileManager.watchFile('test.txt', (eventType) => {
console.log(`File event: ${eventType}`);
});
// Clean up
setTimeout(() => {
watcher.close();
}, 5000);
} catch (error) {
console.error('File operation failed:', error.message);
}
}
HTTP Server Development
Basic HTTP Server
const http = require('http');
const url = require('url');
const querystring = require('querystring');
// Basic HTTP server
const server = http.createServer((req, res) => {
const { method, url: requestUrl, headers } = req;
const parsedUrl = url.parse(requestUrl, true);
const { pathname, query } = parsedUrl;
console.log(`${method} ${pathname}`);
// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
// Handle preflight requests
if (method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
// Route handling
if (pathname === '/') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>Welcome to Node.js Server</h1>');
} else if (pathname === '/api/users' && method === 'GET') {
handleGetUsers(req, res, query);
} else if (pathname === '/api/users' && method === 'POST') {
handleCreateUser(req, res);
} else if (pathname.startsWith('/api/users/') && method === 'GET') {
const userId = pathname.split('/')[3];
handleGetUser(req, res, userId);
} else {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Not found' }));
}
});
// Mock database
let users = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' },
];
function handleGetUsers(req, res, query) {
const { page = 1, limit = 10, search } = query;
let filteredUsers = users;
if (search) {
filteredUsers = users.filter(
(user) =>
user.name.toLowerCase().includes(search.toLowerCase()) ||
user.email.toLowerCase().includes(search.toLowerCase())
);
}
const startIndex = (page - 1) * limit;
const endIndex = startIndex + parseInt(limit);
const paginatedUsers = filteredUsers.slice(startIndex, endIndex);
const response = {
users: paginatedUsers,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: filteredUsers.length,
pages: Math.ceil(filteredUsers.length / limit),
},
};
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(response));
}
function handleGetUser(req, res, userId) {
const user = users.find((u) => u.id === parseInt(userId));
if (!user) {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'User not found' }));
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(user));
}
function handleCreateUser(req, res) {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
try {
const userData = JSON.parse(body);
if (!userData.name || !userData.email) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Name and email are required' }));
return;
}
const newUser = {
id: users.length + 1,
name: userData.name,
email: userData.email,
};
users.push(newUser);
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(newUser));
} catch (error) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid JSON' }));
}
});
req.on('error', (error) => {
console.error('Request error:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Internal server error' }));
});
}
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
Advanced HTTP Server with Express-like Features
// Simple Express-like framework
class SimpleExpress {
constructor() {
this.routes = {
GET: {},
POST: {},
PUT: {},
DELETE: {},
};
this.middleware = [];
}
use(middleware) {
this.middleware.push(middleware);
}
get(path, handler) {
this.routes.GET[path] = handler;
}
post(path, handler) {
this.routes.POST[path] = handler;
}
put(path, handler) {
this.routes.PUT[path] = handler;
}
delete(path, handler) {
this.routes.DELETE[path] = handler;
}
async handleRequest(req, res) {
// Add helper methods to response
res.json = (data) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(data));
};
res.status = (code) => {
res.statusCode = code;
return res;
};
// Parse request body for POST/PUT requests
if (req.method === 'POST' || req.method === 'PUT') {
await this.parseBody(req);
}
// Run middleware
for (const middleware of this.middleware) {
await new Promise((resolve, reject) => {
middleware(req, res, (error) => {
if (error) reject(error);
else resolve();
});
});
}
// Find and execute route handler
const handler = this.routes[req.method]?.[req.url];
if (handler) {
try {
await handler(req, res);
} catch (error) {
console.error('Route handler error:', error);
res.status(500).json({ error: 'Internal server error' });
}
} else {
res.status(404).json({ error: 'Not found' });
}
}
async parseBody(req) {
return new Promise((resolve) => {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
try {
req.body = JSON.parse(body);
} catch (error) {
req.body = {};
}
resolve();
});
});
}
listen(port, callback) {
const server = http.createServer((req, res) => {
this.handleRequest(req, res);
});
server.listen(port, callback);
return server;
}
}
// Usage example
const app = new SimpleExpress();
// Middleware
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
});
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
next();
});
// Routes
app.get('/', (req, res) => {
res.json({ message: 'Welcome to Simple Express!' });
});
app.get('/api/users', (req, res) => {
res.json({ users: users });
});
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'Name and email are required' });
}
const newUser = {
id: users.length + 1,
name,
email,
};
users.push(newUser);
res.status(201).json(newUser);
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Streams and Buffers
Working with Streams
const fs = require('fs');
const { Readable, Writable, Transform, pipeline } = require('stream');
const { promisify } = require('util');
// Readable stream
class NumberStream extends Readable {
constructor(options) {
super(options);
this.current = 1;
this.max = options.max || 10;
}
_read() {
if (this.current <= this.max) {
this.push(`Number: ${this.current}\n`);
this.current++;
} else {
this.push(null); // End the stream
}
}
}
// Writable stream
class LogStream extends Writable {
constructor(options) {
super(options);
this.filename = options.filename || 'output.log';
}
_write(chunk, encoding, callback) {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] ${chunk.toString()}`;
fs.appendFile(this.filename, logEntry, callback);
}
}
// Transform stream
class UpperCaseTransform extends Transform {
_transform(chunk, encoding, callback) {
const transformed = chunk.toString().toUpperCase();
callback(null, transformed);
}
}
// File processing with streams
class FileProcessor {
static async processLargeFile(inputFile, outputFile) {
const readStream = fs.createReadStream(inputFile, { encoding: 'utf8' });
const writeStream = fs.createWriteStream(outputFile);
const transform = new UpperCaseTransform();
const pipelineAsync = promisify(pipeline);
try {
await pipelineAsync(readStream, transform, writeStream);
console.log('File processing completed');
} catch (error) {
console.error('File processing failed:', error);
}
}
static async copyFileWithProgress(source, destination) {
const stats = await fs.promises.stat(source);
const totalSize = stats.size;
let processedSize = 0;
const readStream = fs.createReadStream(source);
const writeStream = fs.createWriteStream(destination);
readStream.on('data', (chunk) => {
processedSize += chunk.length;
const progress = ((processedSize / totalSize) * 100).toFixed(2);
process.stdout.write(`\rProgress: ${progress}%`);
});
readStream.on('end', () => {
console.log('\nFile copy completed');
});
readStream.pipe(writeStream);
}
static createBackupStream(filename) {
const readStream = fs.createReadStream(filename);
const backupStream = fs.createWriteStream(`${filename}.backup`);
readStream.pipe(backupStream);
return new Promise((resolve, reject) => {
backupStream.on('finish', resolve);
backupStream.on('error', reject);
});
}
}
// Stream utilities
class StreamUtils {
static async streamToString(stream) {
const chunks = [];
return new Promise((resolve, reject) => {
stream.on('data', (chunk) => chunks.push(chunk));
stream.on('error', reject);
stream.on('end', () => resolve(Buffer.concat(chunks).toString()));
});
}
static createLineStream() {
let buffer = '';
return new Transform({
transform(chunk, encoding, callback) {
buffer += chunk.toString();
const lines = buffer.split('\n');
// Keep the last incomplete line in buffer
buffer = lines.pop();
// Emit complete lines
for (const line of lines) {
this.push(line + '\n');
}
callback();
},
flush(callback) {
if (buffer) {
this.push(buffer);
}
callback();
},
});
}
static createThrottleStream(delay = 100) {
return new Transform({
transform(chunk, encoding, callback) {
setTimeout(() => {
callback(null, chunk);
}, delay);
},
});
}
}
// Buffer operations
class BufferUtils {
static createBuffer(data, encoding = 'utf8') {
return Buffer.from(data, encoding);
}
static concatenateBuffers(buffers) {
return Buffer.concat(buffers);
}
static compareBuffers(buf1, buf2) {
return Buffer.compare(buf1, buf2);
}
static bufferToHex(buffer) {
return buffer.toString('hex');
}
static hexToBuffer(hex) {
return Buffer.from(hex, 'hex');
}
static bufferToBase64(buffer) {
return buffer.toString('base64');
}
static base64ToBuffer(base64) {
return Buffer.from(base64, 'base64');
}
}
Process Management and Child Processes
const { spawn, exec, execFile, fork } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
const execFileAsync = promisify(execFile);
// Process management
class ProcessManager {
constructor() {
this.processes = new Map();
}
async executeCommand(command, options = {}) {
try {
const { stdout, stderr } = await execAsync(command, options);
return { stdout, stderr, success: true };
} catch (error) {
return {
stdout: error.stdout,
stderr: error.stderr,
success: false,
error: error.message,
};
}
}
spawnProcess(command, args = [], options = {}) {
const child = spawn(command, args, {
stdio: 'pipe',
...options,
});
const processId = Date.now() + Math.random();
this.processes.set(processId, child);
child.stdout.on('data', (data) => {
console.log(`[${processId}] stdout: ${data}`);
});
child.stderr.on('data', (data) => {
console.error(`[${processId}] stderr: ${data}`);
});
child.on('close', (code) => {
console.log(`[${processId}] Process exited with code ${code}`);
this.processes.delete(processId);
});
child.on('error', (error) => {
console.error(`[${processId}] Process error:`, error);
this.processes.delete(processId);
});
return { processId, child };
}
killProcess(processId) {
const child = this.processes.get(processId);
if (child) {
child.kill('SIGTERM');
return true;
}
return false;
}
killAllProcesses() {
for (const [processId, child] of this.processes) {
child.kill('SIGTERM');
}
this.processes.clear();
}
// Fork Node.js processes
forkWorker(scriptPath, args = [], options = {}) {
const child = fork(scriptPath, args, options);
child.on('message', (message) => {
console.log('Received from worker:', message);
});
child.on('error', (error) => {
console.error('Worker error:', error);
});
child.on('exit', (code, signal) => {
console.log(`Worker exited with code ${code} and signal ${signal}`);
});
return child;
}
}
// Worker script example (worker.js)
/*
process.on('message', (message) => {
console.log('Worker received:', message);
if (message.type === 'compute') {
const result = heavyComputation(message.data);
process.send({ type: 'result', data: result });
}
});
function heavyComputation(data) {
// Simulate heavy computation
let result = 0;
for (let i = 0; i < data.iterations; i++) {
result += Math.random();
}
return result;
}
process.send({ type: 'ready' });
*/
// Cluster management
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
// Fork workers
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`);
// Restart worker
cluster.fork();
});
cluster.on('online', (worker) => {
console.log(`Worker ${worker.process.pid} is online`);
});
} else {
// Worker process
const server = http.createServer((req, res) => {
res.writeHead(200);
res.end(`Hello from worker ${process.pid}\n`);
});
server.listen(3000, () => {
console.log(`Worker ${process.pid} started`);
});
}
// Process monitoring
class ProcessMonitor {
constructor() {
this.startTime = Date.now();
this.setupMonitoring();
}
setupMonitoring() {
// Memory usage monitoring
setInterval(() => {
const memUsage = process.memoryUsage();
console.log('Memory usage:', {
rss: `${(memUsage.rss / 1024 / 1024).toFixed(2)} MB`,
heapTotal: `${(memUsage.heapTotal / 1024 / 1024).toFixed(2)} MB`,
heapUsed: `${(memUsage.heapUsed / 1024 / 1024).toFixed(2)} MB`,
external: `${(memUsage.external / 1024 / 1024).toFixed(2)} MB`,
});
}, 30000); // Every 30 seconds
// CPU usage monitoring
setInterval(() => {
const cpuUsage = process.cpuUsage();
console.log('CPU usage:', {
user: `${(cpuUsage.user / 1000).toFixed(2)} ms`,
system: `${(cpuUsage.system / 1000).toFixed(2)} ms`,
});
}, 30000);
}
getUptime() {
return Date.now() - this.startTime;
}
getSystemInfo() {
return {
platform: process.platform,
arch: process.arch,
nodeVersion: process.version,
pid: process.pid,
ppid: process.ppid,
uptime: this.getUptime(),
workingDirectory: process.cwd(),
execPath: process.execPath,
};
}
}
// Signal handling
process.on('SIGINT', () => {
console.log('Received SIGINT, performing graceful shutdown...');
// Perform cleanup operations
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('Received SIGTERM, performing graceful shutdown...');
// Perform cleanup operations
process.exit(0);
});
// Uncaught exception handling
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
// Log error and exit gracefully
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
// Log error but don't exit immediately
});
Node.js Best Practices
// Error handling patterns
class ErrorHandler {
static createError(message, statusCode = 500, code = 'INTERNAL_ERROR') {
const error = new Error(message);
error.statusCode = statusCode;
error.code = code;
return error;
}
static asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
static globalErrorHandler(error, req, res, next) {
const statusCode = error.statusCode || 500;
const message = error.message || 'Internal Server Error';
console.error('Error:', {
message,
stack: error.stack,
url: req.url,
method: req.method,
timestamp: new Date().toISOString(),
});
res.status(statusCode).json({
error: {
message,
code: error.code,
...(process.env.NODE_ENV === 'development' && { stack: error.stack }),
},
});
}
}
// Configuration management
class Config {
constructor() {
this.config = {
port: process.env.PORT || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
database: {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
name: process.env.DB_NAME || 'myapp',
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || '',
},
jwt: {
secret: process.env.JWT_SECRET || 'default-secret',
expiresIn: process.env.JWT_EXPIRES_IN || '7d',
},
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
},
};
this.validate();
}
validate() {
const required = ['JWT_SECRET', 'DB_PASSWORD'];
const missing = required.filter((key) => !process.env[key]);
if (missing.length > 0) {
throw new Error(
`Missing required environment variables: ${missing.join(', ')}`
);
}
}
get(key) {
return this.config[key];
}
isDevelopment() {
return this.config.nodeEnv === 'development';
}
isProduction() {
return this.config.nodeEnv === 'production';
}
}
// Logging utility
class Logger {
constructor() {
this.logLevel = process.env.LOG_LEVEL || 'info';
this.levels = {
error: 0,
warn: 1,
info: 2,
debug: 3,
};
}
log(level, message, meta = {}) {
if (this.levels[level] <= this.levels[this.logLevel]) {
const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
...meta,
};
console.log(JSON.stringify(logEntry));
}
}
error(message, meta) {
this.log('error', message, meta);
}
warn(message, meta) {
this.log('warn', message, meta);
}
info(message, meta) {
this.log('info', message, meta);
}
debug(message, meta) {
this.log('debug', message, meta);
}
}
// Database connection pool
class DatabasePool {
constructor(config) {
this.config = config;
this.connections = [];
this.maxConnections = config.maxConnections || 10;
this.activeConnections = 0;
}
async getConnection() {
if (this.connections.length > 0) {
return this.connections.pop();
}
if (this.activeConnections < this.maxConnections) {
const connection = await this.createConnection();
this.activeConnections++;
return connection;
}
// Wait for available connection
return new Promise((resolve) => {
const checkForConnection = () => {
if (this.connections.length > 0) {
resolve(this.connections.pop());
} else {
setTimeout(checkForConnection, 50);
}
};
checkForConnection();
});
}
releaseConnection(connection) {
this.connections.push(connection);
}
async createConnection() {
// Implement actual database connection
return {
query: async (sql, params) => {
// Mock query implementation
console.log('Executing query:', sql, params);
return { rows: [] };
},
close: () => {
this.activeConnections--;
},
};
}
async closeAll() {
this.connections.forEach((conn) => conn.close());
this.connections = [];
this.activeConnections = 0;
}
}
// Application lifecycle management
class Application {
constructor() {
this.config = new Config();
this.logger = new Logger();
this.isShuttingDown = false;
this.setupGracefulShutdown();
}
setupGracefulShutdown() {
const gracefulShutdown = async (signal) => {
if (this.isShuttingDown) return;
this.logger.info(`Received ${signal}, starting graceful shutdown`);
this.isShuttingDown = true;
try {
// Close server
if (this.server) {
await new Promise((resolve) => {
this.server.close(resolve);
});
}
// Close database connections
if (this.database) {
await this.database.closeAll();
}
this.logger.info('Graceful shutdown completed');
process.exit(0);
} catch (error) {
this.logger.error('Error during shutdown:', { error: error.message });
process.exit(1);
}
};
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
}
async start() {
try {
this.logger.info('Starting application');
// Initialize database
this.database = new DatabasePool(this.config.get('database'));
// Start server
this.server = http.createServer(this.requestHandler.bind(this));
const port = this.config.get('port');
this.server.listen(port, () => {
this.logger.info(`Server started on port ${port}`);
});
} catch (error) {
this.logger.error('Failed to start application:', {
error: error.message,
});
process.exit(1);
}
}
async requestHandler(req, res) {
// Basic request handling
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(
JSON.stringify({
message: 'Node.js application running',
timestamp: new Date().toISOString(),
})
);
}
}
// Start application
const app = new Application();
app.start();
Conclusion
Node.js fundamentals provide the foundation for building scalable server-side JavaScript applications. Understanding the event loop, module system, file operations, HTTP servers, streams, and process management enables you to create robust backend services. By following best practices for error handling, configuration management, logging, and application lifecycle, you can build production-ready Node.js applications that are maintainable, scalable, and reliable.