Search code examples
javascripthtmlcssnode.jsless

Node.js doesn't load CSS and images


Situation

I'm an absolute begginer to Node.js, I'm mostly just trying to implement an npm package into my website but I am struggling to run my existing HTML and CSS (.less specifically).

I am not using express.

My code

Here's my current index.js:

const http = require("http");
const fs = require("fs");

const PORT = 3030;

fs.readFile('website/index.html', function (error, html) {
    if (error) {
        console.error("Error reading HTML file:", error);
        return;
    }

    http.createServer(function (request, response) {
        response.writeHeader(200, {"Content-Type": "text/html"});
        response.write(html);
        response.end();
    }).listen(PORT);

    console.log(`Server is running on port ${PORT}`);
});

Issue

When I enter the page it's just plain HTML with no images and any kind of css. The issue is not with LESS, I tried using just CSS aswell.

Potential solution

I think the issue is that I have to tell the server to load the image and style like I did with HTML but I don't know how, I couldn't find any question or tutorial for this since I don't use Express.

Any help is appreciated, I'm willing to provide more info on need.


Solution

  • Welcome to Stack Overflow!

    Looking at your code, it makes total sense that you're only seeing a plain HTML file — the browser is probably trying to load styles and images, but your backend is hardcoded to only ever return your HTML file.

    If you look at this section of your code:

        http.createServer(function (request, response) {
            response.writeHeader(200, {"Content-Type": "text/html"});
            response.write(html);
            response.end();
        }).listen(PORT);
    

    This code creates an HTML server that, only every request, responds with the contents of the html variable with the Content-Type header set to html. Since the html variable only contains the contents of website/index.html, that is all that will ever be sent whenever any page is visited!

    There are a couple ways to serve everything.

    Plain Node.js Solution (Naive)

    If you want to keep using plain Node.js without any external libraries, you can access request.url to return different files depending on what path was requested. When, for example, http://localhost:3030/file.txt is loaded, the value of request.url will be "/file.txt".

    A naive solution is to use a switch statement, like so:

    switch (request.url) {
        case "/": {
            response.writeHeader(200, {"Content-Type": "text/html"});
            fs.createReadStream("website/index.html").pipe(response);
            response.end();
            break;
        }
        case "/file.txt": {
            // serve file.txt
            // etc.
        }
    }
    

    Note: what are createReadStream() and .pipe()?

    That line basically just feeds the contents of the file into the HTTP response.

    createReadStream() creates something called a stream, which is a little bit like it sounds — a stream of bytes flowing from somewhere into something else. Calling .pipe(thing) on a stream lets you choose what the stream flows into. In this case, we're streaming data from a file into the HTTP response.

    You can learn more about streams in this lovely FreeCodeCamp article.

    However, this quickly falls apart when you have lots of different files and images. It sucks to have to hardcode another branch every time you want to add another file. It would be much better just to serve up everything in your filesystem...

    Plain Node.js Solution (Filesystem)

    I heavily encourage you to figure this out yourself, but I will provide a couple pointers.

    To get a file path, you can simply concatenate "website" + request.url. If request.url is "/file.txt", this will be "website/file.txt", giving you the right file path!

    However, notice that paths ending in / typically serve a file called index.html instead. You can handle this with an if case, like so:

    let path = "website" + request.url;
    if (path.endsWith("/")) {
        path += "index.html";
    }
    // now serve the file at path!
    

    This appends index.html to the end of the path if it ends with /.

    Finally, be really careful with this method! You can easily let anyone on the internet access any file on your computer simply by using ... You might know from terminals that .. means "previous directory," and this works when reading files in Node.js too! You should make sure to remove all instances of /.. from the path, perhaps like so:

    path = path.replaceAll("/..", "");
    

    Boring "Install a Library" Solution

    If you don't care about learning vanilla Node.js, or you want to experiment with some more complex code, Express is a very popular HTTP server library.

    Using Express, you can implement a static server (that's also ..-safe) in just a couple lines:

    const express = require("express");
    const app = express();
    
    app.use(express.static("website"));
    
    app.listen(3030, () => {
        console.log("Server is running on port 3030");
    });
    

    (Make sure to run the command npm install express to install the library.)

    I recommend exploring the Express documentation to learn more if you want to go down this route!