Here's the 4th blog of this series that covers Building a Full Stack Web Application using Next JS.
We would essentially use Next.js & MongoDB to build the data and to store and retrieve it.
Before you read further, make sure to checkout my Previous Blog on this series.
But waitπ«·πΌ, if you are someone who has already setup their next.js project and is struggling to figuring out "how to setup backend in next js using mongoose, bcrypt, jsonwebtoken (jwt)" then trust me, this will be your final search to the Topic π₯³
NOTE: For the sake of this series, I won't be using Next Auth. I know that it is a superb library and provides a solution for authentication, session management and user account creation in one roof.
But I am currently focusing on helping everyone understand the basics of authentication and session management using simple tools inside Next JS.
Before Jumping on to the setup, let me present you the importance of each and every element that will be used to plot our backend.
mongoose:
In our project, Mongoose is an essential tool for interacting with MongoDB.
Mongoose allows you to define schemas, which represent the structure of your data in MongoDB. Schemas define the properties, types, and constraints of your data, providing a clear structure for your documents.
It supports middleware functions that enable you to execute logic before or after certain database operations.
Additionally, Mongoose simplifies the process of querying MongoDB by providing a fluent and expressive API for performing CRUD operations.
bcryptjs:
bcrypt.js is a library used for securely hashing passwords.
One of the primary functions of bcrypt.js is to securely hash passwords before storing them in the database.
Hashing is a one-way process that converts plain text passwords into a fixed-length string of characters, making it computationally infeasible for an attacker to reverse-engineer the original password.
jsonwebtoken (JWT):
JWTs are commonly used for authentication purposes. When a user logs in to your application, a JWT is generated and sent to the client.
It contains information about the user (such as their user ID or role) and is digitally signed by the server to ensure its authenticity.
In addition to authentication, JWTs can also be used for authorization. You can include user permissions or roles in the JWT payload, allowing you to make access control decisions based on the contents of the token. We will be taking a look at practical implementation of JWT in the upcoming section of this blog..env file:
The dotenv (.env) file is a crucial component for managing environment variables in your project.
This file allows you to store sensitive configuration variables, such as database credentials, API keys, and other environment-specific settings, outside of your codebase.
In this project, the .env file contains configuration variables such as database connection strings, JWT secret.
route.js file:
In a Next.js project, the
route.js
file is a common convention used to define the routes or endpoints for serverless functions or API routes.It handles the incoming HTTP requests and defines the logic for processing and responding to those requests.
If you are working on Next JS version 14 and if you have enabled "App Router", then you are likely to place your route file as: src/app/api/route.js
Diagram Representation of Token verification via middleware file:
The above image shows raw representation of how the routes would work in case of presence or absence of tokens.
In our project:
we have declared the Signup route and Signin route as --> Public Route.
Dashboard route as --> Protected Route.
So we will be able to open SignUp and SignIn page, irrespective of presence or absence of token.
But, if the token is absent, we won't be able to open Dashboard page.
Q. Why is token so important that I am emphasizing on this topic a lot ? π€
Ans. Well here are 2 Reasons to its defence:
It saves time. When the user wants to access some features which he/she has already accessed some time ago (It can be upto an hour ago), he/she does not need to enter the authentication information again. Simply clicking on the link to the particular page can open that up.
Token-based authentication improves security by ensuring that only users with valid tokens can access protected resources, thereby reducing the risk of unauthorized access and enhancing data protection.
Q. How are tokens originated ? π€·πΌββοΈ
Ans. In this project, tokens are originated during the sign-in process, specifically in the signin/route.js
file. When a user successfully signs in with their credentials, the server generates a token using the "jsonwebtoken"
library. This token contains information about the user's identity and possibly their permissions or roles.
Once the token is generated, it is sent back to the client as part of the response to the sign-in request. The client then typically stores this token locally, often in browser storage (such as local storage or session storage) or in memory.
Having discussed the basics, let's get to setting up backend for our Next JS project.
Step 1:
Install the dependencies in your pre-existing next js project:
npm install axios bcryptjs dotenv jsonwebtoken mongoose
Step 2:
Setup the important directories and files inside your main directory.
You would need to have the following files arranged in order to properly run your backend.
.env: inside root directory
config.js: inside src, make a new folder "database" and place it inside the folder.
user.js: inside src, make a new folder "models" and place it inside the folder.
middleware.js: place it inside src folder.
Add route files as instructed below
Add a folder "api" inside src/app.
Make a new folder "users" inside api.
Add folders namely "signup", "signin" inside users folder.
Finally add route.js file inside both the folders.
You can compare your file arrangement here.
sample-frontend
ββββ.next
β
ββββnode_modules
β
ββββpublic
β β ...
β
ββββsrc
β
ββββapp
β β favicon.ico
β β globals.css
β β layout.js
β β page.js
β β page.module.css
β β
β ββββapi
β β β
β β ββββusers
β β ββββlogout
β β β route.js
β β β
β β ββββsignin
β β β route.js
β β β
β β ββββsignup
β β route.js
β β
β ββββcomponents
β β ...
β β
β ββββdashboard
β β dashboard.css
β β page.js
β β
β ββββsignin
β β page.jsx
β β signin.css
β β
β ββββsignup
β β page.jsx
β β signup.css
β β
β ββββ...
β
ββββ middleware.js
β
ββββ database
β config.js
β
ββββ models
user.js
ββββ .env
ββββ .gitignore
ββββ firebase.js
ββββ jsconfig.json
ββββ next.config.mjs
ββββ package-lock.json
ββββ package.json
ββββ README.md
Step 3:
Make a new account (If you don't have one) in mongoDB Atlas setup the database: https://account.mongodb.com/account/login
You may refer to this video in order to get help to set this up: https://youtu.be/084rmLU1UgA?feature=shared
Get your unique MongoDB connection string (MONGODB_URI) and paste it in .env file as following and save it β¬οΈ.
MONGODB_URI=paste your mongoDB connection string here
Step 4:
Copy and paste the code for each file from my GitHub Repository : https://github.com/Sujal-2820/Full-Stack-Web-Application
Now one by one we will be looking at what's the role of each file in implementing the backend:
database/config.js:
ππΌ responsible for establishing a connection to the MongoDB database.
ππΌ It utilizes the Mongoose library to connect to MongoDB.
ππΌ It uses environment variables (
process.env.MONGODB_URI
) to securely store the connection URI.models/user.js:
β‘οΈ This file defines the schema for the user data stored in the MongoDB database.
β‘οΈ It utilizes Mongoose's schema feature to define the structure of the user document, including fields like username, email, and password.
β‘οΈ It creates a Mongoose model named
User
based on the defined schema.middleware.js:
π― This file implements middleware functionality in Next.js to control user access to different routes based on authentication status.
π― First It extracts the current pathname from the request object to determine the route the user is trying to access.
π― Then it retrieves the authentication token from the user's cookies, allowing for user authentication.
π― Route Access Control: It defines rules for route access:
Allows access to
/signup
and/signin
routes without requiring a token.Redirects the user to the
/signin
page if they try to access the/dashboard
route without a token.Redirects the user to the home page (
/
) if they try to access any other route without a token.Allows access to routes specified in the
matcher
array if a token exists.
route.js (api/users/signup/route.js):
β‘οΈ This file defines the route handler for the user signup endpoint.
β‘οΈ It first ensures the database connection is established by calling the
Connection
function from thedatabase/config.js
file.β‘οΈ Then parses the incoming request body to extract the username, email, and password provided by the user during signup.
β‘οΈ Checks if a user with the provided email already exists in the database. If an existing user is found, it returns a response indicating that the user already exists.
β‘οΈ It securely hashes the user's password using the
bcrypt
library before storing it in the database. This ensures that passwords are not stored in plain text, enhancing security.β‘οΈ It creates a new user object with the provided username, email, and hashed password, and saves it to the database using the
save
method.route.js (api/users/signin/route.js):
ππΌ Manages the logic for the user signin endpoint, handling incoming requests to authenticate users.
ππΌ Connects to the database (using
Connection
function) to check if the user exists and validate the provided password.ππΌ If the user exists and the password is correct, generates a JWT (JSON Web Token) containing the user ID, signs it with a secret key (
process.env.JWT_SECRET
), and sets it as an HTTP-only cookie in the response for subsequent authentication.
NOTE: The secret, known as "JWT_SECRET"
, acts as a key to encode and decode the token.
It's essential to securely store the JWT_SECRET
within the .env
file, ensuring it remains confidential.
It can be any set of characters:
MONGODB_URI=paste your mongoDB connection string here
JWT_SECRET=Random set of alpbhanumeric characters
Calling the route file from frontend file:
Tech geeks who are following this series from Blog 1 would surely know that I previously established a basic frontend of this project.
Therein I also initialized the SignUp and SignIn files as signup/page.js
and signin/page.js
.
We need to implement a function inside these files which would call the API endpoint inside the respective route files.
Here is an example showing the same inside signup/page.js
:
const handleSubmit = async (e) => {
e.preventDefault();
try {
console.log(username,email,password);
const response = await axios.post("/api/users/signup", {
username,
email,
password,
});
console.log(response.data);
router.push('/signin');
} catch (error) {
console.error(error.response.data.message);
setErrorMessage(error.response.data.message);
}
};
In the handleSubmit() function above, we have used the endpoint as /api/users/signup. This endpoint acts as a reference and will make sure to transfer the data form signup/page.js
to β‘οΈ /api/users/signup/route.js
.
Similarly you can establish other route files and call the endpoints to their reference.
Backend representation of User Actions for this project:
Examine this diagram closely, and you'll gain a precise understanding of how all the components seamlessly interconnect and operate together π€π₯.
Well Done π, you are all set with the understanding about backend routes, models and middleware in Next.js.
In the further parts of this series "Building Full-Stack Web Application using Next.js" I would be covering the following:
CRUD operation using Next JS routes and model πΏ
Image Upload using Firebase π₯
Deploying the project globally to make it shareable π
In case of any doubt, you can reach out to me via LinkedIn π¨βπ»
You can also connect with me on various other platforms like Twitter, GitHub
If you liked this Blog, make sure to give it a π and do Follow me on this platform to get notified for my next blogs in this series.
Happy Coding!! π¨βπ»