Improving a Node.js app built with Express and EJS

This post is about improving a Node.js application built with Express and EJS.

Intro

In the last tutorial, we've built a basic Node.js web application using EJS and Express. So, to understand this one, you should have followed the previous.

In this post, we are going to improve this application to make it look nicer on the server.
As usual, I'll be explaining every step so you get the most out of this tutorial if you follow.

Dynamic content

Till now, we had a homepage with an image and a button that displays a message when you click on it.
We also had an about page with a link inside a paragraph and an image under it.
Both pages have different document title, different page title, and the same menu.

Nothing fancy in fact, but let's assume that our homepage will display a list of titles.
Those titles may come from each post in a blog.

Update index.js to create a list of titles of posts that will be rendered on the homepage.

// /index.js
...
app.get("/", function (req, res) {
    let posts = [
        { title: "Who invented JavaScript ?" },
        { title: "My first JavaScript piece" },
        { title: "Logging into the console !" },
    ]
    res.render("index", {
        docTitle: "Homepage | EJS App",
        pageTitle: "Homepage of templating with EJS application",
        posts: posts,
    })
})
...

We are creating an array of objects inside the main route "/" called posts, where each object has a key and in front of each one a value.

Remember that our main route renders index.ejs. So to access and display those titles, we should update index.ejs to integrate them like so :

<!-- /views/index.ejs -->
...
<!-- Looping through the posts array -->
<% posts.forEach((post) => { %>
<h2><%= post.title %></h2>
<% }) %>
<button id="btn" class="pure-button">A Button</button>
...

We are looping through the posts array using the forEach() method.
Note how we are opening and closing EJS tags !
It's very important to understand the logic of opening and closing the EJS and HTML tags when using them together.
The code isn't difficult at all, we are telling EJS to get the posts, and forEach one we want to display it's title inside an <h2> tag.
Save both files and take a look at the homepage.
Great job !

In addition to a title, a post should have at least a description to give the reader an idea about it's content, maybe an image to make it more appealing, and a date indicating how recent and relevant it is in today's world.
Of course it should also have a link to the post itself, but we are not going to do it since we're not going to create them on the frontend.
Let's update our files to look like so :

// /index.js
...
app.get("/", function (req, res) {
    let posts = [
        {
            title: "Who invented JavaScript ?",
            description: "A post about the father of JavaScript",
            date: "2022-06-03",
        },
        {
            title: "My first JavaScript piece",
            description: "A post about my first JavaScript block",
            date: "2022-06-10",
        },
        {
            title: "Logging into the console !",
            description: "A post about messages in the console",
            date: "2022-06-17",
        },
    ]
    res.render("index", {
        docTitle: "Homepage | EJS App",
        pageTitle: "Homepage of templating with EJS application",
        posts: posts,
    })
})
...
<!-- /views/index.ejs -->
...
<!-- Looping through the posts array -->
<% posts.forEach((post) => { %>
<!-- Adding the title inside an <h2> -->
<h2><%= post.title %></h2>
<!-- Adding the date inside a paragraph -->
<p>posted on <%= post.date %></p>
<!-- Adding the description inside an <h3> -->
<h3><%= post.description %></h3>
<% }) %>
<!-- Closing the loop -->
...

Save both files and take a look at the homepage. All of this is great, but imagine that we have a blog of 50 posts and each post have a title, a link, a description, an image, a date of publication, a date of update, and some tags 😱
Yes, index.js would be crowded and difficult to maintain with a huge block of information just for the posts.
We'll be addressing this in the next section.

Organizing the backend

Since we are working on a server-side application, our code on the backend will evolve and get longer.
We shouldn't put everything inside our main server file because it will become almost impossible to look for a specific portion to update it or find a bug and correct it...

I don't think that their is a developer who doesn't split his code.
We split our code into pieces where each one is easily trackable in a near or far future to keep it clean and maintainable.

Routing


For now we only have 2 routes, main "/" and "/about", but when our application will have some other routes where each one have it's logic and functionalities, our main server file will be huge, a little bit ugly and unmaintainable.

Do you remember the Simple routing section ?
Well now we'll do a better one by putting each route in it's own file.

To do so, we'll use the express.Router class.
Create a new folder called routes at the root of the app.
Inside this folder, create a file called home.js with the following content :

// /routes/home.js

const express = require("express")
const router = express.Router()

// Defining a route
router.get("/", (req, res) => {
    let posts = [
        {
            title: "Who invented JavaScript ?",
            description: "A post about the father of JavaScript",
            date: "2022-06-03",
        },
        {
            title: "My first JavaScript piece",
            description: "A post about my first JavaScript block",
            date: "2022-06-10",
        },
        {
            title: "Logging into the console !",
            description: "A post about messages in the console",
            date: "2022-06-17",
        },
    ]
    res.render("index", {
        docTitle: "Homepage | EJS App",
        pageTitle: "Homepage of templating with EJS application",
        posts: posts,
    })
})

module.exports = router

Instead of app.get we use router.get and export it as a module.
Now, in index.js, replace the main route by this :

// /index.js
...
/**
 * Rendering index page on the main route,
 * using the express.Router class.
 */
app.use("/", require("./routes/home"))
...

Here we are telling Express to look for the file home.js that lives under the routes folder and to execute it's content on the main route "/".

So far, so good, but the posts array will grow and now this home.js file will not be maintainable, so let's get it out of there inside a separate file where only the posts info lives.
Create a file called postsListInfo.js inside the routes folder :

// /routes/postsListInfo.js

module.exports = [
    {
        title: "Who invented JavaScript ?",
        description: "A post about the father of JavaScript",
        date: "2022-06-03",
    },
    {
        title: "My first JavaScript piece",
        description: "A post about my first JavaScript block",
        date: "2022-06-10",
    },
    {
        title: "Logging into the console !",
        description: "A post about messages in the console",
        date: "2022-06-17",
    },
]

See, we directly exported the array itself.
Now, all we have to do is update home.js like so :

// /routes/home.js

const express = require("express")
const router = express.Router()

let posts = require("./postsListInfo")

// Defining a route
router.get("/", (req, res) => {
    res.render("index", {
        docTitle: "Homepage | EJS App",
        pageTitle: "Homepage of templating with EJS application",
        posts: posts,
    })
})

module.exports = router

Pay attention !

  1. Since the two files are at the same level, we directly require postsListInfo.js. See Paths.
  2. The variable posts assigned to this require statement should match the value of the data object !
    We could name the variable postsListInfo, but the data object would now be posts: postsListInfo. See Data object.

Save both files and take a look at the homepage.
Great job ! We now have the same display on the frontend, but with a clean, clear and maintainable code on the backend !

Nota bene : we could create at the root of the app a folder just for the postsListInfo.js file and require it from there.

Lets's now put the "/about" route in it's own file.
Under routes, create a file called about.js with the following content :

// /routes/about.js

const express = require("express")
const router = express.Router()

// Defining a route
router.get("/", (req, res) => {
    res.render("about", { docTitle: "About me | EJS App", pageTitle: "A little bit about myself" })
})

module.exports = router

We are defining a route on which we'll render about.ejs.
Now replace "/about" route in index.js by :

// /index.js
...
/**
 * Rendering about page on the /about route,
 * using the express.Router class.
 */
app.use("/about", require("./routes/about"))
...

Here we are telling Express to look for the file about.js that lives under the routes folder and to execute it's content on the "/about" route.

Now, if you ever want to make changes on a specific route, you know where to make them, it will always be under one specific file 😉

Don't Repeat Yourself


Do you remember the DRY section ?
Well, in the backend also, you should avoid to repeat yourself !

If you take a closer look to postsListInfo.js, you'll notice that every description begins with A post about, assuming that you'll follow this pattern for each and every post's description, or maybe a much longer one such as The content of the following post is about, we could do the following :

// /routes/postsListInfo.js

const descIntro = "The content of the following post is about"

module.exports = [
    {
        title: "Who invented JavaScript ?",
        description: `${descIntro} the father of JavaScript`,
        date: "2022-06-03",
    },
    {
        title: "My first JavaScript piece",
        description: `${descIntro} my first JavaScript block`,
        date: "2022-06-10",
    },
    {
        title: "Logging into the console !",
        description: `${descIntro} messages in the console`,
        date: "2022-06-17",
    },
]

WHAT IS THIS ?!
This is a Template string.
You can pass a variable into a string by writing the whole string inside backtick and call your variable inside a ${} at the desired place.
Template literals are much more powerful than that, they can be used inside functions to create reusable blocks, yes just like template engines...

On the other hand, if you look at home.js and about.js, they both begin with the same block of code :

const express = require("express")
const router = express.Router()

To resolve this one, we'll use a global.router configuration.
Update the content of those files as well as index.js :

// /routes/home.js

const router = global.router

let posts = require("./postsListInfo")

// Rendering index.ejs on the main route.
router.get("/", (req, res) => {
    res.render("index", {
        docTitle: "Homepage | EJS App",
        pageTitle: "Homepage of templating with EJS application",
        posts: posts,
    })
})

module.exports = router
// /routes/about.js

const router = global.router

// Rendering about.ejs on /about route.
router.get("/about", (req, res) => {
    res.render("about", { docTitle: "About me | EJS App", pageTitle: "A little bit about myself" })
})

module.exports = router
// /index.js complete file
const express = require("express")
const app = express()

// Set EJS as template engine
app.set("view engine", "ejs")

// Serve static files from a folder named public
app.use(express.static("public"))

// Require livereload and connectLiveReload
const livereload = require("livereload")
const connectLiveReload = require("connect-livereload")

// Create a server with livereload and fire it up
const liveReloadServer = livereload.createServer()

// Refresh the browser after each saved change on the server with a delay of 100 ms
liveReloadServer.server.once("connection", () => {
    setTimeout(() => {
        liveReloadServer.refresh("/")
    }, 100)
})

// Add livereload script to the response
app.use(connectLiveReload())

/** Start Routing */
// Parse an express router to a global router variable
const router = (global.router = express.Router())
// Use the global router variable
app.use(router)
// Execute home.js content on the entry route of the app.
app.use("/", require("./routes/home"))
// Execute about.js content on the entry route of the app.
app.use("/", require("./routes/about"))
/** End Routing */

// Launching the app on port 3000
app.listen(3000, () => {
    console.log(`App 🚀 @ http://localhost:3000`)
})

Now you know the basics to organize your backend and make it maintainable.

To test your new skills, I suggest that you replicate the Blog of Pure.css Layouts as a Node.js app.

I hope that this was helpful.

SYA,
LebCit.