Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript Build a Simple Dynamic Site with Node.js HTTP Methods and Headers Sending Content Type Headers in Node.js

Amber Diehl
Amber Diehl
15,402 Points

Error "write after end" being thrown when calling the /username route.

I am getting the above error when trying to get a valid or invalid user name. I should mention that the page renders perfectly but when I return to the terminal, the server has crashed. Below is a sample trace and the related code.

Trace

events.js:182
      throw er; // Unhandled 'error' event
      ^

Error: write after end
    at write_ (_http_outgoing.js:620:15)
    at ServerResponse.write (_http_outgoing.js:615:10)
    at Object.view (/Users/amberdiehl/js_development/TreehouseNodeServer/renderer.js:32:11)
    at Profile.<anonymous> (/Users/amberdiehl/js_development/TreehouseNodeServer/router.js:28:13)
    at emitOne (events.js:115:13)
    at Profile.emit (events.js:210:7)
    at IncomingMessage.<anonymous> (/Users/amberdiehl/js_development/TreehouseNodeServer/profile.js:38:36)
    at emitNone (events.js:110:20)
    at IncomingMessage.emit (events.js:207:7)
    at endReadableNT (_stream_readable.js:1057:12)

app.js

const http = require('http');
var router = require('./router');

const hostname = '127.0.0.1';
const port = 8000;

//1. Create web server
const server = http.createServer((request, response) => {
    router.home(request, response);
    router.user(request, response);
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

router.js

var Profile = require("./profile.js");
var renderer = require("./renderer.js");

var commonHeader = {"Content-Type": "text/html"} // was {"Content-Type": "text/plain"}

function homeRoute(request, response){
    if (request.url === '/') {
        response.writeHead(200, commonHeader);
        renderer.view('search', {}, response);
    }
}

function userRoute(request, response){
    var userName = request.url.replace('/', '');
    if (userName.length > 0) {

        var values = {};

        var studentProfile = new Profile(userName);
        studentProfile.on("end", function(profileJSON) {

            values["avatarUrl"] = profileJSON.gravatar_url;
            values["username"] = profileJSON.profile_name;
            values["badges"] = profileJSON.badges.length;
            values["javaScriptPoints"] = profileJSON.points.JavaScript;

            response.writeHead(200, commonHeader);
            renderer.view('profile', values, response); 
        });

        studentProfile.on("error", function(error){

            values["errorMessage"] = error.message;
            response.writeHead(200, commonHeader);
            renderer.view('search', values, response);
        });

    }
}

module.exports.home = homeRoute;
module.exports.user = userRoute;

renderer.js

var fs = require('fs');

function mergeValues(values, content){

    for (var key in values) {
        content = content.replace("{{" + key + "}}", values[key]);
    }
    return content;
}

function view(templateName, values, response) {

    // If error, render error template first and pass to content
    if ('errorMessage' in values) {
        var errorTemplate = fs.readFileSync('./templates/error.html', 'utf8');
        errorTemplate = mergeValues(values, errorTemplate);
        values["errorTemplate"] = errorTemplate;
    } else {
        values["errorTemplate"] = '';
    }

    // Get content template and merge data values
    var contentTemplate = fs.readFileSync('./templates/' + templateName + '.html', 'utf8');
    contentTemplate = mergeValues(values, contentTemplate);

    // Get base template and merge content template
    var baseTemplate = fs.readFileSync('./templates/base.html', 'utf8');
    values["body"] = contentTemplate;
    baseTemplate = mergeValues(values, baseTemplate);

    // Write response
    response.write(baseTemplate);
    response.end();
}

module.exports.view = view;

Regarding the templates (the html files located in the "views" folder that I placed in a "templates" folder), I have created a base.html which is similar to but not the same as the 'header.html' in the course, the key difference being that it includes a {{body}} variable and the closing html tag.

In turn, either profile.html or search.html are being rendered into the {{body}} variable. And finally, the search.html has a {{errorTemplate}} tag that is replaced with the error div (and message) if the user profile is not found.

I can share the contents of these templates, of course, if that would be of value but since they're rendering did not include them.

Thanks in advance to any help!

Amber Diehl
Amber Diehl
15,402 Points

Actually, I have more data based on using some console.log information.

It appears that a request to the server is being made before I can even finish typing in the url. If you look at the log statements below you'll see that the call to my profile succeeded and the server didn't crash. But then when I try adding a number to my name WITHOUT PRESSING ENTER, it seems to have made a request anyway and on the heels of that another one with 123 which crashes.

I am truly stumped about why this would be happening. Help!!

Below the first /amberdiehl is from the request.url that is within the "home" route. There is no "home url executed" as would be expected. The second /amberdiehl is within the userRoute and the following console message is correct. But the next bit I don't get--I never typed /amberdiehl1 and pressed enter.

Server running at http://127.0.0.1:8000/
/amberdiehl
/amberdiehl
user url executed
/amberdiehl1
/amberdiehl1
user url executed
/amberdiehl123
/amberdiehl123
user url executed
events.js:182
      throw er; // Unhandled 'error' event
      ^

1 Answer

Amber Diehl
Amber Diehl
15,402 Points

Okay, I think I solved this (though it's a hack). Hopefully, this may save someone else a lot of time.

I moved the logging of the request.url to the server before the routes were executed and saw that /favicon.ico was being executed (though not all the time, from what I can tell). That route would be executed immediately after a legit user profile request and was causing the failure.

I added this to the app.js and all is working:

    if (request.url === '/') {
        router.home(request, response);
    } else if (request.url != 'favicon.ico') {
        router.user(request, response);
    }

There must be a better way; implementing something similar to Django's url.py and using regular expressions to invoke views. :)

Gabbie Metheny
Gabbie Metheny
33,778 Points

I had the same problem and did a similar hack!

I made my change inside the https.get() in profile.js (coded by Andrew, we never touch this file, although I had already gone in to make it more ES6-friendly) because the custom error message I was getting came from this file, with favicon.ico being popped in as the username... >_<

There was an error getting the profile for favicon.ico. (Not Found)

if (res.statusCode !== 200 && username !== 'favicon.ico') {
  req.abort();
  profileEmitter.emit('error', new Error(`There was an error getting the profile for ${username}. (${res.statusMessage})`));
}

Glad to see I wasn't the only one having favicon problems, and glad you figured it out!