Node.js: Server-Side JavaScript Essentials
Complete guide to Node.js development — setup, file system, streams, events, and best practices
Introduction
Node.js is a JavaScript runtime built on Chrome's V8 engine that lets you run JavaScript on the server. It's event-driven, non-blocking, and perfect for building scalable network applications, APIs, and tools.
Getting Started
Installation
# Check if Node.js is installed
node --version
npm --version
# Install via nvm (recommended)
nvm install 22
nvm use 22
Your First Server
import http from 'node:http';
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'Hello from Node.js!' }));
});
server.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
Core Modules
File System (fs)
import fs from 'node:fs';
import fsPromises from 'node:fs/promises';
// Synchronous (blocking — avoid in servers)
const data = fs.readFileSync('file.txt', 'utf8');
// Callback-based
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
// Promise-based (preferred)
async function readFile() {
try {
const data = await fsPromises.readFile('file.txt', 'utf8');
console.log(data);
} catch (error) {
console.error('Error reading file:', error);
}
}
// Write files
await fsPromises.writeFile('output.txt', 'Hello World');
// Append to files
await fsPromises.appendFile('log.txt', 'New log entry\n');
// Check existence
import { existsSync } from 'node:fs';
if (existsSync('file.txt')) {
// File exists
}
// Directory operations
await fsPromises.mkdir('new-folder', { recursive: true });
const files = await fsPromises.readdir('folder');
Path
import path from 'node:path';
// Join paths safely
const fullPath = path.join('users', 'alice', 'documents', 'file.txt');
// 'users/alice/documents/file.txt' (or with backslashes on Windows)
// Get file extension
path.extname('file.txt'); // '.txt'
// Get directory name
path.dirname('/users/alice/file.txt'); // '/users/alice'
// Get file name
path.basename('/users/alice/file.txt'); // 'file.txt'
// Resolve relative paths
path.resolve('src', 'index.js'); // Absolute path
// Parse path
path.parse('/users/alice/file.txt');
// { root: '/', dir: '/users/alice', base: 'file.txt', ext: '.txt', name: 'file' }
Events
import { EventEmitter } from 'node:events';
class MyEmitter extends EventEmitter {}
const emitter = new MyEmitter();
// Register event listener
emitter.on('data', (payload) => {
console.log('Data received:', payload);
});
// One-time listener
emitter.once('connect', () => {
console.log('Connected (only once)');
});
// Emit event
emitter.emit('data', { id: 1, value: 'test' });
// Remove listener
const handler = (data) => console.log(data);
emitter.on('event', handler);
emitter.off('event', handler);
// Error handling
emitter.on('error', (error) => {
console.error('Error:', error);
});
Streams
import fs from 'node:fs';
import { Transform, pipeline } from 'node:stream';
import { promisify } from 'node:util';
const pipelineAsync = promisify(pipeline);
// Read stream
const readStream = fs.createReadStream('input.txt', { encoding: 'utf8' });
readStream.on('data', (chunk) => console.log(chunk));
readStream.on('end', () => console.log('Done reading'));
// Write stream
const writeStream = fs.createWriteStream('output.txt');
writeStream.write('Hello\n');
writeStream.end('World');
// Transform stream
const upperCaseTransform = new Transform({
transform(chunk, encoding, callback) {
callback(null, chunk.toString().toUpperCase());
}
});
// Pipe streams together
await pipelineAsync(
fs.createReadStream('input.txt'),
upperCaseTransform,
fs.createWriteStream('output.txt')
);
npm and Package Management
# Initialize project
npm init -y
# Install dependencies
npm install express
npm install --save-dev typescript
# Run scripts
npm run build
npm test
# Update packages
npm update
npm outdated
# Audit security
npm audit
npm audit fix
Environment Variables
// Access environment variables
const port = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;
// Load from .env file
import 'dotenv/config';
// Now process.env has values from .env
// .env file (never commit!)
// DATABASE_URL=postgresql://localhost/mydb
// API_KEY=secret-key-here
Error Handling
// Try-catch for async/await
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
console.error('Fetch failed:', error.message);
throw error; // Re-throw or handle
}
}
// Uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
process.exit(1);
});
// Unhandled promise rejections
process.on('unhandledRejection', (reason) => {
console.error('Unhandled rejection:', reason);
});
Best Practices
Do's
// ✅ Use node: prefix for built-in modules
import fs from 'node:fs';
import path from 'node:path';
// ✅ Use async/await over callbacks
const data = await fsPromises.readFile('file.txt', 'utf8');
// ✅ Handle errors properly
try { /* ... */ } catch (error) { /* ... */ }
// ✅ Use environment variables for config
const port = process.env.PORT || 3000;
// ✅ Use streams for large files
pipeline(readStream, transform, writeStream);
Don'ts
// ❌ Don't use sync methods in servers
fs.readFileSync('file.txt'); // Blocks the event loop!
// ❌ Don't hardcode secrets
const apiKey = 'sk-1234567890'; // Use env vars!
// ❌ Don't ignore errors
fs.readFile('file.txt', (err, data) => {
console.log(data); // What if err?
});
// ❌ Don't use callback hell
fs.readFile('a.txt', (err, a) => {
fs.readFile('b.txt', (err, b) => {
fs.readFile('c.txt', (err, c) => {
// Deep nesting!
});
});
});
Summary Table
| Module | Purpose | Key Methods |
|---|---|---|
fs | File system | readFile, writeFile, readdir |
path | Path utilities | join, resolve, extname |
events | Event handling | on, emit, once |
stream | Streaming data | Readable, Writable, Transform |
http | HTTP server/client | createServer, request |
process | Process info | env, argv, exit |
Conclusion
Node.js brings JavaScript to the server with:
- Non-blocking I/O — Handle thousands of concurrent connections
- Event-driven architecture — Efficient and scalable
- Rich standard library — File system, streams, networking, crypto
- Massive ecosystem — npm with millions of packages
- Full-stack JavaScript — Same language on frontend and backend
Master the core modules, embrace async/await, and follow best practices for production-ready Node.js applications.
#nodejs #javascript #backend #server #web-development