DEV Community

Brandon Weaver
Brandon Weaver

Posted on

Handling POST Requests in Node.js

Last week I covered simple routing with Node.js. If you haven't already read the article, you can find it here. This week I wanted to expand on the introduction, and I believe that the next step is handling POST requests, so let's get started!

Take a moment to set up your file structure.

controllers
    app_controller.js
    items_controller.js
views
    items
        index.html
        new.html
    index.html
app.js

Now, place the following markup in your top-level index.html.

<!DOCTYPE html>
<html>
    <head>
        <title>Index</title>
    </head>
    <body>
        <h1>Index</h1>
        <a href="/items">Items</a>
    </body>
</html>

Place the following markup in items/index.html.

<!DOCTYPE html>
<html>
    <head>
        <title>Items</title>
    </head>
    <body>
        <h1>Items</h1>
        <a href="/">Index</a>
        <a href="/items/new">Create Item</a>
    </body>
</html>

For our final template, we're going to create a form which will serve as our means to submit a POST request. Place the following markup in items/new.html, and be sure that the form's action field is set to "/items" and its method field is set to "POST". It's also worth mentioning that the value of the name field of our text input will be the name of the parameter which we extract data from when we handle the request.

<!DOCTYPE html>
<html>
    <head>
        <title>Create Item</title>
    </head>
    <body>
        <h1>Create Item</h1>
        <a href="/items">Items</a>
        <form action="/items" method="POST">
            <input type="text" name="value" />
            <input type="submit" />
        </form>
    </body>
</html>

With our views in place, let's focus on building our controllers. We'll start with a simple application controller. Place the following code in app_controller.js.

const fs = require("fs");

const appController = (request, response) => {
    if (request.url === '/') {
        fs.readFile("./views/index.html", (error, html) => {
            if (error) throw error;
            response.write(html);
            return response.end();
        });
    }
}

module.exports = appController;

We're going to separate the logic for our items from our application controller to keep things tidy. To do that, we'll first place the following code in items_controller.js

const fs = require("fs");

const items = [];

const itemsController = (request, response) => {
    if (request.url === "/items") {
        fs.readFile("./views/items/index.html", (error, html) => {
            if (error) throw error;
            response.write(html);
            response.write("<ul>");
            for (const item of items) {
                response.write(`<li>${item}</li>`);
            }
            response.write("</ul>");
            return response.end();
        });
    }
    if (request.url === "/items/new") {
        fs.readFile("./views/items/new.html", (error, html) => {
            if (error) throw error;
            response.write(html);
            return response.end();
        });
    }
}

module.exports = itemsController;

You may be wondering about the block of code dedicated to our items route. For this example, we're going to render our items in an unordered list. We need to generate the list dynamically to account for changes made to the items array. We're simply making multiple calls to response.write in order to append the elements manually.

Now, Let's integrate our items controller. To do this, we simply call it from within the application controller, being sure to pass the appropriate arguments down.

app_controller.js should now look as follows.

const fs = require("fs");

const itemsController = require("./items_controller");

const appController = (request, response) => {
    if (request.url === '/') {
        fs.readFile("./views/index.html", (error, html) => {
            if (error) throw error;
            response.write(html);
            return response.end();
        });
    }
    itemsController(request, response);
}

module.exports = appController;

Before we worry about implementing the logic for our POST request, we need to create our server by placing the following code in app.js.

const http = require("http");
const controller = require("./controllers/app_controller");
http.createServer(controller).listen(3000);

At this point, it would probably be worthwhile to run the following command in your terminal.

node app.js

Visit localhost:3000 in your browser, and confirm that you are able to navigate to each route. You can also take this opportunity to test the form submission. You'll notice that upon submission, we're directed to the /items route, however, our entry doesn't appear on the document. Let's get that sorted out!

To implement this behavior, we only need to alter our /items route, and leverage a Node.js package called querystring to parse the incoming parameter.

We need to start by creating two separate execution paths within the route. We will check the method property of our request parameter to create these branches.

The overall layout of our route will look as follows.

if (request.url === "/items") {
    if (request.method === "POST") {
        ...
    }
    else {
        fs.readFile("./views/items/index.html", (error, html) => {
            if (error) throw error;
            response.write(html);
            response.write("<ul>");
            for (const item of items) {
                response.write(`<li>${item}</li>`);
            }
            response.write("</ul>");
            return response.end();
        });
    }
}

As you can see, our GET request logic will now only execute in the event that the method property of the request parameter is not equal to "POST".

Now, for the somewhat tricky part. Due to the asynchronicity of HTTP requests, we'll need to utilize event-driven behavior to capture the incoming data, and push the value into our array using callbacks.

When we're finished implementing this feature, items_controller.js will look as follows.

const fs = require("fs");
const qs = require("querystring");

const items = [];

const itemsController = (request, response) => {
    if (request.url === "/items") {
        if (request.method === "POST") {
            let body = '';
            request.on("data", chunk => {
                body = `${body}${chunk.toString()}`;
            });
            request.on("end", () => items.push(qs.parse(body).value));
            response.writeHead(301, { Location: "/items" });
            return response.end();
        }
        else {
            fs.readFile("./views/items/index.html", (error, html) => {
                if (error) throw error;
                response.write(html);
                response.write("<ul>");
                for (const item of items) {
                    response.write(`<li>${item}</li>`);
                }
                response.write("</ul>");
                return response.end();
            });
        }
    }
    if (request.url === "/items/new") {
        fs.readFile("./views/items/new.html", (error, html) => {
            if (error) throw error;
            response.write(html);
            return response.end();
        });
    }
}

module.exports = itemsController;

At a quick glance, you'll notice two calls to an unfamiliar method of the request parameter named on. The first argument we pass to this method is the event we're listening for. When the event is triggered, our callback will be invoked.

When the "data" event is triggered, we're passing a parameter named chunk into an anonymous function which serves as our callback. Within this function we're concatenating the stringified chunk of data to an empty string we've named body. This data contains the value we want to extract, however, we need to gather the data in chunks as they come through.

The "end" event is triggered once the request is finalized. This is the perfect time to parse our body variable, and store the value property of the newly returned object in our items array, as we can be sure we have all of the data required at this time. Finally, we write a status code of 301, and a location of "/items" to the head of our response. This will invoke a redirect to /items once we call response.end, which follows immediately.

Once again, enter the following command in your terminal.

node app.js

Visit localhost:3000/items/new in your browser, fill out the text field and press submit. You should now see your entry displayed in the unordered list! Note that you are able to submit multiple entries, although, once you close the application, the entries will be lost, as we aren't persisting the data.

I hope some of you were able to find this small tutorial helpful. Next week I will cover data persistence with a SQLite database, and the sqlite3 Node.js package. I will take that opportunity to discuss rendering JSON for API setups as well. We will begin that endeavor where we left off here.

Top comments (0)