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 Perfection Suggestions

Robert Stefanic
Robert Stefanic
35,170 Points

Can't get the CSS to load in the node.js server

So I've been trying to serve css in my dynamic website using node.js, but when my server serves css, it just serves it as plain/text and not CSS. I can't tell what I'm doing wrong. We didn't go over anything like this in the video, and I've been watching them over and over, but it seems like this challenge was out of left field with no help at all.

Here's what I have. In my router module, I included this in my home function:

//I defined two headers, one for text/html, and the other for text/css
var commonHeaders = {'Content-Type': 'text/html'};
var cssHeaders = {'Content-Type': 'text/css'};

//main function that's used to create the page

function home(request, response) {
  //if url == "/" && GET

  if (request.url === "/"){
    if(request.method.toLowerCase() === "get") {
      response.writeHead(200, cssHeaders);
      renderer.css(response);
      response.writeHead(200, commonHeaders);
      renderer.view("header", {}, response);
      renderer.view("search", {}, response);
      renderer.view("footer", {}, response);
      response.end();

    } else {
        //if url == "/" && POST
    //redirect to /:username

      request.on("data", function(postBody) {
        var query = querystring.parse(postBody.toString());
        response.writeHead(303, {"Location":"/" + query.username});
        response.end();
      });
    }
  }
}

My thought process here was that I could just change the Content-Type to text/css, and then everything could load perfectly fine. The video made it seem like that's all that needed to be done in order to load css properly.

Then in my renderer module, I added a function to add css, which looks similar to my view function that was called in the home function above. The view one works fine, but the css doesn't:

function css(response) {
  var cssFile = fs.readFileSync("./views/styles.css", {encoding: "utf8"});
  response.write(cssFile);
}
function view(templateName, values, response) {
  //READ from the Template files
  var fileContents = fs.readFileSync('./views/' + templateName + '.html', {encoding: "utf8"});

  //Insert values into content
  response.writeHead(200, {'Content-Type': 'text/html'});
  fileContents = mergeValues(values, fileContents);

  //write out contents to the response
  response.write(fileContents);
}

I added the view function here that's called in my router module because that one works, and looks similar to my css function; yet my css function doesn't work. Why is this? And on top of why this doesn't work, how can I get it to work? In my head, this all makes sense, but node.js doesn't think so hah.

Thanks for the help!

  • Rob
Robert Stefanic
Robert Stefanic
35,170 Points

Okay, I've simplified my code even more, and I still can't figure out why this doesn't work. In my main module, app.js (where I run the app), I tried to add all the code needed to load an external CSS file sheet there:

var http = require('http');
var server = http.createServer(function (request, response) {
  response.writeHead(200,{"Content-type" : "text/css"});
  var fileContents = fs.readFileSync('./views/styles.css', {encoding: "utf8"}, function(err, data) {
    if (!err) {
      response.write(data);
    } else {
      console.log(err);
    }
    });
  router.home(request, response);
  router.user(request, response);
});
server.listen(3000);

Now, it will load the page as if CSS isn't there (the HTML loades perfectly fine), and if I go to my tools in my browser, it has the HTML loaded that I requested, below the CSS that I try to import. Then there's an empty file inside of a folder named styles.css there in the dev tools, but the file is empty. I can't tell why it isn't writing to my css to it. Any thoughts or suggestions?

3 Answers

Robert Stefanic
Robert Stefanic
35,170 Points

Okay, after nearly 3 days of struggle, I figured it out! I hope this can help others and save them from the headaches as well.

So CSS doesn't get sent along with the pages in a dynamic site like this. The HTML gets sent to the client, and then the client requests the files from the server. So what I did was I added a router to the CSS in my app.js:

var server = http.createServer(function (request, response) {
  router.css(request, response);
  router.home(request, response);
  router.user(request, response);
});
server.listen(3000);

It's important to keep the request for the CSS file at the top, so that when the HTML gets sent and the DOM loads it, it can reference the CSS stylesheet that's linked in the header of the HTML. So now we have to make sure that the CSS file is properly linked in the HTML file.

    <link rel="stylesheet" href="styles.css"/>

I put this, where it belonged, in my header.html. The following was the first major thing that I learned doing this. In the normal static sites that'd I'd create, I'd have to reference the subfolder that it was in (so in my case for example, I have the CSS stylesheet in my views folder, and I'd normally reference it like so: href='./views/styles.css'). But on a dynamic site, when a request is made, and a response is sent, the response is sent to the '/', and NOT to the subfolder that's on the server side (at least by default). There is no subfolder on the client's side. Responses are simply just sent to the '/' by default. So make sure that the HTML is referencing the client side's files, and not the server side's files!

Now that the stylesheet is being properly referenced in the HTML, and the HTML can be styled, we can write the CSS request.

I just created a CSS function in the router module. I basically set it up so that if the request.url is '/styles.css', then make the content-type text/css, load the file (from the server side), and write it (in the response to the client).

My new function looks like this:

function css(request, response) {
  if (request.url === '/styles.css') {
    response.writeHead(200, {'Content-type' : 'text/css'});
    var fileContents = fs.readFileSync('./views/styles.css', {encoding: 'utf8'});
    response.write(fileContents);
  }
}  

This made it so that, there's is a copy of styles.css on the client's side, and then when the HTML loads, it can properly reference the stylesheet that's in the HTML CSS tag. I hope this has helped others! Best of luck!

Brendan Moran
Brendan Moran
14,052 Points

Thanks for this! This helped me. One important note: in your path test for the username path, you need to make sure that the path does not contain ".css". If we keep it just the way Andrew taught it, the username path is triggered by any request URL other than "/", which will include the style.css (or whatever), request that is made once your HTML file is loaded. (For this reason, maybe a normal get action would be less brittle than the post that redirects to /:username).

Also, your CSS router code could simplified a tiny bit and made more flexible. By putting the whole local path to the CSS file in the header of your HTML (i.e. ./views/styles.css), you can simply read the request.url in your CSS router. This adds the benefit of scalability: you can add multiple CSS files in your head (common practice), and this code will still work. Here's what I did:

In the header html:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>Treehouse Profile</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- link contains actual path to css -->
    <link rel="stylesheet" type="text/css" href="./styles/main.css" media="screen">
</head>

In the router CSS function:

function css (request, response) {
  if (request.url.indexOf(".css") !== -1){
    var file = fs.readFileSync(`.${request.url}`, {'encoding' : 'utf8'});
    response.writeHead(200, {'Content-Type' : 'text/css'});
    response.write(file);
    response.end();
  }
}

For the username route function:

function user(request, response) {

  var username = request.url.replace("/", "");
  if (username.length > 0 && request.url.indexOf('.css') === -1) {
    // ...etc.

I am going to mess with changing the form from post, back to GET, and seeing how that can improve the scalability of this app. The test for my username route function is still brittle. What if we wanted to get another .js file or load any other kind of file by means of a request? We can't just keep adding conditions to this if statement.

Brendan Moran
Brendan Moran
14,052 Points

Also, encoding does not need to be added to our fs.readFileSync. This is redundant. We are already stating in the header that the content is text/css. This will cause the buffer to be handled appropriately.

Brendan Moran
Brendan Moran
14,052 Points

Woops! I just discovered through some experimenting that you don't even need the response.writeHead statement. It seems that node is pretty smart about figuring out what our contents are made of on its own.

Przemyslaw Mazur
Przemyslaw Mazur
Courses Plus Student 9,296 Points

Could you please add a snapshot of your workspace cause non of the solutions seems to work for me...

pooya tolideh
pooya tolideh
12,184 Points

Here's a non-blocking way to serve CSS. I feel the page loads a little faster

//css router
const serveCSS = function (req, res) {
    if (req.url.indexOf('css') !== -1) {
        const css = fs.createReadStream(__dirname + req.url, 'utf8');
        css.pipe(res);
    }
};


//user router
const userRoute = function (req, res) {
        let username = req.url.replace('/', '');
        const miscUrls = req.url.indexOf('css') !== -1;
        if (username.length > 0 && !(miscUrls)) {
             // do stuff
        }
};

//app.js
http.createServer((req, res) => {
        router.serveCSS(req, res);
        router.home(req, res);
        router.user(req, res); 
});
Brandon Benefield
Brandon Benefield
7,739 Points

You must have an amazing grasp on node.js to come up with this. Did you figure this out on your own or from some external source?

Przemyslaw Mazur if you are getting a "write after end error" in your console, make sure that you are including the

&& request.url.indexOf('.css') === -1)

snippet to your if statement your user function. It should look like this:

if(username.length > 0 && request.url.indexOf('.css') === -1) {
}

Responding to Brandon Benefield Aug 6th 2016

I am going to mess with changing the form from post, back to GET, and seeing how that can improve the scalability of >this app. The test for my username route function is still brittle. What if we wanted to get another .js file or load any >other kind of file by means of a request? We can't just keep adding conditions to this if statement.

I took the function user request one step further so that it includes all extensions, not just .css
It's ugly and can certainly be simplified but it works so far...

Here is Brandon's original code

function user(request, response) {

  var username = request.url.replace("/", "");
  if (username.length > 0 && request.url.indexOf('.css') === -1) {
    // ...etc.

This code only checks for .css and not for other file types i.e. .png, .css, etc.

Here is the revised version -

function user(request, response) {

  var username = request.url.replace("/", "")

  var re = /(?:\.([^.]+))?$/; //regex expression to get a file extension. Thanks stack overflow!
  var username_ext = re.exec(username); //apply regex expression to extract file extension
  if (username_ext.toString() === ",") username_ext = undefined; //basically if there is no extension 
                                //(extension would equal just a comma) set as undefined 

  if (username.length > 0 && username_ext === undefined) { 
      //checks to ensure username has no extension defined as a .xxx i.e. .png, .css, .jpg, .12345 etc.
    // ..etc.
Robert McNeil
Robert McNeil
6,466 Points

There may be a simpler solution using some other modules. I had a similar issue where my javascript files wasnt coming in. So i added the modules "path" and "body-parser"

  const path = require('path');
  const bodyParser = require('body-parser');

  //body parser middleware
  app.use(bodyParser.json());
  app.use(bodyParser.urlencoded({extended: false}));
  app.use(express.static(path.join(__dirname, 'public')));

This allowed my html file to access the css and the javascript files in the public folder.

Hope this helps.

lauralouise
lauralouise
14,852 Points

FYI 'body-parser' is middleware that will have to be installed via NPM. it's not a native node module!