Node - Js Web Development For Beginners A Step-By-Step Guide To Build An MVC Web Application With Node - JS, Express, and MongoDB... (Sebhastian, Nathan) (Z-Library)
Node - Js Web Development For Beginners A Step-By-Step Guide To Build An MVC Web Application With Node - JS, Express, and MongoDB... (Sebhastian, Nathan) (Z-Library)
Preface
Working Through This Book
Requirements
Source Code
Contact
Chapter 1: Introduction to Node.js
The Exercise Application
Computer Setup
Summary
Chapter 2: Your First Node.js Project
Creating the Server Application
Running the Server
Routing in Node.js
Adding Nodemon for Development
Summary
Chapter 3: Introduction to Express and Morgan
Using Express
Adding Morgan for Logging
Summary
Chapter 4: Using EJS Templating Engine for Views
Adding EJS to Express
Rendering EJS Template With Express
Reuse EJS Template With Partials
Summary
Chapter 5: Using Tailwind and DaisyUI for CSS
Adding Tailwind CSS to Node.js Project
Using DaisyUI
Summary
Chapter 6: Creating a MongoDB Database Cluster
MongoDB Introduction
Setting Up MongoDB Atlas Cloud
Welcome to Atlas
Create a Cluster
Security Quickstart: Set Authentication and Connection
Add a New Database User
Allow Your IP Address
Summary
Chapter 7: Integrating Mongoose to Express
Mongoose Introduction
Connecting to MongoDB Cluster
Installing Dotenv to Read Environment Variables
Summary
Chapter 8: Implementing the MVC Pattern
What’s an MVC Pattern?
Creating the User Model
Creating User Controller
Creating the User View
Creating the User Route
Running the Routes
Summary
Chapter 9: Developing User Authentication
Implementing Express Session
Creating the AUTH_SECRET Variable
Cleaning The Routes
Creating the Sign Up Page
Adding Font Awesome
Updating User Controller
Adding Express urlencoded Middleware
Adding the Dashboard Page
Summary
Chapter 10: Validating Form Inputs and Displaying Messages
Adding Validation to The Sign Up Process
Storing Error Messages in Flash
Displaying Error Messages on Sign Up Form
Preserving Input Data on Sign Up Form
Showing Toast Notification With Toastify
Summary
Chapter 11: Adding Login and Logout Functionalities
Creating the Landing Page
Updating the Dashboard Page
Adding Login Page to Views
Adding Login Function to User Controller
Adding Logout Function
Updating User Routes
Summary
Chapter 12: Protecting Routes With Middlewares
Express Middleware Explained
Adding Verification Middleware
Creating Middleware to Protect Login and Sign Up Routes
Summary
Chapter 13: Create, Read, Update, and Delete Customers
Creating the Customer Model
Creating the Customer Controller
Creating the Customer Views
Creating the Customer Routes
Creating New Customers
Add Create Routes
Updating Existing Customers
Deleting Customers
Summary
Chapter 14: Handling Invoices Data
Creating the Invoice Model
Creating the Invoice Controller
Creating the Invoice Views
Creating the Invoice Route
Create New Invoices
Update Invoices
Delete Invoices
Delete All Invoices When a Customer is Deleted
Summary
Chapter 15: Using Chart.js On the Dashboard
Formatting the Currency
Creating the Dashboard Controller
Updating the Dashboard View
Showing the Revenue Chart
Showing Five Latest Invoices
Summary
Chapter 16: Adding the Search Feature
Adding Customer Search
Adding Invoice Search
Summary
Chapter 17: Deploying Node.js Application
Preparing the Application for Deployment
Pushing Code to GitHub
Deploying Node Application to Railway
Summary
Wrapping Up
The Next Step
About the author
Node.js Web Development For Beginners
A Step-By-Step Guide to Build an MVC Web Application With Node.js, Express, and
MongoDB
By Nathan Sebhastian
PREFACE
The goal of this book is to provide gentle step-by-step instructions that will help
you see how to develop web applications using Node.js, Express, and MongoDB.
I’ll teach you why Node.js is a great choice to build a web application. We’ll cover
essential web development topics like routing, authentication, input validations,
MVC design pattern, and MongoDB query to see how they are implemented in
Node.js.
After finishing this book, you will know how to build and deploy a web
application using Node.js.
I encourage you to write the code you see in this book and run them so that you
have a sense of what web development with Node.js looks like. You learn best
when you code along with examples in this book.
A tip to make the most of this book: Take at least a 10-minute break after
finishing a chapter, so that you can regain your energy and focus.
Also, don’t despair if some concept is hard to understand. Learning anything new
is hard for the first time, especially something technical like programming. The
most important thing is to keep going.
Requirements
To experience the full benefit of this book, basic knowledge of JavaScript is
required.
If you need some help in learning JavaScript, you can get my book at
https://codewithnathan.com/beginning-modern-javascript
Source Code
In the Summary section of each chapter, you will see a link to download the code
added in that chapter from GitHub.
You can download the source code as a ZIP archive as shown below:
This way, you can continue to the next chapter without getting left behind.
Contact
If you need help, you can contact me at [email protected].
You might also want to subscribe to my 7-day free email course called Mindset of
the Successful Software Developer at https://g.codewithnathan.com/mindset
The email course would help you find the right path forward in your career as a
software developer.
CHAPTER 1: INTRODUCTION TO
NODE.JS
If you’re familiar with JavaScript, you might know that JavaScript was first
created to make the web browser programmable. JavaScript exists and runs only
inside web browsers.
A complete web application is made up of two parts: the frontend or client side,
and the backend or server side. The client-server architecture below shows how a
web application is developed:
The client and server communicate using HTPP requests. When needed, a server
might interact with the database to fulfill the request sent by the client.
But all this changed with the invention of Node.js, which is a program that can
run JavaScript outside of the browser.
By utilizing the power of Node.js, JavaScript can now be used to develop both the
client and server side of a web application:
What’s more, the architecture of Node.js is very flexible, which allows you to code
a prototype application quickly.
In short, learning Node.js enables you to build a complete web application using
just one language, which is JavaScript.
There’s also a graph showing revenue in the last 6 months, as well as the latest
invoices:
We won’t include features to send the invoice by email or print it. We only focus
on building the web application for storing data and showing insights.
With this practice, you will get the experience of developing a web application
from scratch using Node.js, as well as understanding how to complete a project
and organize your code well.
Computer Setup
To start developing with the Node.js, you need to have three things on your
computer:
1. A web browser
2. A code editor
3. The Node.js program
https://www.google.com/chrome/
The browser is available for all major operating systems. Once the download is
complete, follow the installation steps presented by the installer to have the
browser on your computer.
Next, we need to install a code editor. There are several free code editors
available on the Internet, such as Sublime Text, Visual Studio Code, and
Notepad++.
Out of these editors, my favorite is Visual Studio Code because it’s fast and easy to
use.
https://code.visualstudio.com/
When you open the link above, there should be a button showing the version
compatible with your operating system as shown below:
Figure 1. Install VSCode
Now that you have a code editor installed, the next step is to install Node.js
Installing Node.js
Node.js is a JavaScript runtime application that enables you to run JavaScript
outside of the browser. We need this program to generate and run the web
application that we’re going to develop.
You can download and install Node.js from https://nodejs.org. Pick the
recommended LTS version because it has long-term support. The installation
process is pretty straightforward.
To check if Node has been properly installed, type the command below on your
command line (Command Prompt on Windows or Terminal on Mac):
node -v
The command line should respond with the version of the Node.js you have on
your computer.
You now have all the programs needed to start developing a Node.js web
application. We’re going to start building the application in the next chapter.
Summary
In this chapter, we’ve learned what is Node.js and why it’s a great choice, and
then we installed the required tools to develop a Node.js application.
First, create a folder on your computer that will be used to store all files and code
related to this project. You can name the folder 'finly'.
Inside the folder, open your terminal and run the npm command to create a new
JavaScript project:
npm init
npm stands for Node Package Manager. It’s a program used for creating and
installing JavaScript libraries and frameworks. It’s included when you install
Node.js before.
The npm init command is used to initialize a new project and create the
package.json file. It will ask several questions about your project, such as the
project name, version, and license.
For now, just press Enter on all questions until you see the following output:
{
"name": "finly",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Press Enter, and you should see a package.json file generated in the 'finly' folder
containing the same information as shown above.
server.listen(3000, () => {
console.log('Server running on port 3000');
});
In Node.js, the require() function is used to get a module so that you can use it.
The module name is passed as an argument to the function.
The function returns the module, which you need to assign to a variable. To
prevent any confusion, the imported module is usually assigned to a variable
with the same name as the module.
The http module can then be used to create a Node.js web server by calling the
createServer() method.
Node.js will pass two objects to the callback function: request and response
objects, or req and res for short.
The req object contains information about the network request sent by the client,
while the res object is used to send a response back to the client.
In the function above, we simply log the request url value, then respond with a
message 'Hello From Node.js'.
The res.end() method sends the string argument as a response and ends the
request.
After that, the server.listen() method is called to open a specific web port for
connections:
server.listen(3000, () => {
console.log('Server running on port 3000');
});
The callback function will be executed when the server is running, so we call the
console.log() method to let us know that the server is running.
node index.js
You should see 'Server listening at port 3000' logged to the terminal.
Now you can open the browser and visit the website at http://localhost:3000 to get
the following response:
You now have a working server, and you can send a request to that server from
the client (browser). Nice work!
Routing in Node.js
Now that the web server is created, let’s add some routes to the server so that it
can have different responses.
A route is a specific URL address that points to a specific response. It’s like a map
that the web server uses to know what to do with incoming requests.
In the previous section, we logged the req.url value to the terminal for each
incoming request.
/
/about
/contact
This means we already have the means to detect the URL. We only need to make
use of this data to change the response.
To add routes to the server, you can use an if statement to check for the req.url
value, and send the desired response like this:
First, we unpack the url value from the req object, so we don’t have to write
req.url every time we want to access the URL value.
After logging the url value. We create an if-else statement to define different
responses for the request.
When the url route is not defined, we send back 404 error code.
The writeHead() method allows you to write specific data that you want to send
back to the client. The default response code 200 means the request is processed
correctly, while 404 indicates a 'Not Found' error.
The code you added to the index.js file won’t take effect immediately.
You need to stop the server by pressing Control + C on the terminal, then run
node index.js again for the changes to work.
Now if you visit different routes on the localhost, you’ll see different responses
because of the routing that you’ve implemented.
To make the development more pleasing, let’s use Nodemon to run the server
instead.
The --save-dev option is used to specify packages that are only needed for
development purposes.
Packages marked as dev dependencies won’t be installed when you deploy the
application to a production environment later.
If you open the package.json file, you should see nodemon listed as devDependencies
like this:
"devDependencies": {
"nodemon": "^3.1.0"
}
You will also have package-lock.json file and node_modules/ folder generated on
the project.
The package-lock.json file is used to record the exact version of the packages you
installed. It’s only useful when you need to upgrade or downgrade installed
packages.
The node_modules/ folder is where the packages you install using npm will be
stored.
After installing Nodemon, add a start and dev script on the package.json file as
shown below:
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
The scripts in package.json file respond to the npm run <script-name> command
that you can run from the terminal.
To run the dev script, run the npm run dev command.
Nodemon will execute the index.js file and show the following output:
[nodemon] 3.1.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node index.js`
Server listening at port 3000
To see Nodemon in action, open the index.js file and change the message
returned by the / route as follows:
You will see Nodemon automatically restart the server in the terminal as follows:
Now change the message back to 'Hello From Node.js', and you’ll see the restart
message again on the terminal.
If you don’t use Nodemon, then you’ll have to restart the server manually when
you make changes to the project. Thank you Nodemon!
Summary
The code added in this chapter can be found at
https://g.codewithnathan.com/node-2
You’ve managed to create your first Node.js server and implemented routes that
respond to specific URL requests.
You’ve also installed Nodemon so that the server automatically restarts whenever
you add any changes to the project.
In the next chapter, we’re going to learn about Express, the web application
framework for Node.js.
CHAPTER 3: INTRODUCTION TO
EXPRESS AND MORGAN
Using the http module, you’ve created a server that can receive HTTP requests.
But as you can see from the routing implementation, building a web application
using Node.js is verbose and complicated.
To simplify the process of building a web application, you can use a framework
called Express.
Express is a Node.js web framework that’s popular because of its simplicity and
minimalist nature. The framework is built on top of Node.js modules like http.
By using Express, you gain access to functions and modules that speed up the
development process. Let me show you an example.
Using Express
To start using Express, you need to install the package using npm as follows:
Once the package is installed, open the index.js file and replace the http server
you’ve created before with this:
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Here, we import the express module and call the express() function to create an
Express application.
From this application, you can write the same routes created using http module
before:
The app.get() method is used to respond to GET HTTP requests. If you want to
specify a route for the POST method, you can use app.post() instead (more on this
later as we build the application)
After that, you can call the app.listen() method to enable the server to listen for
requests. We add the port number as a constant just to make it reusable.
To add a Not Found 404 page, you can add another app.get() method above the
PORT variable as follows:
The asterisk * symbol is known as the wild card route, and it will match any URL
route.
The ordering of the routes matters in Express. If you place the asterisk route on
top of other routes like this:
Then you’ll get the 404 response even when you visit a valid. Make sure that you
define a wild card route as the last route on your application.
Because Express separates the request handlers into their own get() methods,
logging the URL means you need to write a console.log on each handler like this:
Morgan is a library that you can use to report detailed logs for your Node.js
application.
Then in your index.js file, import the module and call the app.use() method as
follows:
app.use(morgan('dev'));
Here, we call the morgan() function and pass the format we want to use for the
logs, which is 'dev'.
There are other formats such as 'tiny' and 'common' that you can use, but 'dev' is
the best for development use.
I will explain what a middleware does later. For now, let’s run the server and try
to visit some of the available routes.
Here, you can see that Morgan logs the request method, the URL route, the status
returned by the server, and the amount of time required to send a response back
in milliseconds.
By using Morgan, the logs will be more useful as you develop the application.
Summary
The code added in this chapter can be found at
https://g.codewithnathan.com/node-3
In this chapter, you’ve learned how Express makes the web development process
more convenient.
You can separate the route handlers by using Express get() methods, so you don’t
need to create nested if statements.
You’ve also learned about the Morgan library, which is used for creating detailed
logs.
CHAPTER 4: USING EJS TEMPLATING
ENGINE FOR VIEWS
The framework also includes templating engines that you can use to create and
send responses to the client.
Instead of sending back static HTML pages, templating engines allow you to
dynamically render your HTML pages.
Let me show you how templating engines work in the next section
These templating engines have different syntaxes, but they all serve the same
purpose: to create HTML output from the provided data.
We’re going to use EJS for our application because it uses plain JavaScript, so the
syntax will be familiar and easy to understand.
Next, open the index.js file, and set the view engine for Express as follows:
app.set('views', './views');
app.set('view engine', 'ejs');
The app.set() method is used to add specific configurations to Express. Here, we
set the 'views' option to the ./views folder.
Next, create the views/ folder in our project, create a new file named index.ejs,
and add the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1><%= message %></h1>
<p>Response created using EJS</p>
</body>
</html>
Notice that EJS file contains plain HTML code except for the <%= message %> tag.
Ejs enables you to embed JavaScript by using the <% %> tag. Inside this tag, you
can write JavaScript code such as declaring a variable or adding an if statement.
In the code above, we simply output the content of the message variable, which
we’re going to add next.
Go back to your index.js file and change the response inside the route handlers
as follows:
The first argument is the EJS template to render, which is the index file. You can
omit the .ejs extension as shown above.
The second argument is an object containing options you want to send to the
template. The object properties are available in EJS as variables.
In the code above, we specify the message property so that EJS can render the
output.
Now run the server and visit the website routes. You’ll see the HTML response as
follows:
By using EJS, you can use one template file to produce HTML output with
different content. It allows you to reuse the same layout for multiple pages.
What’s more, you can also avoid repetitive code by creating partial templates. Let
me show you how.
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
Now you can reuse this template in any other EJS template by using the include()
function.
Back on the index.ejs file, replace the <head> tag with the following code:
<!DOCTYPE html>
<html lang="en">
<%- include('./partials/head') %>
<!-- body tag... -->
</html>
Now whenever you create another EJS template, you can use the include()
function to reuse a certain part of the template.
Summary
The code added in this chapter can be found at
https://g.codewithnathan.com/node-4
Aside from helping you create a cleaner and more manageable codebase, using
Express also enables you to use JavaScript templating engines.
You can define the layout using HTML, while changing the data shown by Express
using JavaScript variables, statements, and methods.
Now that we can create the interface of our application using EJS, let’s add CSS
libraries so we can speed up the development process.
For styling the interface, we’re going to use Tailwind CSS and Daisy UI.
If you aren’t familiar with Tailwind CSS, it’s basically a collection of CSS classes
where each class applies minimal styling to the element.
CSS frameworks like Bootstrap and Bulma provide you with high-level
components that you can immediately use in your project.
When you need to style a button, you just need to apply the classes that contain
the desired CSS properties:
When using Bootstrap, the btn class provides a combination of CSS properties
such as padding, color, opacity, font weight, and so on.
On the other hand, Tailwind gives you utility classes where each class has only
one or two properties:
In the example above, the px-5 is short for padding padding-left and padding-
right, while 5 is a specific size for the paddings. The text-white applies color:
white, the bg-blue-500 applies the background-color property, and border applies
border-width.
By using Tailwind, you get the flexibility of applying styles to your element by
adding the desired class.
But since Tailwind doesn’t come with pre-made components, we need to specify a
bunch of CSS classes just to create common components like a button, a
dropdown, and a navigation bar.
To avoid building components from scratch, we’re also going to add DaisyUI to
the application. Let’s do it!
Using DaisyUI
DaisyUI is a CSS component library that uses Tailwind CSS under the hood. The
library provides CSS classes that you can use to create common components like a
button, a form input, a dropdown, and so on.
To add DaisyUI to the application, you need to install the package using npm like
this:
Once the installation is finished, you can add DaisyUI on top of the Tailwind
module you added earlier.
Open the tailwind.config.js file and add require('daisyui') to the plugins config
as follows:
With that, you can use DaisyUI class names in your application.
To test this, let’s create a button in the index.ejs file like this:
<body>
<h1 class="text-emerald-500"><%= message %></h1>
<p class="text-emerald-800">Response created using EJS</p>
<button class="btn btn-primary m-2">Click me!</button>
</body>
Go back to the browser, and refresh the page to see a button rendered as follows:
Notice that when creating the button, we also add the m-2 class to add margins
around the button.
This is a base class from the Tailwind library, and it can be used together with
classes from DaisyUI anytime you need it.
This is why I love using Tailwind. I can use pre-made components to build the
interface for my application, and whenever I want to customize the component, I
just add Tailwind classes to the element.
You can also pair Tailwind with Bootstrap, but the integration is more complex
than pairing Tailwind with DaisyUI.
Summary
If you need to check the code added in this chapter, you can visit the GitHub repo
at https://g.codewithnathan.com/node-5
In this chapter, we’ve integrated Tailwind and DaisyUI so that we can build the
view layer faster.
CHAPTER 6: CREATING A MONGODB
DATABASE CLUSTER
In this chapter, you’re going to learn about the MongoDB database software and
how to create one for free.
Don’t worry if you never used MongoDB before. I’ll introduce how the database
works in a nutshell and guide you through creating the database.
MongoDB Introduction
If you already know about MongoDB, you can fast forward to 'Setting Up
MongoDB Atlas Cloud' section below.
MongoDB is one of many databases that you can use to store web application
data. It’s a document-oriented database that provides flexible and scalable
solutions for storing and retrieving data.
Some of the most popular SQL databases are MySQL, SQLite, and PostgreSQL. You
might have heard or used them before.
In an SQL database, data is stored under tables and rows. Suppose we want to use
the database to keep track of users and events that are created in our application.
In each table, there are rows of data, and the Id column in the Users Table is
related to the userId column in the Events Table.
Through the relation created in these tables, we can see that the first two events
were created by the user 'Emily', while the third one was created by 'Kenneth'.
When you update the Id value in the Users Table, the userId value in the Events
Table will be updated automatically.
A collection can have many documents, and each document can have as many
entries of data. A document can store a reference to another document:
Since a NoSQL database is non-relational, the userId entry in the Events
Collection won’t be updated automatically when you update the Id entry in the
Users Collection.
There are many articles available about the comparisons between NoSQL and
SQL databases, but this will be enough for now.
Let’s continue with setting up a MongoDB instance for our application to use next.
When using MongoDB Atlas, you don’t need to manually install and run
MongoDB on your computer for development and testing. The cloud service will
generate a server instance for MongoDB along with credentials to access the
database.
MongoDB Atlas also has a free tier which is perfect for our Express server project,
so head over to https://www.mongodb.com/atlas/database and click on the 'Try
Free' button shown on the page:
You’ll be taken to the registration page, where you can create an account for free:
Figure 2. MongoDB Atlas Sign Up Page
Create a Cluster
Upon completing the form, you will be directed to the Deploy your database page.
If you arrive at the Overview page instead, you can click the big green + Create
button to go to the Deploy your database page:
Figure 5. MongoDB Atlas Overview Page
On the Deploy your database page, Select the M0 FREE plan, which is ideal for
testing and development:
Figure 6. MongoDB Atlas Plans
The Provider option is the cloud service provider that will host your database.
You’re free to select whichever you like.
AWS, Google Cloud, and Azure all offer the same free tier. For the region, select
the region that’s recommended or is closest to you.
You can accept the default cluster name provided, usually 'Cluster0'. Leave the tag
empty as it’s optional. After selecting your provider and nearest region, you can
click the green "Create" button:
Figure 7. MongoDB Atlas Create Database
Now you need to wait a bit for the cluster to be created by MongoDB. If this is the
first time you use MongoDB Atlas, you’ll be taken directly to the Security
Quickstart page after the cluster is created.
You can select Username and Password, then use the Username and Password
that have been generated for you. Click Create User as shown below:
Next, you need to enable access to the database from specific IP addresses.
Because this is a development database, you can enter '0.0.0.0' as the IP address
value to enable access from anywhere:
Now you can click Finish and Close. You should be redirected to the Overview
page, where your active cluster is shown:
The cluster is ready to accept incoming connections. Great work!
On the Network Access page, click the Add IP Address green button and a modal
will appear:
Click Confirm when you’re done, and that’s how you add a new user or IP address
in MongoDB Atlas.
With your database cluster ready, it’s time to try connecting your Express
application to the database.
Summary
Note that there’s no code repository for this chapter because we didn’t add any.
In this chapter, we’ve learned what is MongoDB database, the difference between
MongoDB and SQL databases, and how to set up a MongoDB Atlas cluster for free.
A MongoDB cluster can have many databases, although you usually only need
one database for a single application.
In the next chapter, we’re going to see how to connect the MongoDB cluster to our
Node application.
CHAPTER 7: INTEGRATING
MONGOOSE TO EXPRESS
Now that we’ve created a MongoDB cluster, it’s time to enable Express to connect
to the database.
Mongoose Introduction
Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js.
This library can be used to define relationships between data, provide schema
validation, and send queries to your MongoDB database.
As a database, MongoDB doesn’t provide a schema where you can describe the
data structure of your documents. This means the data you put in a document is
unrestricted.
You can put different types of data in the same field like this:
{
"Id": 1,
"name": "Emily",
},
{
"Id": "2",
"name": "John",
"age": 28,
}
If you look closely, the Id field in the first document is a number, but it’s a string
in the second document.
What’s more, the second document contains an age field, which is not present in
the first document. This database is messy because it has no structure.
By using Mongoose, you can create a schema that defines the shape of your data.
The schema can be written in JavaScript like this:
The schema is an object that defines the characteristics of your data document,
such as:
While you can connect to MongoDB without Mongoose, using the library is
recommended because you can validate your data before inserting it into the
database.
The library is also free, so you only need to install it using npm.
As always, I will hold your hand through installing and using Mongoose.
First, you need to install mongoose and mongodb in your Node.js application using
npm:
Next, create a file named dbConnect.js in your lib/ folder and write the following
code in it:
mongoose.connect(MONGODB_URI, {
dbName: 'finly-db',
bufferCommands: false,
});
console.log('Connected to MongoDB');
When we call the mongoose.connect() method, Mongoose will try to connect to the
MongoDB cluster URI that we specify as MONGODB_URI.
The dbName argument provides the name of the database you want to connect to. If
it doesn’t exist, Mongoose will create one on the cluster.
The bufferCommands option is set to false to disable buffering. This makes sure that
a connection exists before you send further instructions to manipulate the
database.
When you’re connected to the cluster, you’ll see a 'Connected to MongoDB' log on
the terminal.
To get this URI, you need to go back to MongoDB Atlas Overview page, and click
the Connect button on your cluster card:
Figure 10. Click the Connect button in MongoDB Cluster
You will see a tab where you can choose the connection method. Choose Driver as
shown below:
You’ll be shown the connection string URI on the next page as shown below:
Copy the connection string, then create a .env file in your application root folder
with the following content:
Note that you also need to replace the <password> string in the URI with your
user’s password.
If you forgot the password, you need to create a new password by selecting
Database Access and editing the password of the user you created earlier.
To enable the environment variables, you need to install a node package called
dotenv first:
require('dotenv').config();
require('./libs/dbConnect');
You can verify this by looking at the terminal for the 'Connected to MongoDB' log:
And that’s how you connect a Node.js application to a MongoDB cluster using
Mongoose.
Summary
The code added in this chapter can be found at
https://g.codewithnathan.com/node-7
In this chapter, you’ve seen how to connect the Node application with MongoDB
Atlas cluster using Mongoose.
In the next chapter, we’re going to start creating a Mongoose model that will be
used in our application.
CHAPTER 8: IMPLEMENTING THE MVC
PATTERN
With the MongoDB database connection ready, we can start creating models and
use them in our application.
But before we continue with the development process, let’s take a minute to
understand the MVC design pattern, which we’re going to use in the following
chapters to build our application.
The Model is the definition of the data relevant to the application. This element
describes the data structure and the format used to store the data.
The View is the visual representation of the application, it’s also the interface
from which the user can interact with the data in a pleasing and easy manner.
The Controller is the main driver of the application. It acts as the bridge between
the model and the view.
Here’s the MVC pattern that we’re going to implement in our Node.js application:
When the user visits our web application, a view will be rendered on the
browser.
The user can interact with our application through the view. When the user
performs an action, the controller will process that action and fulfill it.
If needed, the controller will access the model to manipulate the database. Once
the model returns a response, the controller will process that response and
render the right view.
We already created the view layer in our views/ folder, so I will show you how to
create the model and controller next.
The first model we’re going to create is the User model, which is used for the user
authentication process.
We’re also going to see how to create a controller that uses the model to interact
with the database.
The content of the user.model.js file is as follows. I will explain the code below:
module.exports = User;
Here, you create a new schema called UserSchema that describes the data structure
of the User document.
After that, a model is created by calling the model() function from mongoose
library. The first argument serves as the model name, and the second argument is
the schema for this model.
A model represents a MongoDB collection, and it’s this model that you can use to
query documents from the collection.
Before creating the User model, we also check if the model already exists under
the models global. If the model already exists, we reuse that model to avoid
creating the model again.
The model is then set as the default export of the file, so any time we need the
model, we just need to import it.
In the root folder of your project, create a new folder named controllers/, then
create a file named user.controller.js in it.
module.exports = {
getUser,
createUser,
deleteUser,
};
Each function will call the right methods from the User model that we have
created before.
The functions are then exported so that they can be called from the correct route
later.
Inside the views/ folder, create a new file named user.ejs and add the code below
to it:
<!DOCTYPE html>
<html lang="en">
<%- include('./partials/head') %>
<body>
<div class="m-4">
<h1 class="text-emerald-500"><%= message %></h1>
<% if (user) { %>
<p><%= user.email %></p>
<p><%= user.password %></p>
<% } %>
</div>
</body>
</html>
Here, we copy the view template from index.ejs and add a conditional rendering
with an if statement.
If the user data exists, we show the field values, such as the email and password.
Now we shouldn’t show the user password anywhere as it’s sensitive, but this is
just for showing how to implement the MVC pattern.
Back to the root folder of your project, create a new file named routes/, then
create a file named user.route.js that has the following content:
const {
getUser,
createUser,
deleteUser,
} = require('../controllers/user.controller');
router.get('/', getUser);
router.get('/create', createUser);
router.get('/delete', deleteUser);
module.exports = router;
Here, we use the Express router to create three routes, the landing route / will
call the getUser() function, the /create route will call the createUser() function,
and the /delete route will call the deleteUser() function respectively.
The route is then exported so you can add it to the index.js file.
Open the index.js file, import the user.route.js file and use it as follows:
// ...
app.use('/users', userRouter);
Notice that here we use the app.use() method instead of app.get() because we
want to let the userRouter object handle the requests coming to the /users route.
Now go to localhost:3000/users/ URL and you’ll see the user details listed as
follows:
You can also visit MongoDB Atlas and click on Browse collections on the Overview
page:
You should see a database named 'finly-db' that contains a 'users' collection, and
in that collection, you’ll have a document containing your user data:
Notice that MongoDB automatically creates the database and collection when you
create the first document.
Next, if you visit the localhost:3000/users/delete URL from the browser, the user
data will be deleted.
This means you’ve successfully implemented the MVC pattern to develop the
user-related actions. Nice work!
Summary
If you need to check the code added in this chapter, you can view the code at
https://g.codewithnathan.com/node-8
In this chapter, you’ve learned how the MVC design pattern can be used in web
application development.
Using the MVC pattern enables you to organize your code based on its role in the
application. For everything related to the data, we place the code in the model.
For everything related to the interface, we place the code in the view, the rest is
stored in the controller. This way, the code is more manageable and scalable.
In the next chapter, we’re going to learn how to implement signup and login
functionalities to our Express application using the same MVC pattern. I’ll see you
there.
CHAPTER 9: DEVELOPING USER
AUTHENTICATION
With the MVC pattern implemented, the next task is to develop the signup and
login process to authenticate the user.
The cookie needs to be included in all subsequent requests sent by the browser.
This way, Express knows that the user is allowed to access the resource it wants
to access.
When the user logs out, the session and cookie are removed from both the server
and the client.
Next, you need to import and use the module in the index.js file as follows:
// ...
app.use(morgan('dev'));
app.use(express.static('./public'));
app.use(
session({
secret: process.env.AUTH_SECRET,
saveUninitialized: true,
resave: false,
})
);
We call the session() function inside the app.use() method, passing three options:
▪ The secret option is the key used to sign the session. It will be used again to
verify the cookie later
▪ The saveUninitialized and resave options are used to optimize the session
storage.
Now we can use the session in our application, but let’s add the AUTH_SECRET
variable in our .env file first.
This will generate a string that you can use as the value of AUTH_SECRET and put it
in the .env file.
If your terminal doesn’t have openssl installed, you can use the one I provide
below:
AUTH_SECRET=fsfqdB6WAq8TeiT7rRG14afTsZsAYuvJ5/nzWwhmwPw=
That will take care of the session signing key. The next step is to create the signup
process.
app.use('/', userRouter);
Now open the user.route.js file, remove all routes we’ve added before, and add
new routes for the home page /, /login, and /signup as follows:
module.exports = router;
The next step is to create the sign up page for the view layer.
Next, create a file named signup.ejs and write the code below:
<!DOCTYPE html>
<html lang="en">
<%- include('../partials/head') %>
<body>
<main class="flex mx-auto w-full max-w-[400px] flex-col space-y-2.5 p-4 mt-12">
<div class="flex h-24 w-full items-end rounded-lg bg-green-500 p-3">
<div class="flex flex-row items-center text-white">
<i aria-hidden="true" class="fa-3x fa-solid fa-coins pr-4">
</i>
<p class="text-[44px]">Finly</p>
</div>
</div>
<span>
Already have an account? <a href='/login' class="link link-primary link-hover">Log in</a>
</span>
</main>
</body>
</html>
This template simply renders the application name and a link to the login page in
case the user already has an account
Between the div and span elements, add a form as shown below:
The sign up form has three inputs: email, password, and repeat password.
Also, note that there are several icons added using the <i> tags. We need to add
icon libraries to our application to render those icons.
<head>
<title>Document</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/styles/style.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
/>
</head>
Also, let’s use the title dynamic value for the <title> tag content as follows:
This way, the title variable from the res.render() method call will be used for
the value of the <title> tag.
module.exports = {
signup,
};
This sign up function will take the email and password data from the request body
object, and then look for a user with the same email in our database.
We need to send a message that says the email is already registered, but we’re
going to do it later since we need another library for sending the message.
When there’s no existingUser, then Express will hash the password value using the
bcrypt library.
We haven’t installed the bcrypt library used by the signup() function yet, so let’s
do it now:
After hashing the password, Express will insert the user data into the database,
create a session object, and redirect the user to the page.
app.use(morgan('dev'));
app.use(express.static('./public'));
app.use(express.urlencoded({ extended: false }));
The extended option is used to let Express know whether we want to process
advanced input formats (like nested objects or arrays)
Since we’re only going to pass a standard form, we don’t need the extended option.
Without adding the urlencoded() middleware, the form data won’t be parsed by
Express.
<!DOCTYPE html>
<html lang="en">
<%- include('../partials/head') %>
<body>
<h1>This is the Dashboard</h1>
</body>
Next, create a dashboard.route.js file under the routes/ folder and write the
following code:
module.exports = router;
Finally, add the dashboard route into the index.js file as follows:
// ...
app.use('/', userRouter);
app.use('/dashboard', dashboardRouter);
While you can already test the sign up page from the browser, notice that we
haven’t completed the home page view and apply validation to the sign up page.
Let’s take a short break and continue the authentication feature in the next
chapter.
Summary
The code added in this chapter can be found at
https://g.codewithnathan.com/node-9
In this chapter, you’ve added the Express session and bcrypt library needed for
the authentication process.
You’ve also created the sign up page for the view and the signup() function for
the controller.
Currently, we haven’t shown an error message when the user already exists, and
we also didn’t validate the form input values.
This is not right as the user can submit the form without adding any data to it.
To validate form inputs and show error messages we need to use the express-
validator and connect-flash libraries.
I will show you how to integrate both libraries into Express. Let’s get started.
Next, open the user.controller.js file, and add the code below:
const validateSignup = [
body('email', 'Email must not be empty').notEmpty(),
body('password', 'Password must not be empty').notEmpty(),
body('password', 'Password must be 6+ characters long').isLength({ min: 6 }),
body('repeatPassword', 'Repeat Password must not be empty').notEmpty(),
body('repeatPassword', 'Passwords do not match').custom((value, { req }) => (value ===
req.body.password)),
];
The body() function from express-validator gives you access to the body object. In
this function, you can pass the property to validate and an error message to send
when the validation fails.
Once you select the field, call the validator method to execute on that field, such
as notEmpty() and isLength().
Here, we also add a custom() validation function because we want to make sure
the value of repeatPassword field is equal to the password field.
This validator array needs to be called before the signup() process, which means
we need to export it from the controller:
module.exports = {
signup,
validateSignup
};
And then imports the array inside the user.route.js file as follows:
const {
validateSignup,
signup,
} = require('../controllers/user.controller');
// ...
Back in the user.controller.js file, we can check on the result of the validation
inside the signup() method as follows:
// ...
}
The validationResult() returns the errors object generated from running the
validator functions.
When the errors object is not empty, we pass the errors as an array to the flash()
function, then redirect the user to the sign up page.
Now the flash() method is a part of the connect-flash library, which we haven’t
added to our application.
// ...
app.use(flash());
Because flash requires an existing session, make sure that you call
app.use(flash()) after app.use(session()) in your file.
And that’s it. Anytime you want to store a message on the flash, you only need to
call the req.flash() method as shown before:
req.flash('key', 'value');
The flash() method store messages in key-pair values. The first argument is the
key you can use to retrieve the message, and the second argument is the value to
save.
Once a message is retrieved, flash will remove that message from the session.
To display the error messages, you only need to access the errors object from EJS
template file.
In your signup.ejs file, include a partial template below the <h1> element as
follows:
Next, create the partials/formErrors.ejs file and write the following code in it:
When the errors array is defined, then we iterate over the array using the
forEach() method and display the error.msg string contained in each error object.
Now if you try to submit the sign up form without entering any value, you’ll get
error messages as shown below:
Not only we’ve displayed the error messages, we’ve also prevented the the
signup() function from processing the data further. Nice work!
Right now, the form inputs will be empty after the form fails, and the user needs
to re-enter their data into the form.
We can provide a better user experience by keeping the form inputs filled in case
of an error.
Back in the signup() function in the user controller, add the req.body data to the
flash object as follows:
if (!validationErrors.isEmpty()) {
const errors = validationErrors.array();
req.flash('errors', errors);
req.flash('data', req.body);
return res.redirect('/signup');
}
Now we can retrieve user data when rendering the sign up page:
router.get('/signup', (req, res) => {
res.render('pages/signup', {
title: 'Sign up',
user: req.flash('data')[0],
errors: req.flash('errors'),
});
});
With the user data retrieved, you can display that data in signup.ejs file.
<input
name="email"
type="email"
class="grow"
placeholder="Email"
value="<%= user?.email || '' %>" />
<input
name="password"
type="password"
class="grow"
placeholder="Password"
value="<%= user?.password || '' %>" />
<input
name="repeatPassword"
type="password"
class="grow"
placeholder="Repeat Password"
value="<%= user?.repeatPassword || '' %>" />
Now when the form fails to submit, the error messages will be displayed, and the
input values won’t be removed.
Inside the signup() function, we can add another flash message inside the if
(existingUser) block like this:
if (existingUser) {
req.flash('data', req.body);
req.flash('info', {
message: 'Email is already registered. Try to login instead',
type: 'error'
})
res.redirect('/signup');
}
Here, we include the req.body to flash to preserve the input values, then we add
another flash message under the 'info' key.
When rendering the sign up page, we can add the flash message to a variable as
follows:
Now we need a way to display the info data to the user. This is where we need to
use a toast notification library called Toastify.
Toastify allows you to show a notification that disappears after a certain time.
You can learn more about this library at https://apvarun.github.io/toastify-js
To use this library, you need to add the CSS and JavaScript files to your
application.
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css"
/>
Next, create a new partial template named script.ejs and add the code below:
Here, we add the link to the JavaScript code for Toastify, then we check if the info
variable is defined.
When the info variable is declared, we create a toast notification by calling the
Toastify() function and passing the appropriate arguments.
In this case, we want the notification to be positioned at the bottom right, and the
background color of the notification would follow the info.type value.
Now if you submit the form using an email that’s already registered, you should
see a toast notification as follows:
After 3000 milliseconds, the notification will disappear.
Let’s also show a notification when the user successfully signed up for a new
account
On the else block where we set the session.userId value and redirects to the
/dashboard, add the following code:
else {
//...
req.session.userId = result._id;
req.flash('info', {
message: 'Signup Successful',
type: 'success'
});
res.redirect('/dashboard');
}
Now the sign up page is completed. We’ll continue by adding the login and logout
functionalities in the next chapter.
Summary
The code added in this chapter can be found at
https://g.codewithnathan.com/node-10
In this chapter, you’ve learned how to validate user inputs using the express-
validator library.
After validating the form, you can show any existing error messages by using the
connect-flash library.
Now that the signup process is complete, it’s time to add functionalities so users
can log in and log out of their existing accounts.
But before we do that, let’s improve other parts of the application first,
particularly the landing and dashboard page, which we haven’t updated for a
while
Inside the views/ folder, you need to create a pages/index.ejs file and write the
following code:
<!DOCTYPE html>
<html lang="en">
<%- include('../partials/head') %>
<body>
<main class="flex min-h-screen flex-col p-4">
<div class="flex h-24 shrink-0 items-end rounded-lg bg-green-500 p-4 text-5xl">
<div class="flex flex-row items-center text-white">
<i class="fa-solid fa-coins pr-4"></i>
Finly
</div>
</div>
<div class="mt-4 flex grow flex-col gap-4 md:flex-row">
<div class="flex flex-col justify-center gap-6 rounded-lg bg-slate-100 px-6 py-10 md:w-2/5
md:px-10">
<h1 class="text-3xl font-medium">Welcome to Finly!</h1>
<p class="text-xl text-gray-800">
The invoicing software for small business and freelancer.
<br>
Create professional invoices in an instant with smart invoicing software.
</p>
<a class="btn btn-primary w-32" href="/login">
Log in
<i class="ml-auto fa-solid fa-arrow-right"></i>
</a>
</div>
<div class="flex items-center justify-center p-6 md:w-3/5 md:px-20 md:py-8">
<img alt="Grow your business with Finly" width="400" height="400" src="/images/revenue-
bro.png" />
</div>
</div>
</main>
<%- include('../partials/script') %>
</body>
</html>
This flash message will be used when the user logs out later.
This template will have lots of Tailwind classes because it’s a custom interface:
<!DOCTYPE html>
<html lang="en">
<%- include('../partials/head') %>
<body class="bg-gray-100">
<div class="flex h-screen overflow-hidden">
<%- include('../partials/navbar') %>
<div class="ml-56 flex-grow p-10 overflow-y-auto">
<h1 class=" mb-4 text-xl md:text-2xl">Dashboard</h1>
</div>
</div>
<%- include('../partials/script') %>
</body>
</html>
Now if you visit the dashboard page, you’ll see a navigation bar as follows:
This navigation bar can be used to test the logout function later.
<!DOCTYPE html>
<html lang="en">
<%- include('../partials/head') %>
<body>
<main class="flex mx-auto w-full max-w-[400px] flex-col space-y-2.5 p-4 mt-12">
<div class="flex h-24 w-full items-end rounded-lg bg-green-500 p-3">
<div class="flex flex-row items-center text-white">
<i aria-hidden="true" class="fa-3x fa-solid fa-coins pr-4">
</i>
<p class="text-[44px]">Finly</p>
</div>
</div>
<form action="/login" method="post" class="space-y-3">
<div class="flex flex-col rounded-lg bg-slate-100 p-6 gap-4">
<h1 class="mb-3 text-2xl">Enter Your Account</h1>
<%- include('../partials/formErrors') %>
<label class="input input-bordered flex items-center gap-2">
<i class="opacity-70 fa-solid fa-envelope">
</i>
<input
name='email'
type="email"
class="grow"
placeholder="Email"
value="<%= user?.email || '' %>" />
</label>
<label class="input input-bordered flex items-center gap-2">
<i class="opacity-70 fa-solid fa-key">
</i>
<input
name='password'
type="password"
class="grow"
placeholder="Password"
value="<%= user?.password || '' %>" />
</label>
</div>
<button class="btn btn-primary w-full">
Log in
<i aria-hidden="true" class="ml-auto fa-solid fa-arrow-right fa-lg">
</i>
</button>
</form>
<span>
Don't have an account? <a href='/signup' class="link link-primary link-hover">Sign Up</a>
</span>
</main>
<%- include('../partials/script') %>
</body>
</html>
This template file will show a form similar to the sign up form, except it only
shows two inputs: email and password.
const validateLogin = [
body('email', 'Email must not be empty').notEmpty(),
body('password', 'Password must not be empty').notEmpty(),
];
In the code above, the login() function will check on the validation result first,
just like the signup() function.
When the form is validated, we will try to find an existing user by using the email
value.
When the user is found, we check the password using bcrypt.compare(), then we
set the session.userId value to authenticate the user.
To log out a user, we only need to set the session.userId value to null, show a
flash message indicating that the user is logged out, and redirect to the landing
page.
module.exports = {
signup,
validateSignup,
login,
validateLogin,
logout
};
const {
validateSignup,
signup,
validateLogin,
login,
logout
} = require('../controllers/user.controller');
We need to add a GET and POST routes for the /login URL, and a GET route for
the /logout URL:
router.get('/logout', logout);
The login and logout functionalities are now finished. You can try to log in by
navigating to the /login URL from the browser.
Summary
The code added in this chapter is available at https://g.codewithnathan.com/node-
11
In this chapter, you’ve completed the login and logout functionalities, and you’ve
updated the landing page and the dashboard page.
Using the MVC pattern, you set the controller to run database queries using the
user model, then show the data by rendering the view.
The MVC pattern organizes our code in a clear way, so we can develop new
functionalities faster than when we don’t use the pattern.
CHAPTER 12: PROTECTING ROUTES
WITH MIDDLEWARES
Now that user authentication is completed, it’s time to add a verification process
each time the user wants to access a route that requires authentication.
These objects are defined as req, res, and next in our functions. Express
automatically sends these data when it receives an HTTP request.
This is why you need to place your specific routes above general routes like this:
app.use('/dashboard', dashboardRouter);
Then any request will match the * route, so no request will reach the /dashboard
route.
When a request arrives, Express will try to find the middleware that matches the
route ordered from top to bottom.
The (req, res, next) parameters are passed to the running middleware
automatically.
The next() function can used to run the next middleware. So far, we haven’t used
this function because we don’t need it, but Express always passes this function to
all middlewares.
Now that you know what a middleware is, let’s create one for protecting our
routes.
When this session object contains a valid user id, the request is passed to the next
middleware.
If the session isn’t valid, then we redirect the user to the login page.
Inside your libs/ folder, create a new file named middleware.js and write the
following code in it:
module.exports = {
verifyUser,
};
The verifyUser() middleware will check whether there’s a valid userId property
in the request session object.
When there’s no userId property, the function redirects to the login page.
Now you can use this middleware on the dashboard route. Open the index.js file,
import the middleware, and place it in the app.use('/dashboard') call as follows:
// ...
app.use('/dashboard', verifyUser, dashboardRouter);
When unauthenticated users try to access the dashboard page, they will be
redirected to the login page.
module.exports = {
verifyUser,
redirectAuthenticated
};
Import the middleware inside the user.route.js file, then place it in front of the
sign up and login pages as shown below:
Now whenever authenticated users access the sign up or login page, they will be
redirected to the dashboard.
Summary
The code added in this chapter is available at https://g.codewithnathan.com/node-
12
In this chapter, you’ve learned how Express middlewares work and created two
middlewares for protecting the routes in your application.
In the next chapter, we’re going to develop one of the main features of this
application, which is handling customer data.
CHAPTER 13: CREATE, READ, UPDATE,
AND DELETE CUSTOMERS
Now we need to provide a way for users to create, read, update, and delete
customers from the application.
Since we’ve seen the MVC pattern in action before, developing this feature will
feel familiar.
We need to start by creating the data model, then create controllers that use
request data to manipulate the model accordingly, and then present the data
using a view template.
module.exports = Customer;
The model above is connected to the User model through the owner field, which
reference the _id field of the User model.
Inside the controllers/ folder, create the customer.controller.js file and begin by
importing the model and express validator:
Next, create the validation logic for the customer data. Here, we simply check that
the values are not empty:
const validateCustomer = [
body('name', 'Name must not be empty').notEmpty(),
body('email', 'Email must not be empty').notEmpty(),
body('phone', 'Phone must not be empty').notEmpty(),
body('address', 'Address must not be empty').notEmpty(),
];
The following showCustomers() function will find customers of the current user,
then render the customers page:
res.render('pages/customers', {
title: 'Customers',
type: 'data',
customers,
info: req.flash('info')[0],
});
};
You can see here that we use the req.session.userId value to find the customers.
Notice there’s a new type variable added to the render() method. This variable
will be used by our view template later to show the correct partial template.
module.exports = {
showCustomers,
validateCustomer,
};
We’ll go back to create more functions later. But for now, let’s focus on showing
the customer data.
<!DOCTYPE html>
<html lang="en">
<%- include('../partials/head') %>
<body class="bg-gray-100">
<div class="flex h-screen overflow-hidden">
<%- include('../partials/navbar') %>
<div class="ml-56 flex-grow p-10 overflow-y-auto">
<% if (type === 'data') { %>
<%- include('../partials/customerData') %>
<% } else { %>
<%- include('../partials/customerForm') %>
<% } %>
</div>
</div>
<%- include('../partials/script') %>
</body>
</html>
In this template, we use the type variable value to render either the customerData
or the customerForm template.
The customerData template will show existing customers along with some buttons
to modify the data.
In your views/partials/ folder, create the customerData.ejs file and write the code
below:
<div class="w-full">
<div class="flex w-full items-center justify-between">
<h1 class="text-2xl"><%= title %></h1>
</div>
<form>
<div class="mt-4 flex items-center justify-between gap-2 md:mt-8">
<div class="relative flex flex-1 flex-shrink-0">
<label for="search" class="input input-bordered flex items-center gap-2 w-full">
<i class="fa-solid fa-magnifying-glass"></i>
<input id="search" name="search" type="text" class="grow" placeholder="Search
customers..." />
</label>
</div>
<a class="btn btn-primary" href="customers/create">
<i class="fa-solid fa-plus fa-lg mr-2"></i>
New Customer
</a>
</div>
</form>
</div>
The above code will render a search input and a button to create a new customer.
Just below the closing </form> tag, add the code to render a table as follows:
The table above will show the customer data, then show two buttons: one for
editing the customer, and one for deleting the customer.
Notice that on the Delete button, there’s an onclick attribute that calls the
deleteModal() function.
The deleteModal() function is used to ask for confirmation from users that they
really want to delete the customer data.
First, you need to create a <dialog> element that will be used as the modal. You
can place this element at the bottom of the template file:
Next, add a <script> element and define the deleteModal() function as follows:
<script>
function deleteModal(customerId){
const modal = document.querySelector('#delete-modal');
const deleteForm = document.querySelector('#delete-form');
deleteForm.setAttribute('action', `customers/${customerId}/delete`)
modal.showModal();
}
</script>
The function simply sets the action attribute of the delete-form that we created
inside the modal, then shows that modal.
When the user clicks on 'Yes', then the customer data will be deleted. Otherwise,
the modal is simply disabled again.
Next, you need to create the customer form template. This will be a simple form
with four inputs as shown below:
Now the view is completed. Let’s continue with adding the routes.
const {
showCustomers,
} = require('../controllers/customer.controller');
router.get('/', showCustomers);
module.exports = router;
The customer routes will be nested below the /dashboard route, so you need to
import this route on the dashboard.route.js file:
// router.get...
router.use('/customers', customersRouter);
module.exports = router;
Alright, now you can navigate to the /customers route, but there’s only an empty
table there for now:
Next, we need to enable the users to create a new customer.
await Customer.create(newCustomer);
req.flash('info', {
message: 'Customer Created',
type: 'success'
});
res.redirect('/dashboard/customers');
};
This function will run after the validator, so it will check on the validation results.
If there’s any error, we redirect the user to the create page. Otherwise, we set the
newCustomer data and call the Customer.create() method to insert the user to the
database.
After that, we simply set the info message and redirect the users to the customers
page.
const {
showCustomers,
createCustomer,
validateCustomer
} = require('../controllers/customer.controller');
router.get('/', showCustomers);
Now if you press the '+ New Customer' button on the customers page, you will be
shown the customer form:
Fill out this form and submit it, and you’ll see the customer data shown in the
table
res.render('pages/customers', {
title: 'Edit Customer',
type: 'form',
formAction: 'edit',
customer: req.flash('data')[0] || customer,
errors: req.flash('errors'),
});
};
This function will be used for the GET route when editing customers.
The function above will call the Customer.findByIdAndUpdate() method when the
request data passes the validation process.
module.exports = {
showCustomers,
editCustomer,
updateCustomer,
createCustomer,
validateCustomer,
};
Alright, now you need to create the update routes in customer.route.js file:
const {
showCustomers,
editCustomer,
updateCustomer,
createCustomer,
validateCustomer
} = require('../controllers/customer.controller');
// other routes...
router.get('/:id/edit', editCustomer);
Now when you click on the edit button, you will be shown a form populated with
existing customer data.
You can update the data as you need, then click the submit button to update the
database.
Deleting Customers
The last step is to add the delete customer function. This function is very simple:
await Customer.findByIdAndDelete(customerId);
req.flash('info', {
message: 'Customer Deleted',
type: 'success'
});
res.redirect('/dashboard/customers');
};
module.exports = {
showCustomers,
editCustomer,
deleteCustomer,
updateCustomer,
createCustomer,
validateCustomer,
};
The function will call the findByIdAndDelete() method to delete the customer data.
const {
showCustomers,
editCustomer,
updateCustomer,
createCustomer,
deleteCustomer,
validateCustomer
} = require('../controllers/customer.controller');
// ...
router.post('/:id/delete', deleteCustomer);
Here, the id of the customer that wants to be deleted will be read from the URL
parameter, which we have set in the deleteModal() function.
Now all functionalities relating to the customer data are finished. We still have
the search function, which we will add later.
For now, let’s take a short break before continuing to the next chapter.
Summary
The code added in this chapter is available at https://g.codewithnathan.com/node-
13
In this chapter, you’ve implemented a new feature to your Node application from
scratch.
Here, you used the MVC pattern extensively. First, you create the model, then
create the controller function, then the views.
The controller then got connected to the application by creating new routes that
call the right controller functions.
Now users can create, read, update, and delete customers. Good work!
CHAPTER 14: HANDLING INVOICES
DATA
Now we need to develop a feature to manipulate invoice data, which is the main
purpose of our application.
Handling invoice data will be similar to how we handled the customer data in the
previous chapter, but there is some extra code for formatting the date and
amount values.
module.exports = Invoice;
The invoice model is connected to both the user and customer models, so there
are owner and customer fields in this model.
Later in the form, we can select the customer that we want to pass the invoice to.
Creating the Invoice Controller
The next step is to create the controller. In the controllers/ folder, create a new
file named invoice.controller.js and import the modules that will be used:
Next, write the validation logic for the invoice data. Let’s just make sure that none
of the values are empty:
const validateInvoice = [
body('customer', 'Select the Customer').notEmpty(),
body('amount', 'Amount must not be empty').notEmpty(),
body('date', 'Due Date must not be empty').notEmpty(),
body('status', 'Select the Status').notEmpty(),
]
After that, you need to create a function to show the existing invoices.
Now the function to show invoices also needs to retrieve customer data, so you
need to use the populate() method from Mongoose.
The populate() method is used to pull referenced document data. Using this
method, we can pull the customer name for each invoice we have.
The populate method is called on the query object, which is returned when you
call the find() method.
This means you can call the populateInvoices() method and pass Invoices.find()
as the argument like this:
The customer field will be transformed into an object with _id and name properties.
We’ll use this object later in the views. Export the function from the file:
module.exports = {
showInvoices
}
<!DOCTYPE html>
<html lang="en">
<%- include('../partials/head') %>
<body class="bg-gray-100">
<div class="flex h-screen overflow-hidden">
<%- include('../partials/navbar') %>
<div class="ml-56 flex-grow p-10 overflow-y-auto">
<% if (type === 'data') { %>
<%- include('../partials/invoiceData') %>
<% } else { %>
<%- include('../partials/invoiceForm') %>
<% } %>
</div>
</div>
<%- include('../partials/script') %>
</body>
</html>
Now the next step is to create the partial templates.
First, the invoiceData.ejs file. This one is similar to customersData.ejs except for
the table content:
<div class="w-full">
<div class="flex w-full items-center justify-between">
<h1 class="text-2xl"><%= title %></h1>
</div>
<form>
<div class="mt-4 flex items-center justify-between gap-2 md:mt-8">
<div class="relative flex flex-1 flex-shrink-0">
<label for="search" class="input input-bordered flex items-center gap-2 w-full">
<i class="fa-solid fa-magnifying-glass"></i>
<input id="search" name="search" type="text" class="grow" placeholder="Search invoices..."
/>
</label>
</div>
<a class="btn btn-primary" href="invoices/create">
<i class="fa-solid fa-plus fa-lg mr-2"></i>
New Invoice
</a>
</div>
</form>
<div class="mt-6 overflow-x-auto bg-white rounded-lg p-2">
<table class="table">
<thead>
<tr>
<th>Customer Name</th>
<th>Amount</th>
<th>Due Date</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
<% invoices.forEach(function(invoice){ %>
<tr>
<td> <%= invoice.customer.name %> </td>
<td> <%= invoice.amount %> </td>
<td> <%= new Date(invoice.date).toLocaleDateString('en-US') %> </td>
<td>
<% if(invoice.status === 'paid') { %>
<span class="ml-2 badge badge-sm badge-success p-3 gap-3 text-white">
Paid <i class="fa-regular fa-circle-check"></i>
</span>
<% } else { %>
<span class="ml-2 badge badge-sm badge-ghost p-3 gap-3">
Pending <i class="fa-regular fa-clock"></i>
</span>
<% } %>
</td>
<td>
<div class="flex justify-end gap-3">
<a
class="rounded-md border p-2 hover:bg-gray-100"
href="invoices/<%= invoice._id %>/edit">
<i class="fa-solid fa-pen-to-square fa-lg"></i>
</a>
<button
class="rounded-md border p-2 hover:bg-gray-100"
onclick="deleteModal('<%= invoice._id %>')">
<span class="sr-only">Delete</span><i class="fa-solid fa-trash fa-lg"></i>
</button>
</div>
</td>
</tr>
<% }); %>
</tbody>
</table>
</div>
</div>
<dialog id="delete-modal" class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg">Are you sure?</h3>
<p class="py-4">The invoice will be deleted</p>
<div class="modal-action">
<form id='delete-form' method="post">
<button class="btn btn-danger">Yes</button>
</form>
<form method="dialog">
<button class="btn">Cancel</button>
</form>
</div>
</div>
</dialog>
<script>
function deleteModal(invoiceId) {
const modal = document.querySelector('#delete-modal');
const deleteForm = document.querySelector('#delete-form');
deleteForm.setAttribute('action', `invoices/${invoiceId}/delete`)
modal.showModal();
}
</script>
Open your head.ejs file and add the following CSS library:
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/datepicker.min.css"
/>
Now in the partials/ folder, create the invoiceForm.ejs template and write the
code below in it:
The form contains a select input for the customer, a number input for the
amount, a datepicker for the due date, and a radio input for the status.
<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/datepicker-full.min.js">
</script>
<script>
const elem = document.querySelector('#date');
const datepicker = new Datepicker(elem, {
autohide: true,
format: 'm/d/yyyy'
});
</script>
Here, the datepicker is rendered on the #date element, which is the invoice due
date input we’ve added above.
Inside the routes/ folder, create an invoice.route.js file and write the code
below:
const {
showInvoices,
} = require('../controllers/invoice.controller');
router.get('/', showInvoices);
module.exports = router;
// ...
router.use('/invoices', invoicesRouter);
Now you can already visit the /dashboard/invoices URL from the browser, but
there’s only an empty table there.
await Invoice.create(newInvoice);
req.flash('info', {
message: 'New Invoice Created',
type: 'success'
});
res.redirect('/dashboard/invoices');
};
Oh and since we need to get the customer data for the invoice form, let’s create
another function to get the customer data:
module.exports = {
showInvoices,
createInvoice,
getCustomers,
validateInvoice
};
const {
showInvoices,
createInvoice,
getCustomers,
validateInvoice
} = require('../controllers/invoice.controller');
Update Invoices
The next step is to enable updating invoices. Back on the invoice.controller.js
again, write the editInvoice() function:
res.render('pages/invoices', {
title: 'Edit Invoice',
type: 'form',
formAction: 'edit',
customers,
invoice: req.flash('data')[0] || invoice,
errors: req.flash('errors'),
});
};
module.exports = {
showInvoices,
createInvoice,
getCustomers,
editInvoice,
updateInvoice,
validateInvoice
};
const {
showInvoices,
createInvoice,
editInvoice,
updateInvoice,
getCustomers,
validateInvoice
} = require('../controllers/invoice.controller');
Delete Invoices
The last step is to enable the delete invoice function.
await Invoice.findByIdAndDelete(invoiceId);
req.flash('info', {
message: 'Invoice Deleted',
type: 'success'
});
res.redirect('/dashboard/invoices');
};
module.exports = {
showInvoices,
editInvoice,
deleteInvoice,
updateInvoice,
createInvoice,
getCustomers,
validateInvoice
};
const {
showInvoices,
editInvoice,
createInvoice,
updateInvoice,
deleteInvoice,
getCustomers,
validateInvoice
} = require('../controllers/invoice.controller');
//...
router.post('/:id/delete', deleteInvoice);
And that’s it. Now you can manipulate the invoice data as required.
Open your customer.controller.js file, import the invoice model, and update the
deleteCustomer() function:
This way, the customer and invoice data will always be up to date.
Summary
The code added in this chapter is available at https://g.codewithnathan.com/node-
14
In this chapter, you’ve implemented a feature to create, read, update, and delete
invoices from the application.
Aside from a few small differences like rendering a datepicker and creating a
select input, the invoice views are similar to the customer views.
Right now, our dashboard is still empty, so let’s fill it with customer and invoice
data to show some insights.
We will also use Chart.js to draw a chart on the dashboard showing the revenue
in the last 6 months.
When you open the invoices page, notice that the invoice amount is shown as is:
To make it more pleasing, let’s format the amount in USD instead of just bare
numbers.
In the libs/ folder, create a new file named formatter.js and add the code below:
module.exports = {
USDollar,
};
The code above creates a new international number format object that can be
used to format numbers.
Now in the invoice.controller.js file, import the object and pass it to the
res.render() method that renders the invoice table:
// ...
const showInvoices = async (req, res) => {
const query = { owner: req.session.userId };
After passing the object, you can use it in the invoiceData.ejs file as follows:
Now if you open the invoices page, you see the number formatted as $300.00.
Looks nice!
First, there are 4 boxes showing the total sum of invoices that have been paid, are
still pending, total invoices created, and total customers:
Because the dashboard doesn’t create new data, we don’t need a model.
module.exports = {
showDashboard,
};
The showDashboard() function needs to get the data required by the dashboard
page.
Inside the function, get the total count of the customers and invoices using the
countDocument() method first:
Next, you need to get all invoices created by the user, then pull the customer
name using populate() like this:
const allInvoices = await Invoice.find(query)
.populate({
path: 'customer',
model: Customer,
select: '_id name',
});
The above code will fetch all invoice data. You can then use the reduce()
JavaScript function to get the total amount of paid and pending invoices:
res.render('pages/dashboard', {
title: 'Dashboard',
invoiceCount,
customerCount,
totalPaid,
totalPending,
USDollar,
info: req.flash('info')[0]
});
<!DOCTYPE html>
<html lang="en">
<%- include('../partials/head') %>
<body class="bg-gray-100">
<div class="flex h-screen overflow-hidden">
<%- include('../partials/navbar') %>
<div class="ml-56 flex-grow p-10 overflow-y-auto">
<h1 class=" mb-4 text-2xl"><%= title %></h1>
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
<div class="rounded-xl bg-white p-2 shadow-sm">
<div class="flex p-4">
<i aria-hidden="true" class="fa-solid fa-money-bills"></i>
<h3 class="ml-2 text-sm font-medium">Collected</h3>
</div>
<p class=" truncate rounded-xl border-slate-200 border px-4 py-8 text-center text-2xl">
<%= USDollar.format(totalPaid) %>
</p>
</div>
<div class="rounded-xl bg-white p-2 shadow-sm">
<div class="flex p-4">
<i aria-hidden="true" class="fa-regular fa-clock"></i>
<h3 class="ml-2 text-sm font-medium">Pending</h3>
</div>
<p class=" truncate rounded-xl border-slate-200 border px-4 py-8 text-center text-2xl">
<%= USDollar.format(totalPending) %>
</p>
</div>
<div class="rounded-xl bg-white p-2 shadow-sm">
<div class="flex p-4">
<i aria-hidden="true" class="fa-regular fa-folder-open"></i>
<h3 class="ml-2 text-sm font-medium">Total Invoices</h3>
</div>
<p class=" truncate rounded-xl border-slate-200 border px-4 py-8 text-center text-2xl">
<%= invoiceCount %>
</p>
</div>
<div class="rounded-xl bg-white p-2 shadow-sm">
<div class="flex p-4">
<i aria-hidden="true" class="fa-solid fa-users"></i>
<h3 class="ml-2 text-sm font-medium">Total Customers</h3>
</div>
<p class=" truncate rounded-xl border-slate-200 border px-4 py-8 text-center text-2xl">
<%= customerCount %>
</p>
</div>
</div>
</div>
</div>
<%- include('../partials/script') %>
</body>
</html>
The code above uses CSS grid to make the layout of the 4 boxes responsive.
Now if you open the dashboard page, you will see the insights shown.
We’re going to use Chart.js to do this, and if you never use Chart.js before, don’t
worry because it’s quite simple to use.
Basically, Chart.js allows you to create a chart on top of the HTML <canvas>
element by calling a function and specifying the options for the chart.
Back in the dashboard.ejs file, you can create an empty <canvas> element below
the <div> with grid class as follows:
At the bottom of the file, add a link to fetch Chart.js code, then create a new chart
instance by calling the new Chart() function:
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const canvas = document.getElementById('revenueChart');
Chart(canvas, {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: 'Revenue',
data: [100, 200, 300, 400, 500, 600],
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
</script>
When you call the new Chart() function, pass the canvas as the first argument,
and the chart options as the second argument.
Here, we pass the type of the chart we want to render, which is the bar chart.
The data object allows you to specify the data to use in the chart. The labels here
will label each bar generated.
The datasets property contains various options, but the most important one is the
data array, which is the value represented by the chart.
The code looks a bit complicated, but the main point is that the for loop will run 6
times, and in each loop, we get the revenue data for a month.
The loop begins in the current month, and then moves backward for the last 6
months.
As a result, the revenueData variable becomes an array of objects that contain the
name of the month and the revenue for that month like this:
[
{ month: 'Nov', revenue: 300 },
{ month: 'Dec', revenue: 100 },
{ month: 'Jan', revenue: 0 },
{ month: 'Feb', revenue: 400 },
{ month: 'Mar', revenue: 50 },
{ month: 'Apr', revenue: 350 }
]
Next, pass the revenueData to the dashboard page as a JSON string in res.render():
res.render('pages/dashboard', {
title: 'Dashboard',
revenueData: JSON.stringify(revenueData),
// ...
});
Now you can use the revenueData variable on the <script> tag for rendering the
chart:
<script>
const revenueData = JSON.parse('<%- revenueData %>');
const canvas = document.getElementById('revenueChart');
new Chart(canvas, {
type: 'bar',
data: {
labels: revenueData.map(item => item.month),
datasets: [{
label: 'Revenue',
data: revenueData.map(item => item.revenue),
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
// ...
});
</script>
The revenueData is parsed using the JSON.parse() method, then the values are
passed to the labels and data options using the array map() method.
Now if you refresh the dashboard page, your invoice data will be reflected on the
chart:
In the dashboard controller, you need to sort the allInvoices data in descending
order, then slice the first 5 elements of the array as follows:
res.render('pages/dashboard', {
title: 'Dashboard',
latestInvoices,
// ...
});
And update the view template to render the invoices as shown below:
And that’s it. Now you should be able to see the last 5 invoices as shown below:
Summary
The code added in this chapter is available at https://g.codewithnathan.com/node-
15
In this chapter, you’ve learned how to format numbers using the international
number format in JavaScript.
You’ve also updated the dashboard page to show some insights to the user,
displaying the total amount of collected and pending invoices, and adding Chart.js
to show the revenue chart.
You’ve come a very long way! Only one more chapter, then we can deploy this
application for others to see.
CHAPTER 16: ADDING THE SEARCH
FEATURE
With the dashboard page done, the last feature to develop is the search feature on
the customer and invoice pages.
If you type something into the search bar and then press Enter, you will see that
the input value is already added to the URL query parameter under the ?search=
parameter:
This means that the view is already reacting to the user action. You only need to
update the controller to take into account the search value and filter the returned
results.
When the search variable is defined, we add the $or operator so that Mongoose
will search for any matching value in the name, email, phone, and address fields.
The rest of the code stays the same, so if you do a search now, you will see only
matching customer data on the table.
In the showInvoices() function, unpack the search value from req.query object,
then pass it to the populateInvoices() function as follows:
The next step is to update the populateInvoices() function. You need to add the
match option when the search argument is defined.
To make this possible, separate the options passed to the populate option as a
variable, then modify the populate option only when search is defined:
The customer property will be null when the customer data doesn’t match the
search value, so you need to filter the invoice data and remove all invoices that
have the customer value of null.
This can be done on the return statement, where you can chain the populate()
method call with a then() as follows:
return query
.populate(populateOptions)
.then(invoices => invoices.filter(invoices => invoices.customer != null));
Alright, now the invoices can be filtered by the customer name field.
Summary
The code added in this chapter can be found at
https://g.codewithnathan.com/node-16
Here, you just added the controller functions to process the search query
parameter passed by the views.
Mongoose can filter the data you want to retrieve by using the $or operator.
When you need to filter a populated field, you can add the match option, and then
remove all documents that don’t have a matching result.
CHAPTER 17: DEPLOYING NODE.JS
APPLICATION
All that’s left is to deploy the application to a production server so that you can
show your work to others.
In the views/ folder, you can see that we still have the index.ejs and user.ejs files
that are used in the beginning.
The index.ejs file is still used for the wild card route * to display the 'Not Found'
message, so let’s clean up the content of the file.
Remove the paragraph and the button from the template, then add class to style
the <h1> tag as follows:
<!DOCTYPE html>
<html lang="en">
<%- include('./partials/head') %>
<body>
<h1 class="text-2xl m-5"><%= message %></h1>
</body>
</html>
Next, open the index.js file to update the * route and add the title variable:
Also, we need to duplicate the devcss command but omit the watch -w option.
Let’s call the command css as follows:
{
"css": "postcss public/styles/tailwind.css -o public/styles/style.css",
"devcss": "postcss public/styles/tailwind.css -o public/styles/style.css -w"
}
This is because we’re not going to watch for any changes after deploying the
application.
Head over to https://github.com/ and login or register for a new account if you
don’t have one already.
From the dashboard, create a new repository by clicking + New on the left
sidebar, or the + sign on the right side of the navigation bar:
A repository (or repo) is a storage space used to store software project files.
In the Create a Repository page, fill in the details of your project. The only
required detail is the repository name:
You can make the repository public if you want this project as a part of your
portfolio, or you can make it private.
Once a new repo is created, you will be given instructions on how to push your
files into the repository.
Now you need to create a repository for your project. Open the command line on
the finly/ folder, then run the git init command:
git init
This will turn your project into a local repository. Add all project files into this
local repo by running the git add . command:
git add .
Changes added to the repo aren’t permanent until you run the git commit
command. Commit the changes as shown below:
The -m option is used to add a message for the commit. Usually, you summarize
the changes committed to the repository as the message.
Now you need to push this existing repository to GitHub. You can do so by
following the GitHub instructions:
You might be asked to enter your GitHub username and password when running
the git push command.
Once the push is complete, refresh the GitHub repo page on the browser, and you
should see your project files and folders there.
The remote server where we’re going to deploy our application can fetch the code
from this repository.
The platform offers a free tier that you can use to deploy the Node application
we’ve created.
You can sign up for an account at https://railway.app and agree to the privacy
policy and fair use agreement.
Railway will deploy your application, and you’ll be taken into the Variables
setting.
Here, you need to click on the Raw Editor link to open a text editor. Open your
.env file, then copy and paste it to the Railway editor.
Click Save or Update Variables and see the variables we have recorded as follows:
The next step is to run the css command that we’ve added in the build process.
On the Railway menu, click the Settings page, and type 'build command' into the
filter input. You should see the Custom Build Command option.
Click the + Build Command button, then enter npm run css:
Save the changes, and Railway will ask for your permission to deploy the
changes.
Still on the Settings page type 'domain' into the filter input to see the Public
Networking option.
With that, the deployment is completed. You can try to access the application
using the given domain.
Summary
The code changes up to this chapter is available at
https://g.codewithnathan.com/node-17
In this chapter, we clean up the project code a little before we deploy the
application to the public internet.
We also created a GitHub repository to store our code as the source for the
deployment, and finally deployed the Node.js application on Railway.
It’s time to give yourself a pat on the back because you just passed a very
important milestone in your knowledge gain. I’m so proud of you!
WRAPPING UP
Congratulations on finishing this book! It wasn’t easy, but you did it anyway.
You have learned how to use Node.js to build a complex full-stack web
application, and then deploy it to production.
I hope you have fun learning about Node.js as I did writing this book.
Though the book ends here, your journey to master Node.js is not over yet. You
can practice by building more applications using the technology.
React can also be combined with Node.js, Express, and MongoDB to develop a
MERN application, which is more scalable and easier to manage than a
Node.js/Express application.
If you want to learn about the MERN stack, I have written a book that you can get
at https://g.codewithnathan.com/mern-book
I’m currently planning to write more books on web development topics, so you
might consider subscribing to my newsletter to know when I release a new book
at https://codewithnathan.com/newsletter
I also have a 7-day free email course on Mindset of The Successful Software
Developer which is free at https://sebhastian.gumroad.com/l/mindset
Each email unveils a slice of wisdom that I got after working as a web developer
and programmer for 8+ years in the tech industry, from startups to mega-
corporations. It offers a fresh perspective on what it means to be a happy and
successful software developer.
If you like the book, I would appreciate it if you could leave me a review on
Amazon because it would help others to find this book.
If there are some things you wish to tell me about this book, feel free to email me
at [email protected]. I’m very open to feedback and eager to improve
my book so that it can be a great resource for people learning to code.
If you didn’t like the book, or if you feel that I should have covered certain
additional topics, please let me know in the email.
I wish you luck on your software developer career onward, and I’ll see you again
in other books.
A Step-By-Step Guide to Build an MVC Web Application With Node.js, Express, and
MongoDB
By Nathan Sebhastian
https://codewithnathan.com