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);
Let's break down each part of the code.
http .createServer((request, response) => {
// This is the request handler.
}
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!
});
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;
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();
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); })
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;
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');
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();
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);
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)
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?