Node.js for Beginners: From Zero to a Working Server
Node.js revolutionized web development by making it possible to use JavaScript on the server side too. If you already know JavaScript from the browser, you’re already halfway there!
In this guide you’ll learn:
- What Node.js is and why it’s so popular
- How to install and configure it correctly
- The core concepts (modules, npm, async)
- How to build your first web server
- Best practices and common patterns
What Node.js Is and Why You Should Learn It
Node.js is a JavaScript runtime built on Chrome’s V8 engine. It lets you run JavaScript outside the browser, making it perfect for building servers, APIs, CLI tools, and much more.
Why Node.js Is So Popular
- One Language: JavaScript on both frontend and backend
- Performance: The ultra-fast V8 engine with non-blocking I/O
- NPM Ecosystem: Over 2 million reusable packages
- Scalability: Perfect for real-time applications and microservices
- Huge Community: Tons of resources, tutorials, and support
When to Use Node.js
✅ Great for:
- REST and GraphQL APIs
- Real-time applications (chat, gaming, collaboration)
- Microservices
- CLI tools and automation scripts
- Server-side rendering (Next.js, Nuxt.js)
❌ Less suited for:
- CPU-intensive computations (machine learning, video encoding)
- Applications that require heavy multithreading
Installation and Setup
1. Installing Node.js
Recommended Method: NVM (Node Version Manager)
NVM lets you install and manage multiple versions of Node.js:
# Install NVM (macOS/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# Restart your terminal, then install Node.js
nvm install --lts # Long Term Support version
nvm use --lts
# Verify the installation
node --version # Should show v20.x.x
npm --version # Should show 10.x.x
Windows:
Use nvm-windows or download the installer from nodejs.org
Alternative Method (Direct Download):
Go to nodejs.org and download the LTS (Long Term Support) version.
2. Verify the Installation
# Create a test folder
mkdir node-test
cd node-test
# Create your first Node.js file
echo "console.log('Hello Node.js!');" > hello.js
# Run it
node hello.js
You should see Hello Node.js! printed in the terminal. 🎉
Node.js Core Concepts
1. Modules and require/import
Node.js uses a module system to organize code:
CommonJS (traditional):
// math.js - Export functions
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = { add, subtract };
// app.js - Import and use
const math = require('./math');
console.log(math.add(5, 3)); // 8
console.log(math.subtract(10, 4)); // 6
ES Modules (modern):
// math.mjs - Export functions
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// app.mjs - Import and use
import { add, subtract } from './math.mjs';
console.log(add(5, 3)); // 8
console.log(subtract(10, 4)); // 6
To use ES Modules in .js files, add this to package.json:
{
"type": "module"
}
2. Built-in Modules
Node.js ships with a number of useful modules already installed:
// File system
const fs = require('fs');
// HTTP server
const http = require('http');
// Path utilities
const path = require('path');
// Operating system
const os = require('os');
console.log(os.platform()); // linux, darwin, win32
console.log(os.cpus().length); // Number of CPUs
3. NPM: Node Package Manager
NPM manages your project’s dependencies:
# Initialize a new project
npm init -y
# Install a package
npm install express
# Install a package as a dev dependency
npm install --save-dev nodemon
# Install a package globally
npm install -g typescript
Generated package.json file:
{
"name": "my-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"express": "^4.18.0"
},
"devDependencies": {
"nodemon": "^3.0.0"
}
}
4. Asynchronous Programming
Node.js is single-threaded but non-blocking. It uses callbacks, Promises, and async/await:
❌ Synchronous Code (BLOCKING):
const fs = require('fs');
// Blocks execution until the file is read
const data = fs.readFileSync('file.txt', 'utf8');
console.log(data);
console.log('Done');
✅ Callback (OLD STYLE):
const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error:', err);
return;
}
console.log(data);
});
console.log('Done'); // Printed BEFORE the file's contents!
✅ Promise (MODERN):
const fs = require('fs').promises;
fs.readFile('file.txt', 'utf8')
.then(data => console.log(data))
.catch(err => console.error('Error:', err));
✅✅ Async/Await (CLEANEST):
const fs = require('fs').promises;
async function readMyFile() {
try {
const data = await fs.readFile('file.txt', 'utf8');
console.log(data);
} catch (err) {
console.error('Error:', err);
}
}
readMyFile();
Your First Web Server
Simple HTTP Server (Vanilla Node.js)
// server.js
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
// Set response headers
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
// Send the response
res.end('Hello World from my Node.js server!\n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
Run it:
node server.js
Open your browser at http://localhost:3000 and you’ll see the message! 🚀
Server with Simple Routing
// server-routing.js
const http = require('http');
const server = http.createServer((req, res) => {
const { url, method } = req;
res.setHeader('Content-Type', 'application/json');
if (url === '/' && method === 'GET') {
res.statusCode = 200;
res.end(JSON.stringify({ message: 'Welcome to the API' }));
}
else if (url === '/users' && method === 'GET') {
res.statusCode = 200;
res.end(JSON.stringify([
{ id: 1, name: 'Mario' },
{ id: 2, name: 'Luigi' }
]));
}
else if (url === '/health' && method === 'GET') {
res.statusCode = 200;
res.end(JSON.stringify({ status: 'OK', uptime: process.uptime() }));
}
else {
res.statusCode = 404;
res.end(JSON.stringify({ error: 'Not Found' }));
}
});
server.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
Test with curl:
curl http://localhost:3000/
curl http://localhost:3000/users
curl http://localhost:3000/health
curl http://localhost:3000/notfound
Simplifying Things with Express.js
Building servers with vanilla http is fine for learning, but Express.js makes everything much simpler:
1. Setting Up a Project with Express
# Create the project folder
mkdir my-express-app
cd my-express-app
# Initialize npm
npm init -y
# Install Express
npm install express
# Install nodemon for auto-restart (dev only)
npm install --save-dev nodemon
Update package.json to add scripts:
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}
2. Basic Express Server
// server.js
const express = require('express');
const app = express();
const port = 3000;
// Middleware to parse JSON
app.use(express.json());
// GET route
app.get('/', (req, res) => {
res.json({ message: 'Welcome to Express API' });
});
// Route with parameters
app.get('/users/:id', (req, res) => {
const { id } = req.params;
res.json({ userId: id, name: `User ${id}` });
});
// POST route
app.post('/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'Name and email required' });
}
res.status(201).json({
id: Date.now(),
name,
email,
createdAt: new Date()
});
});
// 404 middleware
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
// Start server
app.listen(port, () => {
console.log(`Express server running on http://localhost:${port}`);
});
Run in development mode:
npm run dev
Test with curl:
# GET
curl http://localhost:3000/
curl http://localhost:3000/users/123
# POST
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Mario","email":"[email protected]"}'
3. Full REST API (CRUD)
// api-server.js
const express = require('express');
const app = express();
app.use(express.json());
// In-memory "database" (for demo purposes only)
let users = [
{ id: 1, name: 'Mario Rossi', email: '[email protected]' },
{ id: 2, name: 'Luigi Bianchi', email: '[email protected]' }
];
let nextId = 3;
// GET /users - List all users
app.get('/users', (req, res) => {
res.json(users);
});
// GET /users/:id - Get a specific user
app.get('/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 /users - Create a new user
app.post('/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 /users/:id - Update a user
app.put('/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 /users/:id - Delete a user
app.delete('/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();
});
app.listen(3000, () => {
console.log('CRUD API running on http://localhost:3000');
});
Full test run:
# Create a user
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Anna Verdi","email":"[email protected]"}'
# List users
curl http://localhost:3000/users
# Get a specific user
curl http://localhost:3000/users/1
# Update a user
curl -X PUT http://localhost:3000/users/1 \
-H "Content-Type: application/json" \
-d '{"name":"Mario Rossi Jr."}'
# Delete a user
curl -X DELETE http://localhost:3000/users/2
Working with Files
Reading Files
const fs = require('fs').promises;
const path = require('path');
async function readConfig() {
try {
const filePath = path.join(__dirname, 'config.json');
const data = await fs.readFile(filePath, 'utf8');
const config = JSON.parse(data);
console.log(config);
} catch (err) {
console.error('Error reading file:', err);
}
}
readConfig();
Writing Files
const fs = require('fs').promises;
async function saveUserData(user) {
try {
const data = JSON.stringify(user, null, 2);
await fs.writeFile('user.json', data, 'utf8');
console.log('File saved successfully');
} catch (err) {
console.error('Error writing file:', err);
}
}
saveUserData({
name: 'Mario',
email: '[email protected]'
});
Creating Directories
const fs = require('fs').promises;
async function createUploadsFolder() {
try {
await fs.mkdir('uploads', { recursive: true });
console.log('Folder created');
} catch (err) {
console.error('Error:', err);
}
}
createUploadsFolder();
Environment Variables
DO NOT hardcode sensitive configuration! Use environment variables instead:
1. Install dotenv
npm install dotenv
2. Create a .env File
PORT=3000
DB_HOST=localhost
DB_USER=admin
DB_PASSWORD=supersecret
API_KEY=abc123xyz
NODE_ENV=development
⚠️ IMPORTANT: Add .env to .gitignore!
# .gitignore
node_modules/
.env
3. Use It in Your Code
// server.js
require('dotenv').config();
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
const dbHost = process.env.DB_HOST;
const apiKey = process.env.API_KEY;
app.get('/config', (req, res) => {
res.json({
environment: process.env.NODE_ENV,
port: port,
dbHost: dbHost
// DO NOT expose API_KEY or passwords!
});
});
app.listen(port, () => {
console.log(`Server on port ${port} in ${process.env.NODE_ENV} mode`);
});
Error Handling Best Practices
1. Try-Catch for Async Functions
app.get('/users/:id', async (req, res) => {
try {
const user = await db.getUserById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (err) {
console.error('Error fetching user:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
2. Global Error Middleware
// Error-handling middleware (ALWAYS goes last)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: {
message: err.message || 'Internal Server Error',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
}
});
});
3. Handling Async Errors
// Wrapper to avoid repeating try-catch everywhere
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Usage
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await db.getUserById(req.params.id);
res.json(user);
}));
Common Mistakes and How to Avoid Them
1. Forgetting to Handle Async Errors
❌ WRONG:
app.get('/data', async (req, res) => {
const data = await fetchData(); // If it fails, crash!
res.json(data);
});
✅ RIGHT:
app.get('/data', async (req, res) => {
try {
const data = await fetchData();
res.json(data);
} catch (err) {
res.status(500).json({ error: 'Failed to fetch data' });
}
});
2. Exposing Sensitive Information
❌ WRONG:
app.get('/debug', (req, res) => {
res.json({
dbPassword: process.env.DB_PASSWORD,
apiKey: process.env.API_KEY
});
});
✅ RIGHT:
app.get('/health', (req, res) => {
res.json({
status: 'OK',
environment: process.env.NODE_ENV,
uptime: process.uptime()
// No secrets!
});
});
3. Not Validating Input
❌ WRONG:
app.post('/users', (req, res) => {
const user = req.body; // Accepts anything!
users.push(user);
res.json(user);
});
✅ RIGHT:
app.post('/users', (req, res) => {
const { name, email } = req.body;
if (!name || typeof name !== 'string') {
return res.status(400).json({ error: 'Invalid name' });
}
if (!email || !email.includes('@')) {
return res.status(400).json({ error: 'Invalid email' });
}
const user = { id: nextId++, name, email };
users.push(user);
res.status(201).json(user);
});
4. Blocking the Event Loop
❌ WRONG:
app.get('/heavy', (req, res) => {
// Synchronous, CPU-intensive computation
let result = 0;
for (let i = 0; i < 10000000000; i++) {
result += i;
}
res.json({ result });
});
✅ RIGHT:
const { Worker } = require('worker_threads');
app.get('/heavy', (req, res) => {
const worker = new Worker('./heavy-calc.js');
worker.on('message', (result) => {
res.json({ result });
});
worker.on('error', (err) => {
res.status(500).json({ error: 'Calculation failed' });
});
});
Recommended Project Structure
For larger projects, organize your code like this:
my-app/
├── src/
│ ├── controllers/
│ │ └── userController.js
│ ├── models/
│ │ └── User.js
│ ├── routes/
│ │ └── userRoutes.js
│ ├── middleware/
│ │ └── auth.js
│ ├── utils/
│ │ └── logger.js
│ └── app.js
├── tests/
│ └── user.test.js
├── .env
├── .env.example
├── .gitignore
├── package.json
└── server.js
Example server.js:
require('dotenv').config();
const app = require('./src/app');
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Example src/app.js:
const express = require('express');
const userRoutes = require('./routes/userRoutes');
const app = express();
app.use(express.json());
app.use('/api/users', userRoutes);
module.exports = app;
Next Steps
Now that you have the basics down, you can explore:
- Database: Connect PostgreSQL, MongoDB, or MySQL
- Authentication: Implement JWT or session-based auth
- Testing: Write tests with Jest or Mocha
- Deployment: Deploy to Heroku, Vercel, or AWS
- Frameworks: Try NestJS, Fastify, or Koa
- Real-time: Implement WebSocket with Socket.io
Useful Resources
- Node.js Docs - Official documentation
- Express Guide - Express guide
- NPM Registry - Search for packages
- Node.js Best Practices - GitHub repo
- Awesome Node.js - Curated resource list
Conclusion
You just learned:
✅ What Node.js is and how to install it
✅ The core concepts (modules, npm, async)
✅ How to build HTTP and Express servers
✅ A complete CRUD REST API
✅ Best practices and mistakes to avoid
Node.js opens up a world of possibilities. With practice, you’ll be able to build scalable APIs, automation tools, and real-time applications.
Keep following Code Fortress for more advanced tutorials on backend development, security, and DevOps!
Have questions about Node.js? Comment below or follow us on social media. Happy coding! 🚀