Welcome back, JavaScript enthusiasts! On Day 21 of our 30 Days of JavaScript series, we dive into the world of Node.js, a powerful runtime environment that allows you to run JavaScript on the server side. Today, we’ll guide you through setting up a Node.js environment, understanding the event loop, and building a simple HTTP server. By the end of this lesson, you’ll have a solid foundation in Node.js, enabling you to start building scalable and efficient backend applications.
Table of Contents
- Setting Up a Node.js Environment
- Understanding the Event Loop
- Building a Simple HTTP Server
- Conclusion
Setting Up a Node.js Environment
What is Node.js?
Node.js is an open-source, cross-platform runtime environment that allows developers to execute JavaScript code outside of a browser. Built on Google Chrome’s V8 JavaScript engine, Node.js is designed for building scalable network applications. Its non-blocking, event-driven architecture makes it ideal for real-time applications such as chat servers, APIs, and streaming services.
Installing Node.js
To get started with Node.js, you need to install it on your machine. Node.js comes with Node Package Manager (NPM), which is essential for managing libraries and dependencies in your projects.
Step 1: Downloading Node.js
Visit the official Node.js website and download the appropriate installer for your operating system (Windows, macOS, Linux). Choose the Long-Term Support (LTS) version to ensure compatibility and stability.
Step 2: Installing Node.js
Run the installer and follow the prompts to complete the installation. After installation, open your terminal or command prompt and type:
node -v
This command checks the installed version of Node.js, confirming that it was installed correctly.
Step 3: Installing a Text Editor
While you can use any text editor, Visual Studio Code is highly recommended due to its powerful features and integration with Node.js. Download and install Visual Studio Code, and you’ll be ready to start coding.
Understanding NPM
NPM is a package manager for JavaScript that comes bundled with Node.js. It allows you to install, share, and manage dependencies in your projects.
Example:
npm init -y
This command initializes a new Node.js project with a default package.json file, which stores metadata about your project and its dependencies.
Understanding the Event Loop
The Event-Driven Architecture
One of the core features of Node.js is its event-driven architecture, which enables non-blocking I/O operations. This is crucial for building scalable applications, as it allows the server to handle multiple requests simultaneously without waiting for previous requests to complete.
What is the Event Loop?
The event loop is the mechanism that allows Node.js to perform non-blocking operations. It continuously checks the call stack to see if there are any functions that need to be executed. When an asynchronous operation completes, the event loop places its callback function in the queue to be processed.
Example:
/**
* Demonstrates the event loop in Node.js.
*
* @returns {void}
*/
console.log('Start');
setTimeout(() => {
console.log('This is an asynchronous message');
}, 2000);
console.log('End');
In this example, Start and End are logged immediately, while the asynchronous message is logged after a 2-second delay, demonstrating how the event loop works.
Callbacks, Promises, and Async/Await
Understanding how Node.js handles asynchronous operations is critical. Callbacks, promises, and the async/await syntax are essential tools for managing these operations.
Example with Callbacks:
/**
* Fetches data with a callback.
*
* @param {function} callback - The callback function to execute.
* @returns {void}
*/
function fetchData(callback) {
setTimeout(() => {
callback('Data fetched');
}, 1000);
}
fetchData((message) => {
console.log(message);
});
Example with Promises:
/**
* Fetches data with a promise.
*
* @returns {Promise} - A promise that resolves after fetching data.
*/
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Data fetched');
}, 1000);
});
}
fetchData().then((message) => {
console.log(message);
});
Example with Async/Await:
/**
* Fetches data using async/await.
*
* @returns {void}
*/
async function fetchData() {
const message = await new Promise((resolve) => {
setTimeout(() => {
resolve('Data fetched');
}, 1000);
});
console.log(message);
}
fetchData();
These examples demonstrate different ways to handle asynchronous operations in Node.js, each offering varying levels of complexity and readability.
Building a Simple HTTP Server
Introduction to HTTP Servers in Node.js
One of the most common use cases for Node.js is creating HTTP servers. These servers handle client requests and send back responses. Node.js provides a built-in http module to create and manage servers.
Setting Up a Basic HTTP Server
We’ll start by creating a simple HTTP server that responds with “Hello, World!” to every request.
Example:
/**
* Creates a basic HTTP server in Node.js.
*
* @returns {void}
*/
const http = require('http');
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello, World!\n');
});
server.listen(3000, '127.0.0.1', () => {
console.log('Server running at http://127.0.0.1:3000/');
});
This script creates an HTTP server that listens on port 3000. When you visit `http://127.0.0.1:3000/` in your browser, you’ll see the message “Hello, World!”
Handling Different Routes
To build a more complex server, you’ll need to handle different routes. This means responding with different content depending on the URL requested by the client.
Example:
/**
* Handles different routes in an HTTP server.
*
* @returns {void}
*/
const http = require('http');
const server = http.createServer((req, res) => {
if (req.url === '/') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Home Page\n');
} else if (req.url === '/about') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('About Page\n');
} else {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Page Not Found\n');
}
});
server.listen(3000, '127.0.0.1', () => {
console.log('Server running at http://127.0.0.1:3000/');
});
This script demonstrates how to create different routes in your Node.js server. Each route responds with different content based on the requested URL.
Serving HTML Content
While returning plain text is useful for learning, most real-world applications serve HTML content. You can use the fs (file system) module to read HTML files and send them as responses.
Example:
/**
* Serves an HTML file from the HTTP server.
*
* @returns {void}
*/
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer((req, res) => {
if (req.url === '/') {
fs.readFile(path.join(__dirname, 'index.html'), (err, data) => {
if (err) {
res.statusCode = 500;
res.setHeader('Content-Type', 'text/plain');
res.end('Internal Server Error\n');
} else {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(data);
}
});
} else {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Page Not Found\n');
}
});
server.listen(3000, '127.0.0.1', () => {
console.log('Server running at http://127.0.0.1:3000/');
});
This example demonstrates how to serve an HTML file from your Node.js server, making your application more dynamic and interactive.
Handling Query Parameters and POST Requests
For more advanced applications, you’ll need to handle query parameters and POST requests. Query parameters allow users to pass data to your server via the URL, while POST requests send data to the server through the request body.
Example: Handling Query Parameters
/**
* Handles query parameters in an HTTP server.
*
* @returns {void}
*/
const url = require('url');
const server = http.createServer((req, res) => {
const queryObject = url.parse(req.url, true).query;
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end(`Query Parameters: ${JSON.stringify(queryObject)}\n`);
});
server.listen(3000, '127.0.0.1', () => {
console.log('Server running at http://127.0.0.1:3000/');
});
Example: Handling POST Requests
/**
* Handles POST requests in an HTTP server.
*
* @returns {void}
*/
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'POST') {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
res.end(`Received POST Data: ${body}\n`);
});
} else {
res.end('Send a POST request to see data handling in action\n');
}
});
server.listen(3000, '127.0.0.1', () => {
console.log('Server running at http://127.0.0.1:3000/');
});
This example demonstrates how to parse incoming data from a POST request and handle it within your server. These techniques are essential for building interactive web applications that require user input.
Conclusion
Today, we explored the foundational concepts of Node.js, covering the setup of a development environment, the event-driven architecture, and the creation of a simple HTTP server. By understanding these core concepts, you now have the tools to begin building efficient, scalable server-side applications using JavaScript.
Node.js provides a powerful platform for developers to extend JavaScript beyond the browser and into the backend, offering real-time capabilities and event-driven architecture. Mastering Node.js will not only strengthen your JavaScript skills but also open new doors for full-stack development opportunities.
As we move forward, continue to deepen your understanding of Node.js and explore its vast ecosystem. In the next lesson, we’ll shift gears and dive into JavaScript Design Patterns, focusing on writing cleaner and more maintainable code for your projects.
What’s Next?
In Day 22, we’ll tackle JavaScript Design Patterns, where you’ll learn to implement essential patterns like Module, Singleton, and Observer. Stay tuned as we continue our journey toward becoming a well-rounded JavaScript developer!

