Node.js Documentation
Complete guide to Node.js from basics to advanced
What is Node.js?
Node.js is an open-source, cross-platform JavaScript runtime environment that executes JavaScript code outside a web browser. It allows developers to use JavaScript to write command line tools and for server-side scripting.
Key Features
- Asynchronous and Event-Driven - All APIs are asynchronous and non-blocking
- Single-Threaded - Uses a single thread with event looping for scalability
- High Performance - Built on Google Chrome's V8 JavaScript engine
- NPM Ecosystem - Largest ecosystem of open-source libraries
- Cross-Platform - Runs on Windows, macOS, and Linux
Why Node.js?
Performance Benefits
Node.js's event-driven, non-blocking I/O model makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.
Full-Stack JavaScript
With Node.js, you can use JavaScript for both frontend and backend development, simplifying the development process and reducing context switching.
Rich Ecosystem
Node Package Manager (NPM) provides access to thousands of libraries, making development faster and more efficient.
Node.js vs Other Backend Technologies
| Feature | Node.js | Python (Django) | Java (Spring) | PHP (Laravel) |
|---|---|---|---|---|
| Performance | Excellent | Good | Excellent | Good |
| Learning Curve | Moderate | Easy | Steep | Easy |
| Scalability | Excellent | Good | Excellent | Good |
| Ecosystem | Excellent | Good | Good | Good |
Prerequisites
Before starting with Node.js, ensure you have:
- Basic knowledge of JavaScript
- Familiarity with command line/terminal
- Text editor or IDE (VS Code, WebStorm, etc.)
Installation Methods
Using Version Manager (Recommended)
# Install NVM (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# Reload your shell
source ~/.bashrc
# Install latest LTS version
nvm install --lts
# Use specific version
nvm use 18.17.0
# List installed versions
nvm ls
# Switch between versions
nvm use 16.20.0
Official Installer
Download the installer from the official website: nodejs.org
Verify Installation
# Check Node.js version
node --version
# Check npm version
npm --version
# Check npx version
npx --version
Setting Up Your Development Environment
# Initialize a new Node.js project
mkdir my-node-app
cd my-node-app
npm init -y
# Install Express.js for web applications
npm install express
# Install nodemon for auto-restarting during development
npm install --save-dev nodemon
# Create a basic server
echo "const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});" > server.js
# Add start script to package.json
npm pkg set scripts.start "nodemon server.js"
# Start the development server
npm start
Event Loop
The event loop is what allows Node.js to perform non-blocking I/O operations despite being single-threaded. It's the mechanism that takes callbacks (functions) and registers them to be executed at some later time.
// Example of event loop behavior
console.log('Start');
setTimeout(() => {
console.log('Timeout 1');
}, 0);
setTimeout(() => {
console.log('Timeout 2');
}, 0);
Promise.resolve().then(() => {
console.log('Promise 1');
});
Promise.resolve().then(() => {
console.log('Promise 2');
});
console.log('End');
// Output order:
// Start
// End
// Promise 1
// Promise 2
// Timeout 1
// Timeout 2
Modules
Node.js uses the CommonJS module system by default. Each file is treated as a separate module.
// math.js - Exporting functions
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
// Export individual functions
exports.add = add;
exports.subtract = subtract;
// Or export an object
module.exports = {
add,
subtract,
multiply: (a, b) => a * b
};
// app.js - Importing modules
const math = require('./math');
const { add, subtract } = require('./math');
console.log(add(5, 3)); // 8
console.log(subtract(5, 3)); // 2
console.log(math.multiply(5, 3)); // 15
Global Objects
// Global objects available in Node.js
console.log(__filename); // Current file path
console.log(__dirname); // Directory path of current file
console.log(process.cwd()); // Current working directory
// Process object
console.log(process.env.NODE_ENV); // Environment variables
console.log(process.argv); // Command line arguments
console.log(process.pid); // Process ID
console.log(process.platform); // Operating system platform
// Global functions
setTimeout(() => {
console.log('Delayed execution');
}, 1000);
setInterval(() => {
console.log('Repeated execution');
}, 1000);
// Buffer (Node.js specific)
const buf = Buffer.from('Hello World');
console.log(buf.toString()); // Hello World
Callback Pattern
Callbacks are functions passed as arguments to other functions, which are then invoked inside the outer function to complete some kind of routine or action.
// Synchronous callback
function greet(name, callback) {
console.log(`Hello, ${name}!`);
callback();
}
greet('Alice', () => {
console.log('Callback executed');
});
// Asynchronous callback
function fetchData(callback) {
setTimeout(() => {
callback('Data fetched');
}, 1000);
}
fetchData((data) => {
console.log(data);
});
// Callback hell (nested callbacks)
fs.readFile('file1.txt', 'utf8', (err, data1) => {
if (err) throw err;
fs.readFile('file2.txt', 'utf8', (err, data2) => {
if (err) throw err;
fs.readFile('file3.txt', 'utf8', (err, data3) => {
if (err) throw err;
console.log(data1, data2, data3);
});
});
});
Promises and Async/Await
Promises provide a cleaner way to handle asynchronous operations, avoiding callback hell.
// Creating a promise
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise resolved');
}, 1000);
});
// Using promises
myPromise
.then(result => console.log(result))
.catch(error => console.error(error));
// Async/await syntax
async function fetchData() {
try {
const data = await myPromise;
console.log(data);
} catch (error) {
console.error(error);
}
}
fetchData();
// Promise.all for parallel operations
const promise1 = Promise.resolve('One');
const promise2 = Promise.resolve('Two');
const promise3 = Promise.resolve('Three');
Promise.all([promise1, promise2, promise3])
.then(results => console.log(results))
.catch(error => console.error(error));
File System Module
Node.js provides a built-in fs module for file system operations. It offers both synchronous and asynchronous methods.
Synchronous vs Asynchronous
const fs = require('fs');
const path = require('path');
// Asynchronous operations (recommended)
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
console.log('File content:', data);
});
// Synchronous operations (blocking)
try {
const data = fs.readFileSync('example.txt', 'utf8');
console.log('File content:', data);
} catch (err) {
console.error('Error reading file:', err);
}
// Promise-based operations (fs.promises)
const fsPromises = require('fs').promises;
async function readFileAsync() {
try {
const data = await fsPromises.readFile('example.txt', 'utf8');
console.log('File content:', data);
} catch (err) {
console.error('Error reading file:', err);
}
}
readFileAsync();
File Operations
const fs = require('fs');
const path = require('path');
// Create directory
const dirPath = path.join(__dirname, 'new-directory');
fs.mkdir(dirPath, { recursive: true }, (err) => {
if (err) throw err;
console.log('Directory created');
});
// Write file
const filePath = path.join(dirPath, 'test.txt');
const content = 'Hello, Node.js!';
fs.writeFile(filePath, content, 'utf8', (err) => {
if (err) throw err;
console.log('File written');
});
// Read file
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) throw err;
console.log('File content:', data);
});
// Append to file
fs.appendFile(filePath, '\nAppended content', 'utf8', (err) => {
if (err) throw err;
console.log('Content appended');
});
// Delete file
fs.unlink(filePath, (err) => {
if (err) throw err;
console.log('File deleted');
});
Working with Directories
// Read directory contents
fs.readdir(__dirname, (err, files) => {
if (err) throw err;
console.log('Directory contents:', files);
// Filter only JavaScript files
const jsFiles = files.filter(file => path.extname(file) === '.js');
console.log('JavaScript files:', jsFiles);
});
// Create directory recursively
const createDirRecursive = (dirPath) => {
fs.mkdir(dirPath, { recursive: true }, (err) => {
if (err) throw err;
console.log(`Directory created: ${dirPath}`);
});
};
createDirRecursive(path.join(__dirname, 'nested', 'directory'));
// Watch directory for changes
fs.watch(__dirname, (eventType, filename) => {
console.log(`Event type: ${eventType}`);
console.log(`Filename: ${filename}`);
});
Path Operations
const path = require('path');
// Join path segments
const fullPath = path.join(__dirname, 'files', 'data.txt');
console.log(fullPath);
// Get directory name
const dir = path.dirname('/path/to/file.txt');
console.log(dir); // /path/to
// Get file extension
const ext = path.extname('/path/to/file.txt');
console.log(ext); // .txt
// Get file name
const filename = path.basename('/path/to/file.txt');
console.log(filename); // file.txt
// Parse path
const parsedPath = path.parse('/path/to/file.txt');
console.log(parsedPath);
// {
// root: '/',
// dir: '/path/to',
// base: 'file.txt',
// ext: '.txt',
// name: 'file'
// }
HTTP Module
Node.js has a built-in HTTP module to create HTTP servers and clients. It provides a low-level API for HTTP transactions.
Creating a Basic HTTP Server
const http = require('http');
const url = require('url');
// Create HTTP server
const server = http.createServer((req, res) => {
// Parse URL
const parsedUrl = url.parse(req.url, true);
const path = parsedUrl.pathname;
const method = req.method;
// Set response headers
res.setHeader('Content-Type', 'application/json');
// Basic routing
if (path === '/' && method === 'GET') {
res.writeHead(200);
res.end(JSON.stringify({ message: 'Welcome to Node.js Server!' }));
} else if (path === '/api/users' && method === 'GET') {
res.writeHead(200);
res.end(JSON.stringify([
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' }
]));
} else {
res.writeHead(404);
res.end(JSON.stringify({ error: 'Route not found' }));
}
});
// Start server
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
HTTP Client
const http = require('http');
const https = require('https');
// Making GET request
const options = {
hostname: 'jsonplaceholder.typicode.com',
path: '/posts/1',
method: 'GET'
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
console.log('Response:', JSON.parse(data));
});
});
req.on('error', (err) => {
console.error('Request error:', err);
});
req.end();
// Making POST request
const postData = JSON.stringify({
title: 'foo',
body: 'bar',
userId: 1
});
const postOptions = {
hostname: 'jsonplaceholder.typicode.com',
path: '/posts',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData)
}
};
const postReq = http.request(postOptions, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
console.log('Response:', JSON.parse(data));
});
});
postReq.on('error', (err) => {
console.error('Request error:', err);
});
postReq.write(postData);
postReq.end();
Request and Response Objects
const http = require('http');
const server = http.createServer((req, res) => {
// Request object properties
console.log('Method:', req.method);
console.log('URL:', req.url);
console.log('Headers:', req.headers);
console.log('HTTP Version:', req.httpVersion);
// Response methods
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.write('Hello World');
res.end();
});
server.listen(3000);
Handling Different HTTP Methods
const http = require('http');
const server = http.createServer((req, res) => {
const { method, url } = req;
if (method === 'GET' && url === '/') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('GET request received');
} else if (method === 'POST' && url === '/') {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(`POST request received with body: ${body}`);
});
} else if (method === 'PUT' && url === '/') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('PUT request received');
} else if (method === 'DELETE' && url === '/') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('DELETE request received');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('404 Not Found');
}
});
server.listen(3000);
Handling Request Body
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/api/data') {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
try {
const data = JSON.parse(body);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
message: 'Data received',
data
}));
} catch (error) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid JSON' }));
}
});
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('404 Not Found');
}
});
server.listen(3000);
Introduction to Express.js
Express.js is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. It simplifies the process of building web servers and APIs.
Setting up Express
# Initialize npm project
npm init -y
# Install Express
npm install express
# Install additional useful packages
npm install cors helmet morgan dotenv
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
// Routes
app.get('/', (req, res) => {
res.json({ message: 'Welcome to Express Server!' });
});
app.get('/api/users', (req, res) => {
const users = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' }
];
res.json(users);
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Express server running on port ${PORT}`);
});
Middleware
Middleware functions are functions that have access to the request object (req), the response object (res), and the next function in the application's request-response cycle.
// Application-level middleware
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
});
// Route-specific middleware
const requireAuth = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || authHeader !== 'Bearer secret-token') {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
};
app.get('/protected', requireAuth, (req, res) => {
res.json({ message: 'Protected data' });
});
// Error-handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
Routing
// Route parameters
app.get('/users/:id', (req, res) => {
const { id } = req.params;
res.json({ userId: id });
});
// Query parameters
app.get('/search', (req, res) => {
const { q, page = 1, limit = 10 } = req.query;
res.json({ query: q, page: parseInt(page), limit: parseInt(limit) });
});
// Router module
const express = require('express');
const router = express.Router();
router.get('/users', (req, res) => {
res.json([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]);
});
app.use('/api', router);
Static Files
const path = require('path');
// Serve static files from 'public' directory
app.use(express.static(path.join(__dirname, 'public')));
// Example: Access files
// http://localhost:3000/styles.css
// http://localhost:3000/js/app.js
// http://localhost:3000/images/logo.png
Introduction to Database Integration
Node.js can integrate with various databases including SQL (MySQL, PostgreSQL) and NoSQL (MongoDB, Redis) databases. Here we'll cover examples with MongoDB and MySQL.
MongoDB with Mongoose
# Install Mongoose
npm install mongoose
const mongoose = require('mongoose');
// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true
});
// Define schema
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
age: { type: Number, min: 0 },
createdAt: { type: Date, default: Date.now }
});
// Create model
const User = mongoose.model('User', userSchema);
// CRUD Operations
async function createUser(userData) {
try {
const user = new User(userData);
await user.save();
return user;
} catch (error) {
throw error;
}
}
// Express routes with Mongoose
const express = require('express');
const router = express.Router();
router.post('/users', async (req, res) => {
try {
const user = await createUser(req.body);
res.status(201).json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
module.exports = router;
MySQL with Sequelize
# Install Sequelize and MySQL driver
npm install sequelize mysql2
const { Sequelize, DataTypes } = require('sequelize');
// Initialize Sequelize
const sequelize = new Sequelize('myapp', 'username', 'password', {
host: 'localhost',
dialect: 'mysql'
});
// Define model
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
});
// Sync database
sequelize.sync({ force: false }).then(() => {
console.log('Database synchronized');
});
// CRUD Operations
async function createUser(userData) {
try {
const user = await User.create(userData);
return user;
} catch (error) {
throw error;
}
}
Redis for Caching
# Install Redis client
npm install redis
const redis = require('redis');
// Create Redis client
const client = redis.createClient({
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379
});
client.on('error', (err) => {
console.error('Redis Client Error', err);
});
client.on('connect', () => {
console.log('Connected to Redis');
});
// Set and get values
client.set('key', 'value', (err, reply) => {
if (err) throw err;
console.log('Value set:', reply);
});
client.get('key', (err, reply) => {
if (err) throw err;
console.log('Value:', reply);
});
// Cache middleware
const cache = (duration = 300) => {
return async (req, res, next) => {
const key = req.originalUrl;
try {
// Try to get data from cache
const cachedData = await client.get(key);
if (cachedData) {
return res.json(JSON.parse(cachedData));
}
// Override res.json to cache response
const originalJson = res.json;
res.json = function(data) {
// Cache the response
client.setex(key, duration, JSON.stringify(data));
return originalJson.call(this, data);
};
next();
} catch (error) {
next();
}
};
};
Authentication in Node.js
Authentication is the process of verifying the identity of a user or system. In Node.js applications, we commonly use JWT (JSON Web Tokens) for authentication.
JWT Authentication
# Install JWT and bcrypt
npm install jsonwebtoken bcryptjs
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
// JWT Secret
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
// Hash password
async function hashPassword(password) {
const saltRounds = 10;
return await bcrypt.hash(password, saltRounds);
}
// Compare password
async function comparePassword(password, hashedPassword) {
return await bcrypt.compare(password, hashedPassword);
}
// Generate JWT token
function generateToken(payload) {
return jwt.sign(payload, JWT_SECRET, { expiresIn: '24h' });
}
// Verify JWT token
function verifyToken(token) {
return jwt.verify(token, JWT_SECRET);
}
// Middleware to protect routes
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
try {
const decoded = verifyToken(token);
req.user = decoded;
next();
} catch (error) {
return res.status(403).json({ error: 'Invalid token' });
}
}
Security Best Practices
- Use HTTPS - Always use HTTPS in production to encrypt data in transit
- Validate Input - Always validate and sanitize user input to prevent injection attacks
- Rate Limiting - Implement rate limiting to prevent brute force attacks
- Security Headers - Use security headers like Helmet.js to set security-related HTTP headers
- Environment Variables - Store sensitive data in environment variables, not in code
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
// Security headers
app.use(helmet());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // Limit each IP to 100 requests per windowMs
});
app.use(limiter);
// Content Security Policy
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"]
}
}));
RESTful API Principles
REST (Representational State Transfer) is an architectural style for designing networked applications. It relies on a stateless, client-server protocol, cacheable communications, and a uniform interface.
HTTP Methods in REST
- GET - Retrieve data
- POST - Create new data
- PUT - Update existing data
- PATCH - Partially update data
- DELETE - Delete data
RESTful API Structure
// RESTful routes for users resource
// GET /api/users - Get all users
router.get('/users', async (req, res) => {
try {
const { page = 1, limit = 10, sort = 'createdAt', order = 'desc' } = req.query;
const offset = (page - 1) * limit;
const users = await User.findAndCountAll({
limit: parseInt(limit),
offset: parseInt(offset),
order: [[sort, order.toUpperCase()]]
});
res.json({
users: users.rows,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: users.count,
pages: Math.ceil(users.count / limit)
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// GET /api/users/:id - Get user by ID
router.get('/users/:id', async (req, res) => {
try {
const { id } = req.params;
const user = await User.findByPk(id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// POST /api/users - Create new user
router.post('/users', async (req, res) => {
try {
const { name, email, age } = req.body;
// Validation
if (!name || !email) {
return res.status(400).json({
error: 'Name and email are required'
});
}
// Check if user already exists
const existingUser = await User.findOne({ where: { email } });
if (existingUser) {
return res.status(409).json({
error: 'User with this email already exists'
});
}
const user = await User.create({ name, email, age });
res.status(201).json({
message: 'User created successfully',
user
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// PUT /api/users/:id - Update user
router.put('/users/:id', async (req, res) => {
try {
const { id } = req.params;
const { name, email, age } = req.body;
const user = await User.findByPk(id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
// Check if email is being changed and if it already exists
if (email && email !== user.email) {
const existingUser = await User.findOne({ where: { email } });
if (existingUser) {
return res.status(409).json({
error: 'User with this email already exists'
});
}
}
await user.update({ name, email, age });
res.json({
message: 'User updated successfully',
user
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// DELETE /api/users/:id - Delete user
router.delete('/users/:id', async (req, res) => {
try {
const { id } = req.params;
const user = await User.findByPk(id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
await user.destroy();
res.json({
message: 'User deleted successfully'
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
API Versioning
// Version 1 API
const v1Router = express.Router();
v1Router.get('/users', (req, res) => {
res.json({ version: 'v1', users: [] });
});
// Version 2 API
const v2Router = express.Router();
v2Router.get('/users', (req, res) => {
res.json({ version: 'v2', users: [], metadata: {} });
});
// Mount versioned routes
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
Error Handling in Node.js
Proper error handling is crucial for building robust Node.js applications. Node.js provides several mechanisms for handling errors, including try-catch blocks, error events, and error-first callbacks.
Synchronous Error Handling
try {
const result = someFunctionThatMightFail();
console.log(result);
} catch (error) {
console.error('Error:', error.message);
// Handle error
}
// Throwing errors
function validateUser(user) {
if (!user.name) {
throw new Error('Name is required');
}
if (!user.email) {
throw new Error('Email is required');
}
return user;
}
Asynchronous Error Handling
// Callback error handling
fs.readFile('nonexistent-file.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
console.log('File content:', data);
});
// Promise error handling
Promise.resolve()
.then(result => {
throw new Error('Something went wrong');
})
.catch(error => {
console.error('Error:', error.message);
});
// Async/await error handling
async function readFileAsync() {
try {
const data = await fs.promises.readFile('file.txt', 'utf8');
console.log('File content:', data);
} catch (error) {
console.error('Error:', error.message);
}
}
Custom Error Classes
// Custom error classes
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
class ValidationError extends AppError {
constructor(message) {
super(message, 400);
}
}
class NotFoundError extends AppError {
constructor(message = 'Resource not found') {
super(message, 404);
}
}
// Error handling middleware
const errorHandler = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
// Log error
console.error(err);
res.status(error.statusCode || 500).json({
success: false,
error: error.message || 'Server Error'
});
};
Global Error Handlers
// Uncaught exception handler
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
process.exit(1);
});
// Unhandled promise rejection handler
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
Testing in Node.js
Testing is essential for building reliable Node.js applications. Popular testing frameworks for Node.js include Jest, Mocha, and Supertest.
Setting up Testing Environment
# Install testing dependencies
npm install --save-dev jest supertest nodemon
# Install additional testing libraries
npm install --save-dev @types/jest eslint-plugin-jest
// package.json scripts
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
Unit Testing
// userController.js
const User = require('../models/User');
const { ValidationError } = require('../utils/errors');
exports.createUser = async (userData) => {
const { name, email } = userData;
if (!name || !email) {
throw new ValidationError('Name and email are required');
}
const existingUser = await User.findOne({ where: { email } });
if (existingUser) {
throw new ValidationError('User with this email already exists');
}
return await User.create(userData);
};
// userController.test.js
const { createUser } = require('../controllers/userController');
const User = require('../models/User');
const { ValidationError } = require('../utils/errors');
// Mock the User model
jest.mock('../models/User');
describe('User Controller', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('createUser', () => {
it('should create a user with valid data', async () => {
const userData = { name: 'John Doe', email: 'john@example.com' };
const mockUser = { id: 1, ...userData };
User.findOne.mockResolvedValue(null);
User.create.mockResolvedValue(mockUser);
const result = await createUser(userData);
expect(User.findOne).toHaveBeenCalledWith({ where: { email: userData.email } });
expect(User.create).toHaveBeenCalledWith(userData);
expect(result).toEqual(mockUser);
});
it('should throw ValidationError when name is missing', async () => {
const userData = { email: 'john@example.com' };
await expect(createUser(userData)).rejects.toThrow(ValidationError);
});
});
});
Integration Testing
// app.test.js
const request = require('supertest');
const app = require('../app');
const User = require('../models/User');
describe('User API', () => {
beforeEach(async () => {
await User.destroy({ where: {} });
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
age: 30
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body).toHaveProperty('message');
expect(response.body).toHaveProperty('user');
expect(response.body.user.name).toBe(userData.name);
});
});
});
Performance Optimization in Node.js
Optimizing Node.js applications is crucial for handling high traffic and ensuring scalability. Here are some key techniques:
Caching with Redis
const redis = require('redis');
// Create Redis client
const client = redis.createClient({
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379
});
// Cache middleware
const cache = (duration = 300) => {
return async (req, res, next) => {
const key = req.originalUrl;
try {
// Try to get data from cache
const cachedData = await client.get(key);
if (cachedData) {
return res.json(JSON.parse(cachedData));
}
// Override res.json to cache response
const originalJson = res.json;
res.json = function(data) {
// Cache the response
client.setex(key, duration, JSON.stringify(data));
return originalJson.call(this, data);
};
next();
} catch (error) {
next();
}
};
};
Compression
# Install compression middleware
npm install compression
const compression = require('compression');
// Enable compression for all requests
app.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
},
level: 6, // Compression level (1-9)
threshold: 1024, // Only compress responses larger than 1KB
windowBits: 15,
memLevel: 8
}));
Cluster Mode
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const numCPUs = os.cpus().length;
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`);
cluster.fork(); // Restart worker
});
} else {
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send(`Hello from Worker ${process.pid}`);
});
app.listen(PORT, () => {
console.log(`Worker ${process.pid} started`);
});
}
Streams in Node.js
Streams are objects that let you read data from a source or write data to a destination in a continuous fashion. In Node.js, streams are used for handling large files, network communications, and more.
Readable Streams
const fs = require('fs');
// Create readable stream
const readableStream = fs.createReadStream('large-file.txt', {
encoding: 'utf8',
highWaterMark: 1024 // Buffer size
});
// Handle stream events
readableStream.on('data', (chunk) => {
console.log('Received chunk:', chunk.length);
});
readableStream.on('end', () => {
console.log('Stream ended');
});
readableStream.on('error', (err) => {
console.error('Stream error:', err);
});
Writable Streams
const fs = require('fs');
// Create writable stream
const writableStream = fs.createWriteStream('output.txt');
// Write to stream
writableStream.write('Hello, World!');
writableStream.end();
Transform Streams
const { Transform } = require('stream');
// Uppercase transform stream
const uppercaseStream = new Transform({
transform(chunk, encoding, callback) {
this.push(chunk.toString().toUpperCase());
callback();
}
});
// Pipe streams
fs.createReadStream('input.txt')
.pipe(uppercaseStream)
.pipe(fs.createWriteStream('output.txt'));
Buffers
Buffers are fixed-size chunks of memory. They are used to handle binary data in Node.js.
// Creating buffers
const buf1 = Buffer.from('Hello World');
const buf2 = Buffer.alloc(10);
const buf3 = Buffer.allocUnsafe(10);
// Buffer operations
const buf = Buffer.from('Hello World');
console.log(buf.length); // 11
console.log(buf.toString()); // Hello World
// Buffer comparison
const bufA = Buffer.from('ABC');
const bufB = Buffer.from('ABC');
console.log(bufA.equals(bufB)); // true
Project Structure
A well-organized project structure is essential for maintainability and scalability.
// Recommended project structure
my-node-app/
├── src/
│ ├── controllers/ # Route handlers
│ ├── models/ # Database models
│ ├── routes/ # Route definitions
│ ├── middleware/ # Custom middleware
│ ├── services/ # Business logic
│ ├── utils/ # Utility functions
│ └── config/ # Configuration files
│ └── app.js # Express app setup
├── tests/ # Test files
├── docs/ # Documentation
├── logs/ # Log files
├── .env # Environment variables
├── .gitignore
├── package.json
├── server.js # Application entry point
└── README.md
Environment Configuration
// config/index.js
require('dotenv').config();
const config = {
development: {
port: process.env.PORT || 3000,
database: {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
name: process.env.DB_NAME || 'myapp_dev'
}
},
production: {
port: process.env.PORT || 80,
database: {
host: process.env.DB_HOST,
port: process.env.DB_PORT || 5432,
name: process.env.DB_NAME
}
}
};
const env = process.env.NODE_ENV || 'development';
module.exports = config[env];
Logging
const winston = require('winston');
// Create logger
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'user-service' },
transports: [
new winston.transports.Console({
format: winston.format.simple()
}),
new winston.transports.File({
filename: 'logs/combined.log',
level: 'error'
})
]
});
// Custom logger methods
logger.logRequest = (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info('HTTP Request', {
method: req.method,
url: req.url,
status: res.statusCode,
duration: `${duration}ms`
});
});
next();
};
Security Best Practices
- Use HTTPS in production
- Validate and sanitize all user input
- Implement rate limiting
- Use security headers like Helmet.js
- Store sensitive data in environment variables
- Keep dependencies updated
File Upload Service
# Install multer for file uploads
npm install multer
const multer = require('multer');
const path = require('path');
// Configure multer for file uploads
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadDir = 'uploads/';
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
cb(null, uploadDir);
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
}
});
// Single file upload
app.post('/upload', upload.single('file'), (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
res.json({
message: 'File uploaded successfully',
file: {
filename: req.file.filename,
originalname: req.file.originalname,
size: req.file.size,
mimetype: req.file.mimetype
}
});
});
Email Service
# Install nodemailer
npm install nodemailer
const nodemailer = require('nodemailer');
// Create transporter
const createTransporter = () => {
return nodemailer.createTransporter({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT || 587,
secure: false,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
});
};
// Send email
const sendEmail = async (options) => {
try {
const transporter = createTransporter();
const mailOptions = {
from: process.env.SMTP_FROM,
to: options.to,
subject: options.subject,
text: options.text,
html: options.html
};
const info = await transporter.sendMail(mailOptions);
console.log('Email sent: ' + info.messageId);
return info;
} catch (error) {
console.error('Error sending email:', error);
throw error;
}
};
WebSocket Chat Server
# Install socket.io
npm install socket.io
const http = require('http');
const socketIo = require('socket.io');
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: process.env.CLIENT_URL || "http://localhost:3000",
methods: ["GET", "POST"]
}
});
// Store connected users
const users = new Map();
// Socket connection handling
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
// Handle user joining
socket.on('join', (userData) => {
users.set(socket.id, {
id: socket.id,
name: userData.name,
room: userData.room
});
socket.join(userData.room);
// Notify others in the room
socket.to(userData.room).emit('userJoined', {
name: userData.name,
id: socket.id
});
});
// Handle chat messages
socket.on('chatMessage', (messageData) => {
const user = users.get(socket.id);
if (user) {
const message = {
id: Date.now(),
user: user.name,
userId: socket.id,
text: messageData.text,
timestamp: new Date().toISOString()
};
// Broadcast to room
io.to(user.room).emit('newMessage', message);
}
});
// Handle disconnect
socket.on('disconnect', () => {
const user = users.get(socket.id);
if (user) {
users.delete(socket.id);
// Notify others in the room
socket.to(user.room).emit('userLeft', {
name: user.name,
id: socket.id
});
}
});
});