https://itnext.io/how-to-handle-the-post-request-body-in-node-js-without-using-a-framework-cd2038b93190


This post highlights an attempt to take a peek at the raw format of data sent in a POST request body and how one could parse it. There are packages like body-parser for Express that do this for us so this post is merely for learning purposes. I won’t suggest the use of this solution in production.

When making post requests to the server via HTML form submission, the data sent to the backend is typically configured with one of these media types: application/x-www-form-urlencodedmultipart/form-data or text/plain. I’m looking at application/x-www-form-urlencoded in this example.


Creating our server

Let’s set one up using the snippet below:

const http = require('http');
const server = http.createServer((req, res) => {
res.end(`
<!doctype html>
<html>
<body>
<form action="/" method="post">
<input type="text" name="fname" /><br />
<input type="number" name="age" /><br />
<input type="file" name="photo" /><br />
<button>Save</button>
</form>
</body>
</html>
`);
});
server.listen(3000);

This should spin up a page at http://localhost:3000 with a web form:

Populating the fields and hitting ‘Save’ will submit the results to the root path containing the data fields as expected. This will use the default media type application/x-www-form-urlencoded. That means that it will create a query string using the field names as keys and its data as the values.

Capturing the POSTed data

To set the scene to capture this data, we must first check that its a POST request:

const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'POST') {
// Handle post info...
}
else {
res.end(`
<!doctype html>
<html>
<body>
<form action="/" method="post">
<input type="text" name="fname" /><br />
<input type="number" name="age" /><br />
<input type="file" name="photo" /><br />
<button>Save</button>
</form>
</body>
</html>
`);
}
});
server.listen(3000);

Given that the request sent to the backend is a Readable stream, the EventEmitter API is used as a means of reading data off this stream (we do not need to import the ‘events’ module here since the request object extends EventEmitter):

...
if (req.method === 'POST') {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
// convert Buffer to string
});
req.on('end', () => {
console.log(body);
res.end('ok');
});

}
...

Populating the form and hitting ‘Save’ will log out this information to the console:

fname=Jermaine&age=29&photo=jermaine-photo.png

Each field in the form is delimited by the ampersand(&) character and the key and values are separated by the equal character.

Parsing the data

For ease of accessing each key/value pair we will use Node’s inbuilt querystring module to convert the data to an object:

// At the top of the file
const { parse } = require('querystring');
...
...
if (req.method === 'POST') {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
console.log(
parse(body)
);
res.end('ok');
});
}
...

This will log out the result below:

{
fname: 'Jermaine',
age: '29',
photo: 'jermaine-photo.jpg'
}

To tidy this up a bit more, lets create a utility function to simplify the above:

function collectRequestData(request, callback) {
const FORM_URLENCODED = 'application/x-www-form-urlencoded';
    if(request.headers['content-type'] === FORM_URLENCODED) {
let body = '';
request.on('data', chunk => {
body += chunk.toString();
});
request.on('end', () => {
callback(parse(body));
});
}
else {
callback(null);
}
}

We will use our function like so:

...
if(req.method === 'POST') {
collectRequestData(req, result => {
console.log(result);
res.end(`Parsed data belonging to ${result.fname}`);
});
}
...

Fill in the form and submitting should display the message below:

Below is the full solution:

Limitations

You would have noticed that uploading a file sends only the file name to the backend and not the file itself. This is a limitation of the application/x-www-form-urlencoded media type. Using multipart/form-data will send the raw file along with its metadata. Perhaps in a future post, we will look at how this could be done!


I hope this has been an eye-opener, with respect to how the raw data format looks like being sent to the backend and how we would handle this. As always, please let me know if this helps and what you would like to see in future. Many thanks in advance.

Further Reading