Skip to content
Advertisement

Res.Render Is Stalling

Sorry if I’m sharing too much (or too little!) I’m not sure how to provide code when it’s contained in multiple files like this.

In the small project I have my app.js server using express and ejs. I have a “/compose” route which creates a blog post with a “Title” and “Content”. It then pushes this into a global array called “posts”.

In the “/posts” route, my intention is that the server is supposed to loop over the “posts” array containing the posts and match the correct post title with the “postName” parameter given in the URL. (I am trying to make the Titles case insensitive with the .toLowerCase() method on the strings.)

However, when I do this, the page just stalls and doesn’t actually load. A curious bug though is that if I “reload” the server (I’m running nodemon) by even making a small change to the app.js code such as changing a comma or commenting out a line, such that nodemon reloads, the page will display the requested information.

// This is my app.js code
const express = require("express");
const ejs = require("ejs");
const app = express();

app.set('view engine', 'ejs');

app.use(express.urlencoded({
  extended: true
}));
app.use(express.static(__dirname + "/public"));

const posts = [];

app.get("/posts/:postName", (req, res) => {
  const requestedTitle = req.params.postName.toLowerCase();
  posts.forEach(post => {
    const storedTitle = post.title.toLowerCase();
    if (requestedTitle === storedTitle) {
      res.render("post", {
        post: post
      });
    }
  });
});

app.post("/compose", (req, res) => {
  const post = {
    title: req.body.postTitle,
    content: req.body.postBody
  };
  posts.push(post);
  res.redirect("/")
})
<!-- This is my "Post" route HTML code (a partial called post.ejs) -->
<h1>This is
  <%= post.title%>
</h1>

<!-- This is my "Compose" route HTML Code (a partial called compose.ejs) -->
<h1>Compose</h1>
<form action="/compose" method="POST">
  <div class="form-group">
    <label for="postTitle">Title</label>
    <input class="form-control" type="text" name="postTitle" autofocus>
    <label for="postBody">Post</label>
    <textarea class="form-control" name="postBody" id="" cols="30" rows="5"></textarea>
  </div>
  <button class="btn btn-primary" type="submit" name="button">Publish</button>
</form>

Answer

The main problem I see is that your app.get("/posts/:postName", ...) route needs to always send one and only one http response. The way it is coded now, it will send no response if a matching post isn’t found and it will attempt to send multiple responses if more than one is found. Both conditions are a problem.

Here’s one way to fix that:

app.get("/posts/:postName", (req, res) => {
    const requestedTitle = req.params.postName.toLowerCase();
    for (let post of posts) {
        const storedTitle = post.title.toLowerCase();
        if (requestedTitle === storedTitle) {
            res.render("post", {
                post: post
            });
            return;
        }
    }
    // send some sort of response here with a 404 status
    res.sendStatus(404);
});

As a possible explanation for the server behavior you saw, the browser will only send so many requests in-flight at the same time to the same host. So, if with your original code, you are playing with URLs that don’t have matching posts, those will not get responses sent back to the browser from the server and those requests will still be waiting for a response. Eventually the browser will hit the max requests that it will send to your host until some of them finish. Thus, it will appear that your server becomes non-responsive to new requests, when in reality it’s just waiting for prior requests to finish. You must always send one and only one response from your server to each incoming http request.


I assume this posts array is probably just a temporary storage mechanism, but it would be a lot more efficient if you replaced the array with a Map object with the lowercase title as the key. Then, you could just directly lookup the matching title on the Map object with one map.get() operation rather than looping through the whole array.

Advertisement