Unit 2 - Notes

INT222

Unit 2: Implementing HTTP Services & Basic Websites With Node.JS

1. Introduction to the HTTP Module

Node.js is built with a core module named http which allows Node.js to transfer data over the Hyper Text Transfer Protocol (HTTP). This module is capable of creating an HTTP server that listens to server ports and gives a response back to the client.

Key Characteristics

  • Built-in: It does not require installation via NPM; it is part of the Node.js core.
  • Event-Driven: It inherits from the EventEmitter class.
  • Low-level: It provides low-level access to HTTP requests and responses, requiring manual handling of data chunks, headers, and routing logic compared to frameworks like Express.

To include the HTTP module:

JAVASCRIPT
const http = require('http');


2. Setting up a Basic HTTP Server

The http.createServer() method includes a callback function that is executed every time a request is received. This callback receives two arguments: the Request object and the Response object.

Syntax

JAVASCRIPT
const server = http.createServer((req, res) => {
    // Logic to handle request and send response
});

server.listen(port, hostname, callback);

Example: "Hello World" Server

JAVASCRIPT
const http = require('http');
const PORT = 3000;

const server = http.createServer((req, res) => {
    res.write('Hello World from Node.js Server');
    res.end();
});

server.listen(PORT, () => {
    console.log(`Server running at http://localhost:${PORT}/`);
});


3. Understanding Request and Response Objects

The Request Object (req)

The req object represents the HTTP request and has properties for the request query string, parameters, body, HTTP headers, and so on. It is an instance of http.IncomingMessage.

Key Properties:

  • req.url: The URL string (the path after the domain).
  • req.method: The HTTP method used (GET, POST, DELETE, etc.).
  • req.headers: An object containing the request headers.

The Response Object (res)

The res object represents the HTTP response that an Express app sends when it gets an HTTP request. It is an instance of http.ServerResponse.

Key Methods:

  • res.write(chunk): Sends a chunk of the response body. Can be called multiple times.
  • res.end([data]): Signals to the server that all of the response headers and body have been sent. Must be called on every response.
  • res.setHeader(name, value): Sets a single header value.

4. Implementing Basic Routing (Without Frameworks)

In raw Node.js, routing must be handled manually by inspecting the req.url and req.method within the server callback.

Example: Switch-Case Routing

JAVASCRIPT
const http = require('http');

const server = http.createServer((req, res) => {
    const url = req.url;
    const method = req.method;

    if (url === '/') {
        res.write('<html><body><h1>Home Page</h1></body></html>');
        res.end();
    } else if (url === '/about' && method === 'GET') {
        res.write('<html><body><h1>About Us</h1></body></html>');
        res.end();
    } else if (url === '/api/data') {
        res.setHeader('Content-Type', 'application/json');
        res.write(JSON.stringify({ message: "Here is your data" }));
        res.end();
    } else {
        res.statusCode = 404;
        res.write('<html><body><h1>404 Not Found</h1></body></html>');
        res.end();
    }
});

server.listen(3000);


5. Setting Response Headers and Status Codes

Properly setting headers and status codes is crucial for clients (browsers/APIs) to understand how to process the response.

Status Codes (res.statusCode)

  • 200: OK (Standard response for successful HTTP requests).
  • 201: Created (Request fulfilled, new resource created).
  • 301: Moved Permanently.
  • 400: Bad Request (Client error).
  • 401: Unauthorized.
  • 404: Not Found.
  • 500: Internal Server Error.

Response Headers (res.writeHead or res.setHeader)

Headers provide metadata about the response, such as the content type.

Using writeHead (Sets status and headers simultaneously):

JAVASCRIPT
res.writeHead(200, { 
    'Content-Type': 'application/json',
    'X-Powered-By': 'Node.js' 
});

Common Content-Types:

  • text/html: HTML documents.
  • text/plain: Plain text.
  • application/json: JSON data (standard for APIs).

6. Introducing Express.js

Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.

Why use Express over raw Node.js?

  1. Simplified Routing: Clean syntax for handling different HTTP verbs and URL paths.
  2. Middleware: Easy integration of functions that have access to the request object, response object, and the next middleware function (e.g., authentication, logging).
  3. Template Engines: Easy integration with engines like EJS, Pug, or Handlebars.
  4. Static Files: Built-in middleware to serve static assets (CSS, images).

7. Installing Express

To use Express, it must be installed via the Node Package Manager (NPM) into a project initialized with a package.json.

Step 1: Initialize project

BASH
mkdir my-express-app
cd my-express-app
npm init -y

Step 2: Install Express

BASH
npm install express --save


8. Express Basics: GET and POST

Express simplifies request handling by providing methods corresponding to HTTP verbs.

Setup

JAVASCRIPT
const express = require('express');
const app = express();
const PORT = 3000;

GET Request

Used to retrieve data.

JAVASCRIPT
app.get('/', (req, res) => {
    res.send('Welcome to the Home Page'); // .send() automatically sets Content-Type and ends response
});

app.get('/users', (req, res) => {
    res.json([{ id: 1, name: 'John' }]); // .json() sends JSON response
});

POST Request

Used to submit data to be processed to a specified resource.

JAVASCRIPT
app.post('/users', (req, res) => {
    // Logic to add a user would go here
    res.status(201).send('User Created');
});

Starting the Server

JAVASCRIPT
app.listen(PORT, () => {
    console.log(`Express server running on port ${PORT}`);
});


9. Body-Parser (Handling Request Body)

To read data sent in a POST request (e.g., from a form or an API client), the server must parse the body of the request.

  • Historically: body-parser was a separate npm package.
  • Currently (Express 4.16.0+): It is built into Express.

Configuration

You must configure the middleware before your route definitions.

JAVASCRIPT
// To parse JSON payloads (e.g., from React/Vue/Postman)
app.use(express.json());

// To parse URL-encoded data (e.g., standard HTML forms)
app.use(express.urlencoded({ extended: true }));

Accessing Data

Once parsed, the data is available in req.body.

JAVASCRIPT
app.post('/login', (req, res) => {
    const username = req.body.username;
    const password = req.body.password;
    console.log(`Attempting login for: ${username}`);
    res.send('Login received');
});


10. Modular Routing with express.Router

As an application grows, putting all routes in the main app.js file becomes unmanageable. express.Router creates modular, mountable route handlers (often called "mini-apps").

Creating a Router Module (routes/users.js)

JAVASCRIPT
const express = require('express');
const router = express.Router();

// Define routes relative to the mount path
router.get('/', (req, res) => {
    res.send('Get all users');
});

router.get('/:id', (req, res) => {
    res.send(`Get user with ID: ${req.params.id}`);
});

module.exports = router;

Mounting the Router (app.js)

JAVASCRIPT
const userRoutes = require('./routes/users');

// All routes in userRoutes will be prefixed with /users
// e.g., /users/ and /users/:id
app.use('/users', userRoutes);


11. Input Validation with express-validator

express-validator is a set of Express.js middlewares that wraps validator.js. It allows you to validate and sanitize inputs (body, query, params) to ensure data integrity and security.

Installation

BASH
npm install express-validator

Implementation Workflow

  1. Import check or body and validationResult.
  2. Pass validation chains as middleware to the route.
  3. Check for errors inside the route handler.

Example Code

JAVASCRIPT
const { body, validationResult } = require('express-validator');

app.post('/register', [
    // 1. Define Rules
    body('email').isEmail().withMessage('Must be a valid email'),
    body('password').isLength({ min: 6 }).withMessage('Password must be 6+ chars'),
    body('age').optional().isInt({ min: 18 }).withMessage('Must be 18 or older')
], (req, res) => {
    
    // 2. Check for Validation Errors
    const errors = validationResult(req);
    
    if (!errors.isEmpty()) {
        // Return 400 Bad Request with the error array
        return res.status(400).json({ errors: errors.array() });
    }

    // 3. Proceed if valid
    const { email, password } = req.body;
    res.send(`User registered with email: ${email}`);
});

Common Validators

  • isEmail(): Checks for valid email format.
  • isLength({ min, max }): Checks string length.
  • trim(): Sanitizer that removes whitespace.
  • escape(): Sanitizer that replaces HTML characters (prevents XSS).
  • normalizeEmail(): Sanitizer that canonicalizes the email address.