Serving static file

Serving static files

To read files and serve static them using Node.js, we need a way to locate and read files and then return the file to the end user, here where it comes the fs and path modules of Node.js.

fs (File System) module in Node.js

The fs module provides an API for interacting with the file system in a manner closely modeled around standard POSIX functions. It provides a lot of very useful functionality to access and interact with the file system. There is no need to install it because it is a part of the Node.js core. The fs module is responsible for all the asynchronous or synchronous file I/O operations.

Using fs module

const fs = require("fs");

Check if file exists Synchronously

fs.existsSync(filePath);

Read file Synchronously

fs.readFileSync(filePath);

For more information on how to read and create files Asynchronously and Synchronously in Node.js, visit the following link: Create and Read a file in Node.js

path module in Node.js

The path module provides utilities for working with file and directory paths. It can be accessed using:

Using path module

const path = require("path");

join paths in Node.js

To join paths we use the join method of the path module.

Get the server path

require.main.filename return the entry point of the current application, so we'll use the path.dirname to get the rool directory.

path.dirname(require.main.filename)

Join path

In this part we'll create a path to our static folder. In my case it's under the folder views in the same level as server.js.

let serverPath = path.join(path.dirname(require.main.filename), "views");

Get file path from url

To get the absolute path of the wanted file, we'll use the url.parse(req.url, true), see the routing module for more information.

let filePath = path.join(serverPath, url.parse(req.url, true).pathname);

Securing our files from directory traversal

To check if the user try to traverse our path using ".." we can do a comparison between our serverPath (the absolute path to our public folder) and the final file path sent by the user.

filePath.substring(0, serverPath.length) === serverPath

Checking accepted mime types

to get the extension of the file, Node.js path module, provides us with a function called extname, which return the extension of the file.

const mimes = {
    ".html": "text/html",
    ".css": "text/css"
}
let contentType = mimes[path.extname(baseURI.pathname)];
if (contentType) {
	// Accepted type
}

Serving static files implementation

const url = require("url")
const fs = require("fs")
const path = require("path")

const mimes = {
    ".html": "text/html",
    ".css": "text/css"
}

const router = (req, res) => {
    try {

        let baseURI = url.parse(req.url, true)

        //Static file
        let serverPath = path.join(path.dirname(require.main.filename), "views");
        let filePath = path.join(serverPath, baseURI.pathname);
        if (filePath.substring(0, serverPath.length) === serverPath) {
            if (fs.existsSync(filePath)) {
                let contentType = mimes[path.extname(baseURI.pathname)];
                if (contentType) {
                    res.writeHead(200, { 'Content-type': contentType });
                    res.end(fs.readFileSync(filePath), "utf-8");
                    return;
                }
            }
        }

        // Whatever you have to do to handle the not found files

    } catch (error) {
        // In case of error
        console.log(error)
        res.writeHead(200, { 'Content-type': 'text/html' });
        res.end("<h1>An error occured</h1>");
    }

}

Serving static files full implementation

Project structure

  • server.js
  • router/
    • router.js
    • controller.js
  • views/
    • index.html
    • style.css

Code

const url = require("url");
const controller = require("./controller")

const router = (req, res) => {
    try {
        // Parsing and saving our url data in the req
        let baseURI = url.parse(req.url, true)
        req.queryData = {
            query: baseURI.query,
            baseURI: baseURI.pathname
        }

        // Searching for routes
        let handler = (routes[req.method]) ? routes[req.method][baseURI.pathname] : null
        if (handler) {
            // Handler of this route found
            handler(req, res)
        } else {
            // No route found matching the url
            routes["notFound"](req, res)
        }
    } catch (error) {
        // In case of error
        console.log(error)
        res.writeHead(200, { 'Content-type': 'text/html' });
        res.end("<h1>An error occured</h1>");
    }

}

// routes in form of: route[method][url]
let routes = {
    "GET": {
        "/": controller.home,
        "/posts": controller.getPosts
    },
    "POST": {
        "/posts": controller.insertPost
    },
    "notFound": (req, res) => {
        res.writeHead(200, { 'Content-type': 'text/html' });
        res.end("<h1>Not found</h1>");
    }
}

module.exports = router;
exports.home = (req, res) => {
    res.writeHead(200, { 'Content-type': 'text/html' });
    res.end("<h1>Hello</h1>");
}

exports.getPosts = (req, res) => {
    let posts = [
        { id: 1, title: "article1" },
        { id: 2, title: "article2" }
    ]
    res.writeHead(200, { 'Content-type': 'application/json' });
    res.end(JSON.stringify(posts));
}

exports.insertPost = (req, res) => {
    let bodyString = ""
    req.on("data", data => {
        bodyString += data
    })

    req.on("end", () => {
        let body = JSON.parse(bodyString);
        let post = {
            id: 1,
            title: body.title
        }
        res.writeHead(200, { 'Content-type': 'application/json' });
        res.end(JSON.stringify(post));
    })
}
const http = require("http");
const router = require("./router/router")

const server = http.createServer(router)
    .listen(3000, () => console.log("Server listening on port 3000"));
<!DOCTYPE html>
<html>

<head>
    <title>Woring with Node.js</title>
    <link rel="stylesheet" type="text/css" href="style.css">
</head>

<body>
    <h1>Hello</h1>
</body>

</html>

h1 {
    color: red;
}

Result

serving static files in nodejs result

Source code

The full implementation of this article can be found in the GitHub project: Node.js learning