DEV Community

Ayako yk
Ayako yk

Posted on

Building Dynamic HTTP Handlers in Node.js

In a previous blog post, A Beginner's Guide to HTTP Servers in Node.js, I shared simple, basic HTTP server setups designed primarily for learning purposes. While those examples are functional, more dynamic setups are often required in real-world applications. In this blog post, I'll break down an example code snippet to better understand how it works.

Here's an overall code snippet from the Node.js documentation:

const http = require('node:http');

http
  .createServer((request, response) => {
    const { headers, method, url } = request;
    let body = [];
    request
      .on('error', err => {
        console.error(err);
      })
      .on('data', chunk => {
        body.push(chunk);
      })
      .on('end', () => {
        body = Buffer.concat(body).toString();
        // BEGINNING OF NEW STUFF

        response.on('error', err => {
          console.error(err);
        });

        response.statusCode = 200;
        response.setHeader('Content-Type', 'application/json');
        // Note: the 2 lines above could be replaced with this next one:
        // response.writeHead(200, {'Content-Type': 'application/json'})

        const responseBody = { headers, method, url, body };

        response.write(JSON.stringify(responseBody));
        response.end();
        // Note: the 2 lines above could be replaced with this next one:
        // response.end(JSON.stringify(responseBody))

        // END OF NEW STUFF
      });
  })
  .listen(8080);

Enter fullscreen mode Exit fullscreen mode

Source

Let's break down each part of the code.

http .createServer((request, response) => {
  // This is the request handler.
}
Enter fullscreen mode Exit fullscreen mode

This is a request handler. Node.js requires a server, and createServer creates it. The function passed into createServer is called every time an HTTP request is made.

This part can also be written as:

const server = http.createServer();
server.on('request', (request, response) => { 
  // the same kind of magic happens here!
});
Enter fullscreen mode Exit fullscreen mode

This is a more explicit way of creating a server object. It is suitable for more complex code, such as when multiple events need to be handled.

const { headers, method, url } = request;
Enter fullscreen mode Exit fullscreen mode

The request object is an instance of IncomingMessage, which is provided by Node.js to handle HTTP requests. Node.js simplifies accessing key details from the request by allowing direct access to properties like headers, method, and url.

headers: Contains all HTTP headers sent by the client, always in lowercase for consistency. Repeated headers are combined into a comma-separated string or overwritten, but the useful rawHeaders property is also available.

method: Represents the HTTP method (e.g., GET, POST) used in the request.

url: Represents the part of the URL after the server, protocol, and port. For most URLs, this includes everything after and including the third forward slash.

By destructuring { headers, method, url }, it's easier to work with these properties in the request handler.

let body = []; 
request
  .on('data', chunk => { 
    body.push(chunk); 
  }) 
  .on('end', () => { 
  body = Buffer.concat(body).toString();
Enter fullscreen mode Exit fullscreen mode

When handling a POST or PUT request, the request body is often crucial. The request object implements the ReadableStream interface, allowing it to handle incoming data as a stream. This lets you listen for the stream's data and end events to process the request body.

Each chunk from the data event is emitted as a Buffer. If the data is a string, collect the chunks into an array and concatenate them at the end event.

request .on('error', err => { console.error(err); })
Enter fullscreen mode Exit fullscreen mode

It's better to include error handling because, without a listener for the 'error' event, the error will be thrown and may crash your Node.js application. Adding the listener before processing or sending the data is recommended to prevent such issues.

response.statusCode = 200;
Enter fullscreen mode Exit fullscreen mode

Although Node.js defaults the status code to 200, it is recommended to set it explicitly for better readability, consistency, and professionalism.

response.setHeader('Content-Type', 'application/json');
Enter fullscreen mode Exit fullscreen mode

The setHeader method is used to set headers on the response object. Header names are case-insensitive, and if a header is set multiple times, the last value will be set.

const responseBody = { headers, method, url, body }; response.write(JSON.stringify(responseBody)); 
response.end();
Enter fullscreen mode Exit fullscreen mode

We collect all the data (headers, method, url, and body) into an object called responseBody. This object is converted into a JSON string using JSON.stringify() and sent to the client via response.write(). Finally, response.end() is called to complete the response and signal that no more data will follow.

.listen(8080);
Enter fullscreen mode Exit fullscreen mode

It binds the server to port 8080 and starts listening for incoming HTTP requests on that port.

This code is more dynamic and robust because it:

  • Efficiently processes incoming data in chunks and uses buffers to handle the request body.
  • Dynamically responds based on the request's details, such as method, headers, and URL.
  • Includes error handling for both the request and response streams, ensuring reliability.

Top comments (1)

Collapse
 
nevodavid profile image
Nevo David

growth like this is always nice to see. kinda makes me wonder - you think the magic happens more from tiny improvements over time or just from sticking with the basics?