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 theserve_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
andfs
. - The
http
module helps us create a webserver. On the other hand, thefs
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 usingnpm install http-status-codes --save
. - Next, we declare a
routeMap
. Basically, we are mapping the URL to the appropriate file within theviews
folder. - Using the
http
module, we create a NodeJS server instance. Within the callback function ofhttp.createServer
, we check therouteMap
. If therequest.url
the user tried to access is supported, we use thefs
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.
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.
0 Comments