Module5-NodeJS-TEST

From CSE330 Wiki
Jump to navigationJump to search

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.

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:

http://localhost:3456/

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.
 * req represents the incoming request (URL, headers, etc.)
 * res represents 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 a browser requests `/index.html`, nothing happens unless we explicitly write code to handle that request.

We have two options:

  • Use a framework like Express (common in industry)
  • Write our own simple static file server (excellent for learning how web servers work)

In this module, we will write our own.

---

Project Setup (Modern Node + ES Modules)

Before writing any code, we must properly initialize our Node project.

Step 1: Create and enter a project directory

$ mkdir fileshare
$ cd fileshare

Step 2: Initialize the project

$ npm init -y

This creates a file named package.json.

Step 3: IMPORTANT — Enable ES Modules

Modern Node supports two module systems:

  • CommonJS → uses require()
  • ES Modules → uses import/export

All examples in this course use ES Modules.

After running npm init -y, your package.json may contain:

"type": "commonjs"

You must change this to:

"type": "module"

If the "type" field does not exist, add it manually.

A minimal correct package.json looks like:

{
  "name": "fileshare",
  "version": "1.0.0",
  "type": "module"
}

Without this change, Node will interpret your files using CommonJS, and import statements will fail.

Step 4: Install dependencies

We will use the mime package to determine correct Content-Type values.

$ npm install mime

npm installs packages locally and automatically records them in package.json.

You should now see:

  • node_modules/
  • package-lock.json
  • updated package.json

Your project is now correctly configured for modern Node.js.

---

Static File Server Code

Create a file named static_server.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 available automatically in ES Modules)
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Directory containing files to serve
const STATIC_DIR = YOUR_STATIC_DIRECTORY_HERE

const server = http.createServer(async (req, res) => {
  try {
    // Extract requested path
    const urlPath = new URL(req.url, "http://localhost").pathname;

    // Build full filesystem path
    const filename = path.join(__dirname, STATIC_DIR, decodeURIComponent(urlPath));

    // If file doesn't exist → 404
    if (!existsSync(filename)) {
      res.writeHead(404, { "Content-Type": "text/plain" });
      res.end("Requested file not found");
      return;
    }

    // Read file (non-blocking)
    const data = await readFile(filename);

    // Determine Content-Type
    const mimetype = mime.getType(filename) || "application/octet-stream";

    // Send file
    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/");

---

How This Server Works

When a browser requests:

http://localhost:3456/index.html

The server:

  1. Extracts the requested path from the URL
  2. Builds the full file path inside the public directory
  3. Checks whether the file exists
  4. Reads the file using non-blocking I/O
  5. Determines the correct Content-Type using mime
  6. Sends the file back to the browser

Notice this line:

import { readFile } from "node:fs/promises";

This allows us to use:

const data = await readFile(filename);

This is modern async/await syntax. The file read is still non-blocking — Node can continue handling other requests while waiting for disk I/O.

---

Security Note

This server is intentionally simple for learning purposes.

A real production server must protect against:

  • Directory traversal (paths like ../)
  • Arbitrary file access
  • Improper input validation

We focus on understanding server mechanics first.

---

Running the Server

Start the server:

$ node app.js

Then open:

http://localhost:3456/

If running on EC2, you must open port 3456 in your Security Group.

---

Common Setup Mistakes

If your server fails to start or import fails:

  • Check that "type": "module" is set in package.json
  • Confirm Node version is modern (use node -v)
  • Make sure port 3456 is open (EC2 users)
  • Ensure your public directory exists and contains files

Most student issues come from missing the ES Modules step.

Node.js Security

Just because we're writing in JavaScript does not mean we can ignore the security principles you've been learning throughout CSE 330.

Node.js applications are still vulnerable to:

  • Cross-Site Scripting (XSS)
  • Cross-Site Request Forgery (CSRF)
  • Injection attacks
  • Directory traversal
  • Insecure use of eval()
  • Improper input validation

Node does not automatically protect you from these issues. The fact that you are running JavaScript on the server instead of PHP does not change the underlying security model of the web.

For example:

  • If you echo unsanitized user input into HTML, you still create an XSS vulnerability.
  • If you trust query parameters without validation, you still risk injection attacks.
  • If your static server does not validate paths properly, you risk directory traversal attacks (e.g., requests containing ../).

Modern Node frameworks (like Express) provide tools that can help mitigate certain risks, but they do not eliminate the need for careful programming.

Security remains the responsibility of the developer.