Complete Node.js Tutorial for Beginners 2025: Master Backend JavaScript Development
Introduction to Node.js and Server-Side JavaScript
Node.js has revolutionized web development by enabling JavaScript to run on servers, allowing developers to use a single programming language across entire web applications. Originally released in 2009, Node.js powers backend systems for Netflix, LinkedIn, Uber, PayPal, and thousands of other companies processing millions of requests daily. Learning Node.js opens career opportunities in backend development, full-stack engineering, and API development with skills highly valued in today's job market.
This comprehensive Node.js tutorial guides you from fundamental concepts to building real-world applications, providing practical code examples you can implement immediately. Whether you're transitioning from frontend development, learning backend programming, or pursuing full-stack capabilities, mastering Node.js empowers you to build scalable, high-performance server applications using JavaScript expertise you already possess.
What is Node.js and Why Learn It?
Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine, enabling JavaScript execution outside web browsers. Unlike traditional server technologies like PHP, Python, or Ruby that create new threads for each request, Node.js uses an event-driven, non-blocking I/O model making it lightweight and efficient for data-intensive real-time applications.
Career Opportunities: Node.js developers rank among the highest-paid backend professionals, with average salaries ranging from eighty-five thousand to one hundred fifty thousand dollars annually in the United States. Senior Node.js developers, architects, and specialists in microservices architecture command premium compensation. The demand for Node.js skills continues growing as companies modernize infrastructure and build API-first applications.
Node.js Advantages: Using JavaScript everywhere eliminates context switching between languages, accelerating development and reducing cognitive load. The npm ecosystem provides over 1.5 million packages solving virtually any programming challenge. Node.js excels at handling concurrent connections efficiently, making it ideal for real-time applications, APIs, microservices, and I/O-heavy workloads. The active community ensures abundant learning resources, frequent updates, and extensive third-party tool support.
Node.js Use Cases: Companies leverage Node.js for REST APIs serving mobile and web applications, real-time applications like chat systems and collaborative tools, microservices architectures, streaming services processing data in real-time, Internet of Things backends, server-side rendering for React and Vue applications, build tools and development automation, and command-line utilities.
Installing Node.js and Development Environment Setup
Installing Node.js
Visit nodejs.org and download the LTS (Long Term Support) version recommended for most users. The installer includes Node.js runtime and npm (Node Package Manager) automatically.
Verify Installation:
node --version
npm --version
These commands display installed versions confirming successful installation.
Node Version Manager (NVM): For developers managing multiple Node.js versions across projects, NVM enables easy switching between versions. Install NVM following instructions at github.com/nvm-sh/nvm for Unix systems or github.com/coreybutler/nvm-windows for Windows.
NVM Commands:
nvm install 20.10.0
nvm use 20.10.0
nvm list
nvm alias default 20.10.0
Setting Up Development Environment
Visual Studio Code: Download from code.visualstudio.com for optimal Node.js development. Install helpful extensions including Node.js Extension Pack, ESLint for code quality, Prettier for formatting, REST Client for API testing, and npm Intellisense for package autocomplete.
Terminal Setup: Node.js development relies heavily on command-line interfaces. Windows users should install Windows Terminal or Git Bash. macOS and Linux include suitable terminals by default.
Node.js Fundamentals
Creating Your First Node.js Application
Create a file named app.js:
console.log('Hello, Node.js!');
Run the application:
node app.js
The terminal displays "Hello, Node.js!" demonstrating successful Node.js execution.
Working with Modules
Node.js uses modules organizing code into reusable components. Each file represents a separate module.
Creating and Exporting Modules:
// math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
// Export individual functions
module.exports = {
add,
subtract,
multiply
};
// Alternative: export single function
// module.exports = add;
Importing Modules:
// app.js
const math = require('./math');
console.log(math.add(5, 3)); // 8
console.log(math.subtract(10, 4)); // 6
console.log(math.multiply(3, 7)); // 21
// Destructuring import
const { add, multiply } = require('./math');
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
ES Modules Syntax (Modern):
Node.js also supports ES6 import/export syntax when using .mjs extension or setting "type": "module" in package.json.
// math.mjs
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// app.mjs
import { add, subtract } from './math.mjs';
console.log(add(5, 3));
Built-in Node.js Modules
Node.js includes powerful built-in modules requiring no installation.
File System (fs) Module:
const fs = require('fs');
// Read file synchronously
const data = fs.readFileSync('input.txt', 'utf8');
console.log(data);
// Read file asynchronously (recommended)
fs.readFile('input.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
console.log(data);
});
// Write file
fs.writeFile('output.txt', 'Hello from Node.js!', (err) => {
if (err) {
console.error('Error writing file:', err);
return;
}
console.log('File written successfully');
});
// Append to file
fs.appendFile('output.txt', '\nNew line added', (err) => {
if (err) {
console.error('Error appending to file:', err);
return;
}
console.log('Content appended');
});
// Delete file
fs.unlink('output.txt', (err) => {
if (err) {
console.error('Error deleting file:', err);
return;
}
console.log('File deleted');
});
Path Module:
const path = require('path');
// Join path segments
const filePath = path.join(__dirname, 'files', 'data.txt');
console.log(filePath);
// Get file extension
const ext = path.extname('document.pdf');
console.log(ext); // .pdf
// Get filename
const filename = path.basename('/users/john/document.pdf');
console.log(filename); // document.pdf
// Get directory name
const dirname = path.dirname('/users/john/document.pdf');
console.log(dirname); // /users/john
OS Module:
const os = require('os');
console.log('Platform:', os.platform());
console.log('CPU Architecture:', os.arch());
console.log('CPUs:', os.cpus().length);
console.log('Free Memory:', os.freemem());
console.log('Total Memory:', os.totalmem());
console.log('Home Directory:', os.homedir());
console.log('Uptime:', os.uptime());
NPM - Node Package Manager
Understanding npm
npm is the world's largest software registry containing over 1.5 million packages. Developers use npm to install, share, and manage project dependencies.
Initializing Project:
npm init
This interactive command creates package.json containing project metadata and dependencies. Use npm init -y for default settings.
package.json Example:
{
"name": "my-node-app",
"version": "1.0.0",
"description": "My Node.js application",
"main": "app.js",
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
},
"keywords": ["nodejs", "tutorial"],
"author": "Your Name",
"license": "MIT",
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
Installing Packages
Install Package:
npm install express
npm install lodash
npm install axios
Install as Dev Dependency:
npm install --save-dev nodemon
npm install --save-dev jest
Install Globally:
npm install -g nodemon
npm install -g create-react-app
Install Specific Version:
npm install express@4.17.1
Uninstall Package:
npm uninstall express
Using Installed Packages
// Using lodash
const _ = require('lodash');
const numbers = [1, 2, 3, 4, 5];
const doubled = _.map(numbers, n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// Using axios for HTTP requests
const axios = require('axios');
axios.get('https://api.github.com/users/github')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error('Error:', error);
});
Building HTTP Server with Node.js
Basic HTTP Server
Node.js includes an http module for creating web servers.
const http = require('http');
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello, World!\n');
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}/`);
});
Handling Different Routes
const http = require('http');
const server = http.createServer((req, res) => {
if (req.url === '/') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end('<h1>Home Page</h1>');
} else if (req.url === '/about') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end('<h1>About Page</h1>');
} else if (req.url === '/api/users') {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ users: ['Alice', 'Bob', 'Charlie'] }));
} else {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/html');
res.end('<h1>404 - Page Not Found</h1>');
}
});
server.listen(3000, () => {
console.log('Server running on port 3000');
});
Express.js Framework
Why Use Express.js?
Express is the most popular Node.js web framework, simplifying server creation with elegant routing, middleware support, and extensive features.
Install Express:
npm install express
Basic Express Server
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello from Express!');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Express Routing
const express = require('express');
const app = express();
// Middleware to parse JSON
app.use(express.json());
// Home route
app.get('/', (req, res) => {
res.send('<h1>Home Page</h1>');
});
// About route
app.get('/about', (req, res) => {
res.send('<h1>About Page</h1>');
});
// API route
app.get('/api/users', (req, res) => {
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
res.json(users);
});
// Route with parameters
app.get('/api/users/:id', (req, res) => {
const userId = req.params.id;
res.json({ id: userId, name: 'User ' + userId });
});
// POST route
app.post('/api/users', (req, res) => {
const newUser = req.body;
res.status(201).json({ message: 'User created', user: newUser });
});
// 404 handler
app.use((req, res) => {
res.status(404).send('<h1>404 - Page Not Found</h1>');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Express Middleware
Middleware functions execute during request-response cycle, modifying requests, responses, or terminating requests.
const express = require('express');
const app = express();
// Built-in middleware
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies
// Custom middleware - Logger
app.use((req, res, next) => {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next(); // Pass control to next middleware
});
// Route-specific middleware
const checkAuth = (req, res, next) => {
const token = req.headers.authorization;
if (token === 'valid-token') {
next();
} else {
res.status(401).json({ error: 'Unauthorized' });
}
};
app.get('/api/protected', checkAuth, (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!' });
});
app.listen(3000);
Serving Static Files
const express = require('express');
const path = require('path');
const app = express();
// Serve static files from 'public' directory
app.use(express.static('public'));
// Serve static files with virtual path prefix
app.use('/static', express.static('public'));
app.listen(3000);
Working with Databases
MongoDB with Mongoose
Install Mongoose:
npm install mongoose
Connect to MongoDB:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('MongoDB connected'))
.catch(err => console.error('MongoDB connection error:', err));
Define Schema and Model:
const mongoose = require('mongoose');
// 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);
module.exports = User;
CRUD Operations:
const User = require('./models/User');
// Create
async function createUser() {
const user = new User({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
const savedUser = await user.save();
console.log('User created:', savedUser);
}
// Read all
async function getAllUsers() {
const users = await User.find();
console.log('All users:', users);
}
// Read by ID
async function getUserById(id) {
const user = await User.findById(id);
console.log('User:', user);
}
// Find with conditions
async function findUsers() {
const users = await User.find({ age: { $gte: 25 } });
console.log('Users 25+:', users);
}
// Update
async function updateUser(id) {
const user = await User.findByIdAndUpdate(
id,
{ age: 31 },
{ new: true } // Return updated document
);
console.log('Updated user:', user);
}
// Delete
async function deleteUser(id) {
await User.findByIdAndDelete(id);
console.log('User deleted');
}
MySQL with mysql2
Install mysql2:
npm install mysql2
Connect and Query:
const mysql = require('mysql2');
// Create connection
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'myapp'
});
// Connect
connection.connect((err) => {
if (err) {
console.error('Error connecting:', err);
return;
}
console.log('MySQL connected');
});
// Query
connection.query('SELECT * FROM users', (err, results) => {
if (err) {
console.error('Query error:', err);
return;
}
console.log('Users:', results);
});
// Prepared statement
const userId = 1;
connection.query(
'SELECT * FROM users WHERE id = ?',
[userId],
(err, results) => {
if (err) {
console.error('Query error:', err);
return;
}
console.log('User:', results[0]);
}
);
// Close connection
connection.end();
Building REST API with Express
Complete REST API example with CRUD operations:
const express = require('express');
const app = express();
app.use(express.json());
// In-memory data store
let users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
];
let nextId = 3;
// GET all users
app.get('/api/users', (req, res) => {
res.json(users);
});
// GET single user
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
// POST create user
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'Name and email required' });
}
const newUser = {
id: nextId++,
name,
email
};
users.push(newUser);
res.status(201).json(newUser);
});
// PUT update user
app.put('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
const { name, email } = req.body;
if (name) user.name = name;
if (email) user.email = email;
res.json(user);
});
// DELETE user
app.delete('/api/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) {
return res.status(404).json({ error: 'User not found' });
}
users.splice(index, 1);
res.status(204).send();
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`API server running on port ${PORT}`);
});
Asynchronous Programming
Callbacks
Traditional Node.js pattern for handling asynchronous operations:
const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error:', err);
return;
}
console.log('File content:', data);
});
Promises
Modern approach avoiding callback hell:
const fs = require('fs').promises;
fs.readFile('file.txt', 'utf8')
.then(data => {
console.log('File content:', data);
return fs.writeFile('output.txt', data);
})
.then(() => {
console.log('File written');
})
.catch(err => {
console.error('Error:', err);
});
Async/Await
Cleanest asynchronous code syntax:
const fs = require('fs').promises;
async function processFile() {
try {
const data = await fs.readFile('file.txt', 'utf8');
console.log('File content:', data);
await fs.writeFile('output.txt', data);
console.log('File written');
} catch (err) {
console.error('Error:', err);
}
}
processFile();
Environment Variables
Store sensitive configuration using environment variables:
Install dotenv:
npm install dotenv
Create .env file:
PORT=3000
DATABASE_URL=mongodb://localhost:27017/myapp
JWT_SECRET=your-secret-key
Use in application:
require('dotenv').config();
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Node.js Best Practices
Error Handling: Always handle errors in callbacks and promises, use try-catch with async/await, implement global error handlers, log errors properly, and never expose sensitive error details to clients.
Security: Validate and sanitize user input, use prepared statements preventing SQL injection, implement rate limiting, set security headers with helmet middleware, keep dependencies updated, and never commit secrets to version control.
Performance: Use asynchronous operations avoiding blocking code, implement caching strategies, compress responses, use clustering for multi-core systems, and monitor application performance.
Code Organization: Structure projects logically separating routes, controllers, and models, use environment variables for configuration, implement proper logging, write tests for critical functionality, and follow consistent code style.
Conclusion and Next Steps
You've learned Node.js fundamentals including modules, npm, HTTP servers, Express framework, databases, REST APIs, and asynchronous programming. These skills enable building production-ready backend applications and APIs.
Continue Learning: Explore authentication with JWT and Passport, study WebSockets for real-time features, learn testing with Jest and Mocha, master deployment on AWS, Heroku, or DigitalOcean, practice microservices architecture, and build complete full-stack applications.
Node.js development offers exciting career opportunities in backend and full-stack roles. Start building projects, contribute to open-source, and continuously refine your Node.js expertise. The ecosystem evolves rapidly, making continuous learning essential for staying current with best practices and emerging patterns.