Contents

Building a Web Server with Node.js

Node.js is a powerful tool for building web servers and handling HTTP requests and responses. In this guide, we’ll walk through creating a basic HTTP server using the http module, handling HTTP requests and responses, serving static files, and routing requests to different endpoints.

Creating a Basic HTTP Server Using the http Module

The http module in Node.js allows you to create a web server that listens for incoming requests and sends responses.

Step 1: Import the http Module

Start by importing the http module:

				
					const http = require('http');





				
			

Step 2: Create the HTTP Server

You can create a server using the http.createServer() method. This method takes a callback function that is executed every time a request is made to the server.

Example:

				
					const http = require('http');

const server = http.createServer((req, res) => {
  res.statusCode = 200; // HTTP status code (200: OK)
  res.setHeader('Content-Type', 'text/plain'); // Response headers
  res.end('Hello, World!\n'); // Response body
});

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






				
			

Explanation:

  • req (Request Object): Contains information about the incoming request, such as the URL, method, and headers.
  • res (Response Object): Used to send a response back to the client. You can set the status code, headers, and body of the response.

This basic server listens on port 3000 and responds with “Hello, World!” for every request.

Handling HTTP Requests and Responses

The HTTP server can handle different types of requests (GET, POST, etc.) and respond accordingly.

Example: Handling Different Request Methods

				
					const http = require('http');

const server = http.createServer((req, res) => {
  if (req.method === 'GET') {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('GET request received\n');
  } else if (req.method === 'POST') {
    let body = '';
    req.on('data', chunk => {
      body += chunk.toString(); // Convert Buffer to string
    });
    req.on('end', () => {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/plain');
      res.end(`POST request received with body: ${body}\n`);
    });
  } else {
    res.statusCode = 405; // Method Not Allowed
    res.setHeader('Content-Type', 'text/plain');
    res.end(`${req.method} method not allowed\n`);
  }
});

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





				
			

Explanation:

  • GET Request: The server responds with a simple message.
  • POST Request: The server reads the request body, then responds with the received data.
  • Other Methods: The server responds with a 405 status code for methods that are not allowed.

Serving Static Files with Node.js

To serve static files (e.g., HTML, CSS, JavaScript, images), you need to read the file from the filesystem and send it as the response.

Example: Serving an HTML File

				
					const http = require('http');
const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    const filePath = path.join(__dirname, 'index.html');
    fs.readFile(filePath, 'utf8', (err, data) => {
      if (err) {
        res.statusCode = 500;
        res.setHeader('Content-Type', 'text/plain');
        res.end('Internal Server Error');
      } 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('Not Found');
  }
});

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




				
			

Explanation:

  • Serving HTML: The server checks if the request is for the root path (/). If so, it reads the index.html file and sends it as the response.
  • Error Handling: If the file cannot be read, the server responds with a 500 status code.

Routing Requests to Different Endpoints

To handle different routes (URLs) in your application, you can implement basic routing logic in your server.

Example: Implementing Basic Routing

				
					const http = require('http');
const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    serveFile(res, 'index.html', 'text/html');
  } else if (req.url === '/about') {
    serveFile(res, 'about.html', 'text/html');
  } else if (req.url === '/style.css') {
    serveFile(res, 'style.css', 'text/css');
  } else {
    res.statusCode = 404;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Not Found');
  }
});

function serveFile(res, filename, contentType) {
  const filePath = path.join(__dirname, filename);
  fs.readFile(filePath, (err, data) => {
    if (err) {
      res.statusCode = 500;
      res.setHeader('Content-Type', 'text/plain');
      res.end('Internal Server Error');
    } else {
      res.statusCode = 200;
      res.setHeader('Content-Type', contentType);
      res.end(data);
    }
  });
}

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





				
			

Explanation:

  • Routing Logic: The server checks the req.url to determine which route was requested. Depending on the route, it serves the appropriate file.
  • serveFile Function: This function abstracts the logic for reading and serving files based on the requested URL. It handles different content types like HTML and CSS.

Conclusion

Asynchronous programming is a critical aspect of Node.js, enabling the handling of operations like I/O without blocking the main thread. Understanding callbacks and the callback pattern is essential, but Promises and async/await offer more powerful and readable ways to handle asynchronous code. Additionally, proper error handling with .catch() and try/catch ensures that your application can gracefully manage failures in asynchronous operations. Mastering these techniques will help you write more efficient, maintainable, and error-resistant Node.js applications.