ExpressJS has become so popular that normally, all use-cases in NodeJS gravitate towards using Express. However, almost all things can also be done in NodeJS. In this post, we look at how to serve static HTML files in NodeJS without Express.

In case you are new to NodeJS, I recommend going through the posts about NodeJS Webserver and NodeJS modules first.

Serve Static HTML with NodeJS Webserver

To start with our demo application, follow the below preparatory steps:

  • Create a new project folder known as serve_html.
  • Within this folder, create a blank file index.js.
  • Create another folder called views within the serve_html folder.

With this, we have a basic project structure.

Next, we can create a file named index.html within the views folder.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Home Page</title>
    </head>
    <body>
        <h1>Welcome to the Home Page HTML</h1>
    </body>
</html>

This is a very basic HTML file. Nothing fancy about it.

To render this HTML file in the browser, we need to serve this file when user makes a request to our NodeJS webserver.

Below is the code for the webserver.

const port = 3000;

//NodeJS Core Modules
const http = require('http');
const fs = require('fs');

//Third-Party Modules
const httpStatus = require('http-status-codes');

const routeMap = {
    "/": "views/index.html"
};

server = http.createServer((request, response) => {
    response.writeHead(httpStatus.StatusCodes.OK, {
        "Content-Type": "text/html"
      });

    if (routeMap[request.url]) {
        fs.readFile(routeMap[request.url], (error, data) => {
            response.write(data);
            response.end();
        })
    } else {
        response.end("<h1>Sorry, page not found</h1>");
    }
})

server.listen(port);
console.log(`The server is listening on port: ${port}`);
  • First of all, we require a couple of core NodeJS module – http and fs.
  • The http module helps us create a webserver. On the other hand, the fs module allows us to read the filesystem.
  • We also use a third-party package http-status-codes to handle the status codes. We can install this package using npm install http-status-codes --save.
  • Next, we declare a routeMap. Basically, we are mapping the URL to the appropriate file within the views folder.
  • Using the http module, we create a NodeJS server instance. Within the callback function of http.createServer, we check the routeMap. If the request.url the user tried to access is supported, we use the fs module to read the HTML file. If the url is not supported, we return a response informing that the page is not found.
  • At the end, we start the server to listen on port 3000.

Serve Static HTML file with NodeJS Dynamically

In the previous example, we served just one file i.e. index.html. However, in a more complex application, we might need to serve multiple files. The approach of using a routeMap may not be the most optimum.

For multiple-pages, we can dynamically fetch files based on the user’s request.

To demonstrate the same, we create another HTML file about.html.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>About Page</title>
    </head>
    <body>
        <h1>Welcome to the About Page HTML</h1>
    </body>
</html>

Next, we update the index.js file as below.

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

//Third-Party Modules
const httpStatus = require('http-status-codes');

const getViewUrl = (url) => {
    return `views${url}.html`;
};

server = http.createServer((request, response) => {
    let viewUrl = getViewUrl(request.url);
    fs.readFile(viewUrl, (error, data) => {
        if (error) {
            response.writeHead(httpStatus.StatusCodes.NOT_FOUND);
            response.write("<h1>File Not Found</h1>")
        } else {
            response.writeHead(httpStatus.StatusCodes.OK, {
                "Content-Type": "text/html"
            })
            response.write(data);
        }
        response.end();
    })
})

server.listen(port);
console.log(`The server is listening on port: ${port}`);

Basically, we tweak the logic of determining the file path. In place of the routeMap, we have a function getViewUrl(). This function takes the request.url as input and provides the complete path. For example, if user accesses http://localhost:3000/about, the getViewUrl will return views/about.html.

Using the path from the getViewUrl() function, we use the fs module to read the correct HTML file and send the data back as response. In case there is an error while reading the file (when the file doesn’t exist), we return File Not Found.

Serving Static Assets using NodeJS Webserver

The third example is for serving other static assets apart from HTML files.

Your application’s assets are the images, stylesheets, and JavaScript that work alongside your views on the client side. Like your HTML files, these file types (such as .png and .css) need their own routes to be served by your application.

To handle these assets, we create another folder named public within our project directory. Inside the public folder, we create three more folders – images, css and js.

Below is the structure of our project after this change is done.

serve static html nodejs without express

To handle the different folders and asset types, we also make appropriate modifications in the index.js file.

Check out the below code.

const port = 3000;

//NodeJS Core Modules
const http = require('http');
const fs = require('fs');

//Third-Party Modules
const httpStatus = require('http-status-codes');

const getViewUrl = (url) => {
    return `views${url}.html`;
};

const sendErrorResponse = res => {
    res.writeHead(httpStatus.StatusCodes.NOT_FOUND, {
        "Content-Type": "text/html"
    });
    res.write("<h1>File Not Found!</h1>");
    res.end();
};

const customReadFile = (file_path, res) => {
    if (fs.existsSync(file_path)) {
        fs.readFile(file_path, (error, data) => {
            if (error) {
                console.log(error);
                sendErrorResponse(res);
                return;
            }
            res.write(data);
            res.end();
        });
    } else {
        sendErrorResponse(res);
    }
};

server = http.createServer((request, response) => {
    let url = request.url;

    if (url.indexOf(".") == -1) {
        response.writeHead(httpStatus.StatusCodes.OK, {
            "Content-Type": "text/html"
        });
        customReadFile(getViewUrl(url), response);
    } else if (url.indexOf(".js") !== -1) {
        response.writeHead(httpStatus.StatusCodes.OK, {
            "Content-Type": "text/javascript"
        });
        customReadFile(`./public/js${url}`, response);
    } else if (url.indexOf(".css") !== -1) {
        response.writeHead(httpStatus.StatusCodes.OK, {
            "Content-Type": "text/css"
        });
        customReadFile(`./public/css${url}`, response);
    } else if (url.indexOf(".png") !== - 1) {
        response.writeHead(httpStatus.StatusCodes.OK, {
            "Content-Type": "image/png"
        });
        customReadFile(`./public/images${url}`, response);
    } else {
        sendErrorResponse(response);
    }
})

server.listen(port);
console.log(`The server is listening on port: ${port}`);

Basically, the change revolves around as bunch of if-else conditions that handle different types of assets. For the HTML files, we continue following the same approach using the getViewUrl() function from the previous section. However, for other assets, we check if the request.url contains a file extension. Based on the file extension, we access data from the appropriate folder and return the data as response.

For example, if the user requests for a .js file, we access /public/js folder. If the file is not found, we return a File not Found message.

Conclusion

As you can see, we don’t always need heavy-weight frameworks such as Express to fulfil small requirements. In this post, we saw how to serve static HTML files in NodeJS without Express.

In the process, we learnt how to leverage the fs module to access the file system to render HTML.

Categories: BlogNodeJS

Saurabh Dashora

Saurabh is a Software Architect with over 12 years of experience. He has worked on large-scale distributed systems across various domains and organizations. He is also a passionate Technical Writer and loves sharing knowledge in the community.

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *