Module5-NodeJS-TEST
Node.js is a JavaScript runtime built on Chrome's V8 engine that allows JavaScript to run on the server. Frameworks such as Express run on top of Node.js.
Node.js is especially useful for real-time, high-concurrency applications due to its non-blocking, event-driven execution model.
Contents
Non-Blocking I/O
Node.js uses a non-blocking execution model. Instead of waiting for operations (like database queries or file reads) to complete, Node continues executing other work and runs a callback or Promise when the operation finishes.
Blocking example (conceptual):
var data = database.query("select * from news");
print(data[0].title);
The above pseudo-code queries a database, saves the result of the query in a variable named data, and then prints out the first title in data. The problem arises when multiple clients all want to access the server at the same time. In a blocking I/O model, a client must wait until all other clients finish before it can start its process. With intensive operations and thousands of clients, this can easily crash your server!
The solution is to use non-blocking I/O. Consider this revised pseudo-code:
Non-blocking example:
database.query("select * from news", function(data){
print(data[0].title);
});
The revised pseudo-code achieves the same end result as the first version. However, it allows other clients to perform tasks while this process is waiting for the database to be queried! The code occurs in two parts: first, we start the query. Second, when the query is complete, our anonymous callback function is executed. In Node.js, callback functions are usually passed one or more parameters (often an error parameter and a result parameter).
Better yet, with non-blocking I/O, you avoid many of the concurrency headaches common in multi-threaded server environments, because JavaScript code in Node runs in a single main thread. Nifty!
Important clarification: "Single-threaded" does not mean "no concurrency." Node can handle many concurrent connections. The difference is that it avoids running multiple threads of JavaScript at the same time, which reduces race conditions and locking complexity. The concurrency happens in the underlying event loop and operating system, not by spawning a new JavaScript thread per request.
Modern Syntax: Promises and async/await
Historically, Node relied heavily on callback functions, like the example above. While this model still works, large programs with many nested callbacks can become difficult to read and maintain (sometimes called "callback hell").
Modern Node commonly uses Promises and the async/await syntax built on top of them. Conceptually, the behavior is identical: the operation begins, Node continues handling other work, and when the operation finishes, your code resumes.
For example:
async function getTitle() {
const data = await database.query("select * from news");
console.log(data[0].title);
}
Behind the scenes, this is still non-blocking I/O. The `await` keyword does not freeze the entire server; it simply pauses that particular function until the Promise resolves, while the event loop continues processing other incoming requests.
So:
- Callbacks explain how Node originally structured asynchronous work.
- Promises formalize asynchronous behavior.
- async/await makes asynchronous code look more like synchronous code — but it remains non-blocking underneath.
Understanding the callback model helps you understand how Node works internally. Understanding async/await helps you write modern, readable Node applications.
Installing Node.js
We use nvm (Node Version Manager) to install Node:
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
$ source ~/.bashrc
$ nvm install --lts
$ node -v
$ npm -v
Your First Node.js Application: Hello World
Let’s build and run a very simple HTTP server using Node.js.
Create a file named `hello.js` with the following contents:
import http from "node:http";
http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello World\n");
}).listen(3456);
console.log("Server running at http://localhost:3456/");
Now run the program from the command line:
$ node hello.js
Open your browser and visit:
You should see:
Hello World
To stop the server, press Ctrl+C in the terminal.
Note: If you are running this on your EC2 instance, you must open TCP port 3456 in your Security Group before it will be accessible from your browser.
---
Important: ES Modules vs require()
You may notice that we used:
import http from "node:http";
Older Node.js examples often look like this instead:
const http = require("http");
These are two different module systems:
- CommonJS → uses
require()(older Node style) - ES Modules (ESM) → uses
import/export(modern JavaScript standard)
In this course, we will use ES Modules.
To enable ES Modules in Node, create (or edit) a file named `package.json` in your project directory and include:
{
"type": "module"
}
This tells Node to treat `.js` files as ES Modules.
If you do not include this line, Node may assume CommonJS and your `import` statements will not work.
Think of it this way:
- npm installs packages.
- ES Modules control how files import each other.
- `"type": "module"` tells Node which module system to use.
---
Walking Through the Hello World Code
Let’s examine each line carefully.
import http from "node:http";
This imports Node’s built-in HTTP module. Unlike PHP, Node does not automatically load server functionality for you. You must explicitly import the features you want to use.
http.createServer((req, res) => { ... }).listen(3456);
This creates an HTTP server and tells it to listen on port 3456.
The function (req, res) => { ... } is a callback that runs every time a new HTTP request is received.
*reqrepresents the incoming request (URL, headers, etc.) *resrepresents the outgoing response you send back to the client
res.writeHead(200, { "Content-Type": "text/plain" });
Sets the HTTP status code (200 = OK) and response headers.
res.end("Hello World\n");
Sends the response body and finishes the request.
Even in this tiny example, you are already seeing non-blocking, event-driven behavior:
Whenever a connection is made, Node executes your callback function. Node is not looping over users or blocking while it waits. It reacts to events and runs your code when those events occur.
---
A Static File Server Using Node.js
Node.js does not automatically serve static files the way Apache/PHP does. If someone visits `/index.html`, nothing happens unless we explicitly tell Node how to handle that request.
We have two options:
- Use a framework like Express (very common in industry)
- Write a simple static file server ourselves
For learning purposes, we will write our own. This helps you understand how web servers actually work.
---
Installing Packages
We will use the `mime` package to determine the correct Content-Type for files.
In your project directory:
$ npm init -y
$ npm install mime
npm installs dependencies locally inside `node_modules`.
Important: Do not use `sudo` when running `npm install`. It can break permissions in your project directory.
---
Static File Server Code (Modern Node)
Create a file named `app.js`:
import http from "node:http";
import path from "node:path";
import { readFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import mime from "mime";
import { fileURLToPath } from "node:url";
// Recreate __dirname (not automatically available in ES Modules)
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const STATIC_DIR = "<STATIC DIRECTORY NAME>";
const server = http.createServer(async (req, res) => {
try {
const urlPath = new URL(req.url, "http://localhost").pathname;
const filename = path.join(__dirname, STATIC_DIR, decodeURIComponent(urlPath));
if (!existsSync(filename)) {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Requested file not found");
return;
}
const data = await readFile(filename);
const mimetype = mime.getType(filename) || "application/octet-stream";
res.writeHead(200, { "Content-Type": mimetype });
res.end(data);
} catch (err) {
res.writeHead(500, { "Content-Type": "text/plain" });
res.end("Internal server error");
}
});
server.listen(3456);
console.log("Static server running at http://localhost:3456/");
---
Understanding What This Server Does
When a browser requests:
http://localhost:3456/index.html
The server:
- Extracts the requested path from the URL
- Builds the full file path inside `<STATIC DIRECTORY NAME>`
- Checks whether the file exists
- Reads the file (non-blocking)
- Determines the correct Content-Type using `mime`
- Sends the file back to the browser
Notice that we are using:
import { readFile } from "node:fs/promises";
This allows us to use:
const data = await readFile(filename);
This is modern async/await syntax. Behind the scenes, it is still non-blocking I/O.
---
Security Note
This example is intentionally simple for learning purposes.
A production static server must protect against:
- Directory traversal attacks (e.g., `../`)
- Arbitrary file access
- Input validation issues
We focus on understanding mechanics first.
---
Port 3456 on EC2
If running on EC2, open port 3456 in your Security Group:
- Go to EC2 → Security Groups
- Edit Inbound Rules
- Add Custom TCP rule for port 3456
- Save
Then visit: