Day 4: Workshop Guide for Setting Up a Backend with Express and MongoDB

Day 4: Workshop Guide for Setting Up a Backend with Express and MongoDB

1. 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.

2. 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.

3. 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
    

4. 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";
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();
  • 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.

5. 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

// 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);

6. 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:

// 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,
    });
  }
});

// login user
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");
  }
});

7. Creating Routes

Create the route files for authentication, products, and blogs. Each route file will contain the Express routing logic for handling requests.

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.

8. Testing the Server

Create a middleware to authenticate the user. It check if the current user trying to perform the operation is autenticated user not.

// 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,
    });
  }
});

9. 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