Welcome to the Treehouse Community

The Treehouse Community is a meeting place for developers, designers, and programmers of all backgrounds and skill levels to get support. Collaborate here on code errors or bugs that you need feedback on, or asking for an extra set of eyes on your latest project. Join thousands of Treehouse students and alumni in the community today. (Note: Only Treehouse students can comment or ask questions, but non-students are welcome to browse our conversations.)

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and a supportive community. Start your free trial today.

JavaScript Build a Simple Dynamic Site with Node.js HTTP Methods and Headers Perfection Suggestions

Tom Geraghty
Tom Geraghty
24,162 Points

Extra Credit - Add capability to load css files instead of embedded style element in header.html file.

I modified some files following the leads of Tim (here) and Sim (here) but still cannot get this to load a separate css file. Included in the header.html file is this line:

<link rel="stylesheet" href="./views/style.css" type="text/css">

First the app.js file now includes this:

var router = require("./js/router.js");
// Create a web server
var http = require('http');
http.createServer(function (request, response) {
  //If it's the home page, display home
  if (request.url === '/') {
    router.view(request, response);
  } else { //It's a user page, display user page content
      router.user(request, response);
  }
  router.css(request, response);
  router.js(request, response);
}).listen(5000);

If it's the homepage it loads the homepage otherwise it loads the user page. After those are done it should load the css file. The renderer.js and profile.js files are essentially unchanged. But (again following Tim above) I changed router.js to include more methods such as:

// Handle HTTP route for the CSS file
function css(request, response) {
  if(request.url.indexOf('style.css') != -1){
      var css = fs.readFileSync('./views/style.css');
      response.writeHead(200, {'Content-Type': 'text/css'});
      response.write(css);
      response.end();
  }
}

The home page will correctly load up and display as will a user page (with correct results from the json object) if you manually type /chalkers or whoever into the url.

The search function no longer works. If you attempt to use it node crashes with a "write after end" error displayed. But the content being loaded should be using the fs.readFileSync command so idk why that is happening.

Second problem: None of the displayed information has styling applied from the stylesheet.

Any and all help is greatly appreciated!

You can view my file structure and complete code on GitHub here

3 Answers

Tom Geraghty
Tom Geraghty
24,162 Points

After several days and many attempts I found the answer. I'm sharing it here to hopefully prevent other people from all the headaches it caused me.

The main issue is that the instance of app.js needs to account for all possible HTTP type requests. The original setup has it only serving Content-Type: text/html through your router.js file. If you leave it as is and then put a normal

<link rel="stylesheet" href="./views/style.css" type="text/css">

tag in your html file it is sent through your app.js webserver which only had the option of serving two pages: the homepage and a /username page. Even though the type='text/css' is in the html tag, it still is sent through the node webserver which only knows how to serve type: text/html.

To correct for this I added a series of if/else checks for different possible content types (such as javascript, css, html, you could probably add more for images, pdf, etc etc etc) like this in app.js:

var router = require("./js/router.js");

// Create a web server
var http = require('http');
http.createServer(function (request, response) {
  if(request.url === "/") {
    router.routeHome(request, response);
  } else if(request.url.indexOf('.css') != -1) {
    router.routeCss(request, response);
    } else if(request.url.indexOf('.js') != -1) {
      router.routeJs(request, response);
    } else if(request.url.indexOf('.ico') != -1) {
      //there is no favicon.ico at this time, do nothing
      } else {
        router.routeUser(request, response);
        }
}).listen(1337);
console.log('Server running on port: ' + (process.env.PORT !== undefined ? process.env.PORT : 1337));

This way the url being requested determines what filetypes are served.

The second thing you have to do is make functions in your router.js file to handle these file types. For example for css I wrote this (in router.js):

function routeCss(request, response) {
    var css = fs.readFileSync('./views/style.css');
    response.writeHead(200, {'Content-Type': 'text/css'});
    response.write(css);
    response.end();
}

And another helper function for JavaScript which is essentially the same as above.

Now the server (app.js) knows how to handle different content types and your router (router.js) handles the requests made of it by your server appropriately.

A lot of time was spent on various forums and help sites and they pretty much all say: Use express or some other framework or library. Since I wanted to understand the underlying problem I couldn't accept this answer. But delving further into it there are WAY too many content types for it to be feasible to roll your own server. So yeah, use express or something else to make sure you handle all of the million content type dependencies.

You can check out my complete code on GitHub here

Sean Pascoe
Sean Pascoe
17,151 Points

expanding on Tom Geraghty (thanks!)... -I put the file extension conditional logic in the router.js file as part of a third "route". -I used a variable for the resource url so that external resources can be added, removed, changed.

app.js

var router = require('./router.js');

//Problem: Simple way to look up a user's badge count and Javascript points from a web browser
//Solution: Use node.js to perform the profile look-ups and serve our templates via http


//Create a nodesj webserver
var http = require('http');
http.createServer(function (request,response) {
    router.home(request, response);
    router.user(request, response);
    router.xtr(request, response);

}).listen(3000);

router.js

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

var commonHeaders = {'content-type': 'text/html'};

//Handle HTTP route GET / and POST / i.e. Home
function homeRoute(request, response) {
    //if url === "/" && GET
    if (request.url === "/") {
        if (request.method.toLowerCase() === "get") {
        response.writeHead(200, commonHeaders);
        renderer.view("header", {}, response);
        renderer.view("search", {}, response);
        renderer.view("footer", {}, response);
        response.end();
        } else {
            request.on("data", function(postBody) {
                var query = querystring.parse(postBody.toString());
                response.writeHead(303, {"location": "/" + query.username});
                response.end();
            });
        }
    }
}

//Handle HTTP route GET /:username i.e. /chalkers
function userRoute(request, response) {
    //if url === "/...."
    var username = request.url.replace("/", "");
    if (username.length > 0 && (request.url.indexOf('.') == -1)) {
    response.writeHead(200, commonHeaders);
    renderer.view("header", {}, response);

    //get json from treehouse
    var studentProfile = new Profile(username);
    //on "end"
    studentProfile.on("end", function (profileJSON) {
        //show profile

        //store the value which we need
        var profValues = {
            avatarUrl: profileJSON.gravatar_url,
            name: profileJSON.name,
            username: profileJSON.profile_name,
            badges: profileJSON.badges.length,
            javascriptPoints: profileJSON.points.JavaScript
        };
        //simple response
        renderer.view("profile", {
            avatarUrl: profileJSON.gravatar_url,
            name: profileJSON.name,
            username: profileJSON.profile_name,
            badges: profileJSON.badges.length,
            javascriptPoints: profileJSON.points.JavaScript
        }, response);
        renderer.view("footer", {}, response);
        response.end();
    });
    //on "error"
    studentProfile.on("error", function(error) {
        //show error
        renderer.view("error", {errorMessage: error.message}, response);    
        renderer.view("search", {}, response);  
        renderer.view("footer", {}, response);
        response.end();
    });         

    }
}

function xtrRoute (request, response) {
    var xtrUrl = "";
    var xtr = "";
    var isXtr = false;
    if (request.url.indexOf('.css') != -1) {
        response.writeHead(200, {'Content-Type': 'text/css'});
        isXtr = true;
    } else if (request.url.indexOf('.js') != -1) {
        response.writeHead(200, {'Content-Type': 'application/javascript'});
        isXtr = true;
    }

    if (isXtr) {
        xtrUrl = `./${request.url}`;
        xtr = fs.readFileSync(xtrUrl);
        response.write(xtr);
        response.end();
    }
}

module.exports.home = homeRoute;
module.exports.user = userRoute;
module.exports.xtr = xtrRoute;
Kirsty Pollock
Kirsty Pollock
14,260 Points

very neat - but I am a bit taken aback that all these variants have to get so elaborate, just to include a css file on some pages...

I just added a README with instructions, organized the css into a css folder and added a main.js file with a simple console.log to show that it is also being pulled in externally. But I basically borrowed all the great work of Tom Geraghty.

I also made an effort to place 40 informative commits for each step a long the way. Hope it helps.

My Github Repo