Permalink1. Initialize the Project
Begin by creating a new project directory and initializing it with npm init
:
mkdir blog-backend
cd blog-backend
npm init -y
This will create a package.json
file for your project. Afterward, install the necessary dependencies with:
npm install express body-parser dotenv mongoose cookie-parser morgan nodemon bcrypt jsonwebtoken express-validator express-async-handler slugify
These packages include everything we need for creating and securing routes, handling requests, interacting with MongoDB, and managing cookies.
Permalink2. Project Structure
The basic structure of your project should look like this:
blog-backend/
├── config/
│ └── dbConnect.js
├── routes/
│ ├── authRoute.js
│ ├── productRoute.js
│ └── blogRoute.js
├── index.js
├── .env
└── package.json
config/dbConnect.js
: For setting up the MongoDB connection.routes/
: Directory for handling different routes (auth, product, blog).index.js
: The main entry point for your application.
Permalink3. Database Connection
In the config/dbConnect.js
file, establish a connection to MongoDB using Mongoose:
// dbconnect.js
import dotenv from "dotenv";
import mongoose from "mongoose";
dotenv.config();
export const dbConnect = async () => {
try {
console.log(
"🚀 ~ dbConnect ~ process.env.MONGO_URI:",
process.env.MONGO_URI
);
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log("MongoDB connected successfully");
} catch (error) {
console.error("MongoDB connection failed");
}
};
Make sure to create a
.env
file and add your MongoDB connection string:PORT=8000 MONGO_URI=mongodb://localhost:27017/myDatabase
Permalink4. Setting Up the Server
In the index.js
file, we will set up the server using Express.js:
// index.js
import express from "express";
import dotenv from "dotenv";
import bodyParser from "body-parser";
import cookieParser from "cookie-parser";
import { dbConnect } from "./config/dbconnect.js";
dotenv.config();
const app = express();
const port = process.env.PORT || 3000;
// Body parser is used to parse the incoming request bodies in a middleware before you handle it.
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
dbConnect();
Morgan logs HTTP requests.
Body-parser allows us to parse incoming requests in JSON and URL-encoded formats.
Cookie-parser enables us to work with cookies.
Now if we run the server it must need to be notified with “MongoDB connected successfully”. If the database is not connected then you must need to ensure that it should be connected before proceeding to next step.
Permalink5. Creating Model
Create a model with name userModel.js which willl create a table in mongoDB where we will perform the crud operation from controller
// models/userModel.js
import mongoose from "mongoose";
var userSchema = new mongoose.Schema(
{
firstname: {
type: String,
required: true,
trim: true,
min: 3,
max: 20,
},
lastname: {
type: String,
required: true,
trim: true,
min: 3,
max: 20,
},
username: {
type: String,
required: true,
trim: true,
unique: true,
index: true,
lowercase: true,
},
email: {
type: String,
required: true,
trim: true,
unique: true,
lowercase: true,
},
password: {
type: String,
required: true,
},
},
{
timestamps: true,
}
);
export default mongoose.model("User", userSchema);
Permalink6. Creating Controller
Create the route files for authentication, products, and blogs. Each route file will contain the Express routing logic for handling requests.
Example for controller/UserController.js
:
For testing: You canPlay with below code
import { validationResult } from "express-validator";
import expressAsyncHandler from "express-async-handler";
export const createUser = expressAsyncHandler(async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
res.send("User created successfully");
});
Before initiating any CRUD operations, please ensure that your route is functioning correctly. Once confirmed, you can proceed with performing the CRUD operations.
// UserController.js
import { validationResult } from "express-validator";
import User from "../models/userModel.js";
import expressAsyncHandler from "express-async-handler";
import { generateToken } from "../config/jwtToken.js";
export const createUser = async (req, res) => {
try {
const errors = validationResult(req);
console.log("🚀 ~ createUser ~ req:", req.body);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array(),
message: "validation error",
success: false,
});
}
//check if user already exists
const isExist = await User.findOne({
email: req.body.email,
});
console.log("🚀 ~ createUser ~ isExist:", isExist);
if (isExist) {
return res.status(400).json({
message: "User already exists",
success: false,
});
}
const user = await User.create(req.body);
return res.status(201).json({
message: "User created successfully",
success: true,
data: user,
});
} catch (error) {
console.error(
"🚀 ~ file: UserController.js ~ line 13 ~ createUser ~ error",
error
);
}
};
export const updateUser = expressAsyncHandler(async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array(),
message: "validation error",
success: false,
});
}
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
new: true,
});
return res.status(200).json({
message: "User updated successfully",
success: true,
data: user,
});
} catch (errro) {
return res.status(400).json({
message: "User not found",
success: false,
});
}
});
export const getAllUsers = expressAsyncHandler(async (req, res) => {
try {
const users = await User.find();
return res.status(200).json({
message: "Users fetched successfully",
success: true,
data: users,
});
} catch (error) {
console.error("🚀 ~ getAllUsers ~ error", error);
return res.status(500).json({
message: "Server error",
success: false,
});
}
});
export const deleteUser = expressAsyncHandler(async (req, res) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({
message: "User not found",
success: false,
});
}
await User.findByIdAndDelete(req.params.id);
return res.status(200).json({
message: "User deleted successfully",
success: true,
});
} catch (error) {
console.error("🚀 ~ deleteUser ~ error", error);
return res.status(500).json({
message: "Server error",
success: false,
});
}
});
Permalink7. Creating Routes
Create the route files for authentication, products, and blogs. Each route file will contain the Express routing logic for handling requests. Likewise, the controller is imported in to the rooutes so whenever the route is hitted with certain http method it we response the desired output
Example for routes/authRoute.js
:
//authRoute.js
import express from "express";
import { createUser } from "../controller/UserController.js";
const router = express.Router();
router.post("/createUser", createUser);
export { router as authRouter };
You can follow a similar structure for the productRoute.js
and blogRoute.js
.
PermalinkNow then we should define our route in index.js
, as it is the main serving file.
// index.js
import express from "express";
import dotenv from "dotenv";
import bodyParser from "body-parser";
import cookieParser from "cookie-parser";
import { dbConnect } from "./config/dbconnect.js";
import { authRouter } from "./routes/authRoute.js";
dotenv.config();
const app = express();
const port = process.env.PORT || 3000;
// Body parser is used to parse the incoming request bodies in a middleware before you handle it.
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
app.use("/api/auth", authRouter);
dbConnect();
If you want some validation
PermalinkCreate validators/userValidator.js
:
import { body } from "express-validator";
export const validateUser = [
body("firstname")
.trim()
.notEmpty()
.withMessage("Firstname is required")
.isAlpha()
.withMessage("Firstname must only contain letters")
.isLength({ min: 2, max: 50 })
.withMessage("Firstname must be between 2 and 50 characters"),
body("lastname")
.trim()
.notEmpty()
.withMessage("Lastname is required")
.isAlpha()
.withMessage("Lastname must only contain letters")
.isLength({ min: 2, max: 50 })
.withMessage("Lastname must be between 2 and 50 characters"),
body("username")
.trim()
.notEmpty()
.withMessage("Username is required")
.isAlphanumeric()
.withMessage("Username must only contain letters and numbers")
.isLength({ min: 3, max: 30 })
.withMessage("Username must be between 3 and 30 characters"),
body("email")
.trim()
.notEmpty()
.withMessage("Email is required")
.isEmail()
.withMessage("Invalid email format"),
body("password")
.notEmpty()
.withMessage("Password is required")
.isLength({ min: 8 })
.withMessage("Password must be at least 8 characters long")
.matches(/[A-Z]/)
.withMessage("Password must contain at least one uppercase letter")
.matches(/[a-z]/)
.withMessage("Password must contain at least one lowercase letter")
.matches(/[0-9]/)
.withMessage("Password must contain at least one number")
.matches(/[!@#$%^&*]/)
.withMessage("Password must contain at least one special character"),
];
PermalinkAnd then Update authRoute.js
:
import express from "express";
import { createUser } from "../controller/UserController.js";
import { validateUser } from "../validators/userValidator.js";
const router = express.Router();
router.get("/login", createUser);
router.post("/createUser", validateUser, createUser);
export { router as authRouter };
Permalink8. Middleware for authorization
Create a middleware to authenticate the user. It check if the current user trying to perform the operation is autenticated user not.
// middleware/authMiddleware.js
import User from "../models/userModel.js";
import jwt from "jsonwebtoken";
import expressAsyncHandler from "express-async-handler";
export const authMiddleware = expressAsyncHandler(async (req, res, next) => {
let token;
if (req?.headers?.authorization?.startsWith("Bearer")) {
token = req.headers.authorization.split(" ")[1];
if (token) {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded?.id);
req.user = user;
next();
}
} else {
return res.status(401).json({
message: "Not authorized token, Please login again",
success: false,
});
}
});
Permalink9. Now Update the Route
authMiddleware is kept at the createUser Route or you can add it on any route as per your requirement which is middleware to secure action that we need perform.
import express from "express";
import {
createUser,
deleteUser,
getAllUsers,
updateUser,
userLogin,
} from "../controller/UserController.js";
import { authMiddleware } from "../middleware/authMiddleware.js";
const router = express.Router();
router.post("/createUser", authMiddleware, createUser);
router.put("/updateUser/:id", updateUser);
router.get("/get-all", getAllUsers);
router.delete("/delete/:id", deleteUser);
router.post("/login", userLogin);
export { router as authRouter };
Permalink10. Testing the Server
To test if your server is running, start it by running:
npm run server
If everything is set up correctly, you should see:
Server is listening on port http://localhost:5000
PermalinkFor User Login
// login user in UserController.js
import { generateToken } from "../config/jwtToken.js";
export const userLogin = expressAsyncHandler(async (req, res) => {
const { email, password } = req.body;
// findUser variable include all the information of that user with that email.
const findUser = await User.findOne({ email });
const accessToken = generateToken(findUser._id);
// ava store garam token lai cookie ma
res.cookie("accessToken", accessToken, {
httpOnly: true,
maxAge: 72 * 60 * 60 * 1000,
});
if (findUser && (await findUser.isPasswordMatched(password))) {
// res.json(findUser);
res.json({
// ?. syntax is called optional chaining introduced on ecma script in 2020
_id: findUser?._id,
firstname: findUser?.firstname,
lastname: findUser?.lastname,
email: findUser?.email,
mobile: findUser?.mobile,
token: generateToken(findUser?._id),
});
} else {
throw new Error("Invalid Credentials");
}
});
For that we need to generate AccessToken using JWT for Authentication
//config/jwtToken.js
import jwt from "jsonwebtoken";
export const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: "1d" });
};
Also in models/UserModel.js add the following to encrypt and check password
// Yo code chai password lai encrypt garna lai
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
next();
}
const salt = await bcrypt.genSaltSync(10);
this.password = await bcrypt.hash(this.password, salt);
});
// password match vaxa ki nai vanera check garna
userSchema.methods.isPasswordMatched = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
Overall, which will look like this
// models/userModel.js
import mongoose from "mongoose";
import bcrypt from "bcryptjs";
var userSchema = new mongoose.Schema(
{
firstname: {
type: String,
required: true,
trim: true,
min: 3,
max: 20,
},
lastname: {
type: String,
required: true,
trim: true,
min: 3,
max: 20,
},
username: {
type: String,
required: true,
trim: true,
unique: true,
index: true,
lowercase: true,
},
email: {
type: String,
required: true,
trim: true,
unique: true,
lowercase: true,
},
password: {
type: String,
required: true,
},
},
{
timestamps: true,
}
);
// Yo code chai password lai encrypt garna lai
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
next();
}
const salt = await bcrypt.genSaltSync(10);
this.password = await bcrypt.hash(this.password, salt);
});
// password match vaxa ki nai vanera check garna
userSchema.methods.isPasswordMatched = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
export default mongoose.model("User", userSchema);