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!
-
Since the two files are at the same level, we directly require
postsListInfo.js
. See Paths. -
The variable
posts
assigned to this require statement should match thevalue
of thedata object
!
We could name the variablepostsListInfo
, but thedata object
would now beposts: 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.