DEV Community

Kazi Abdur Rakib
Kazi Abdur Rakib

Posted on • Edited on

Mission 3: Be a NoSQL Backend Brainiac -1

GitHub link: https://github.com/Apollo-Level2-Web-Dev/first-project/tree/first-project-3

ER Diagram: https://lucid.app/lucidchart/e267b342-c202-4628-b55f-24ce0c3dce7a/edit?viewport_loc=-296%2C220%2C2020%2C1001%2C0_0&invitationId=inv_ba107934-aea8-4365-8128-b49a292eb954

Module-11: Building PH University Management System Part-1

11-1 What is SDLC, How will we start our project

11-7 Create user interface ,model and validation

  {
    timestamps: true,
  },
Enter fullscreen mode Exit fullscreen mode

11-10 Create User as Student

  //create a user object
  const userData: Partial<TUser> = {}; // partial interface used from TUser, so use Partial
Enter fullscreen mode Exit fullscreen mode
//student.interface.ts
import { Model, Types } from 'mongoose';
  user: Types.ObjectId;
Enter fullscreen mode Exit fullscreen mode
//student.model.ts
    user: {
      type: Schema.Types.ObjectId,
      required: [true, 'USER Id is required'],
      unique: true,
      ref: 'User',
    },
Enter fullscreen mode Exit fullscreen mode

11-11 Fix bugs and setup basic global error handler

//app.ts
app.use(globalErrorHandeler);
Enter fullscreen mode Exit fullscreen mode
//globalErrorHandeler.ts
/* eslint-disable @typescript-eslint/no-unused-vars */

/* eslint-disable no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Response, Request, NextFunction } from 'express';

export const globalErrorHandeler = (
  err: any,
  req: Request,
  res: Response,
  next: NextFunction,
) => {
  const statusCode = 500;
  const message = err.message || 'Something went wrong';

  return res.status(statusCode).json({
    success: false,
    message,
    error: err,
  });
};

Enter fullscreen mode Exit fullscreen mode

11-12 Create not found route & sendResponse utility

npm i http-status
Enter fullscreen mode Exit fullscreen mode
//notFound.ts
/* eslint-disable @typescript-eslint/no-unused-vars */

/* eslint-disable no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Response, Request, NextFunction } from 'express';
import httpStatus from 'http-status';

export const notFound = (req: Request, res: Response, next: NextFunction) => {
  return res.status(httpStatus.NOT_FOUND).json({
    success: false,
    message: 'API NOT FOUNDS !!',
    error: '',
  });
};

Enter fullscreen mode Exit fullscreen mode
//sendResponse.ts
import { Response } from 'express';

type ResponseType<T> = {
  statusCode: number;
  success: boolean;
  message?: string;
  data: T;
};

const sendResponse = <T>(res: Response, data: ResponseType<T>) => {
  res.status(data?.statusCode).json({
    success: data.success,
    message: data.message,
    data: data.data,
  });
};

export default sendResponse;

Enter fullscreen mode Exit fullscreen mode
//user.controller.ts
    sendResponse(res, {
      statusCode: httpStatus.OK,
      success: true,
      message: 'createStudent is created successgully',
      data: result,
    });

Enter fullscreen mode Exit fullscreen mode

11-13 Create index route and module summary

//app.ts

// all application routes
app.use('/api/v1', router);
Enter fullscreen mode Exit fullscreen mode
//dynmaic index create
//routes/index.ts
import Router from 'express';
const router = Router();

const moduleRoutes = [
  {
    path: '/users',
    route: userRoutes,
  },
  {
    path: '/students',
    route: StudentRoutes,
  },
];

moduleRoutes.forEach((singleroute) =>
  router.use(singleroute.path, singleroute.route),
);

Enter fullscreen mode Exit fullscreen mode

Module-11: Building PH University Management System Part-2

12-1 Avoid try-catch repetition , use catchAsync

Image description

===>>> RequestHandler
const createStudent: RequestHandler = async (req, res, next) => {
Enter fullscreen mode Exit fullscreen mode
//===> old system
const getAllStudents: RequestHandler = catchAsync(async (req, res, next) => {
  try {
    const result = await StudentServices.getAllStudentsFromBD();
    sendResponse(res, {
      statusCode: httpStatus.OK,
      success: true,
      message: 'getAllStudents is are retrived successgully',
      data: result,
    });
  } catch (err) {
    next(err);
  }
});

//=======================================

//===> new formula
const catchAsync = (asyncFn: RequestHandler) => {
  return (req: Request, res: Response, next: NextFunction) => {
    Promise.resolve(asyncFn(req, res, next)).catch((err) => next(err));
  };
};

const getAllStudents = catchAsync(async (req, res, next) => {
    const result = await StudentServices.getAllStudentsFromBD();
    sendResponse(res, {
      statusCode: httpStatus.OK,
      success: true,
      message: 'getAllStudents is are retrived successgully',
      data: result,
    });
});
Enter fullscreen mode Exit fullscreen mode

12-3 Implement validateRequest Middleware

//validateRequest.ts
import { AnyZodObject } from 'zod';
import { NextFunction, Request, Response } from 'express';

const validateRequest = (Schema: AnyZodObject) => {
  return async (req: Request, res: Response, next: NextFunction) => {
    //validate
    try {
      await Schema.parseAsync({
        body: req.body,
      });
      next();
    } catch (err) {
      next(err);
    }
  };
};

export default validateRequest;
Enter fullscreen mode Exit fullscreen mode
//user.touter.ts
router.post(
  '/create-student',
  validateRequest(createStudentValidationSchema),
  userControllers.createStudent,
);
Enter fullscreen mode Exit fullscreen mode
//student.interface.ts
  admissionSemester: Types.ObjectId;
//student.model.ts
    admissionSemester: {
      type: Schema.Types.ObjectId,
      ref: 'AcademicSemester',
    },
Enter fullscreen mode Exit fullscreen mode
//user.utils.ts
import { TAcademicSemester } from '../academicSemester/academicSemester.interface';
import { User } from './user.model';

const findLastStudentId = async () => {
  const lastStudent = await User.findOne(
    {
      role: 'student',
    },
    {
      id: 1,
      _id: 0,
    },
  )
    .sort({
      createdAt: -1,
    })
    .lean();

  return lastStudent?.id ? lastStudent.id : undefined;
};

export const generateStudentId = async (payload: TAcademicSemester) => {
  let currentId = (0).toString();
  const lastStudentId = await findLastStudentId();

  const lastStudentSemesterCode = lastStudentId?.substring(4, 6);
  const lastStudentYear = lastStudentId?.substring(0, 4);

  const currentSemesterCode = payload.code;
  const currentYear = payload.year;

  if (
    lastStudentId &&
    lastStudentSemesterCode === currentSemesterCode &&
    lastStudentYear === currentYear
  ) {
    currentId = lastStudentId.substring(6);
  }

  let incrementId = (Number(currentId) + 1).toString().padStart(4, '0');

  incrementId = `${payload.year}${payload.code}${incrementId}`;

  return incrementId;
};

// Faculty ID
export const findLastFacultyId = async () => {
  const lastFaculty = await User.findOne(
    {
      role: 'faculty',
    },
    {
      id: 1,
      _id: 0,
    },
  )
    .sort({
      createdAt: -1,
    })
    .lean();

  return lastFaculty?.id ? lastFaculty.id.substring(2) : undefined;
};

export const generateFacultyId = async () => {
  let currentId = (0).toString();
  const lastFacultyId = await findLastFacultyId();

  if (lastFacultyId) {
    currentId = lastFacultyId.substring(2);
  }

  let incrementId = (Number(currentId) + 1).toString().padStart(4, '0');

  incrementId = `F-${incrementId}`;

  return incrementId;
};

// Admin ID
export const findLastAdminId = async () => {
  const lastAdmin = await User.findOne(
    {
      role: 'admin',
    },
    {
      id: 1,
      _id: 0,
    },
  )
    .sort({
      createdAt: -1,
    })
    .lean();

  return lastAdmin?.id ? lastAdmin.id.substring(2) : undefined;
};

export const generateAdminId = async () => {
  let currentId = (0).toString();
  const lastAdminId = await findLastAdminId();

  if (lastAdminId) {
    currentId = lastAdminId.substring(2);
  }

  let incrementId = (Number(currentId) + 1).toString().padStart(4, '0');

  incrementId = `A-${incrementId}`;
  return incrementId;
};
Enter fullscreen mode Exit fullscreen mode

12-11 Complete generateStudent() utility

//user.utlis.ts
import { TAcademicSemester } from '../academicSemester/academicSemester.interface';
import User from './user.model';

//findlast student ID
const findLastStudentId = async () => {
  const lastStudent = await User.findOne(
    {
      role: 'student',
    },
    {
      id: 1,
      _id: 0,
    },
  )
    .sort({
      createdAt: -1,
    })
    .lean();

  // eslint-disable-next-line no-undefined
  return lastStudent?.id.substring(6) ?? undefined; // Assuming id is a property of lastStudent
};

//generate student ID
export const generateStudentId = async (payload: TAcademicSemester) => {
  const currentId = (await findLastStudentId()) || (0).toString();
  let incrementId = (Number(currentId) + 1).toString().padStart(4, '0');

  incrementId = `${payload.year}${payload.code}${incrementId}`;
  return incrementId;
};

Enter fullscreen mode Exit fullscreen mode
//user.service.ts
  //set student role
  userData.role = 'student';

  //find academic semester info
  const admissionSemesterID = await AcademicSemester.findById(
    payload.admissionSemester,
  );

  // //set manually generated id
  if (admissionSemesterID != null) {
    userData.id = await generateStudentId(admissionSemesterID);
  }
  // userData.id = '202310002';
  //create a user
  const newUser = await User.create(userData);

Enter fullscreen mode Exit fullscreen mode
//user.service.ts=> full code
import config from '../../config';
import { AcademicSemester } from '../academicSemester/academicSemester.model';
// import { TAcademicSemester } from '../academicSemester/academicSemester.interface';
import { TStudent } from '../student/student.interface';
import { Student } from '../student/student.model';
import { TUser } from './user.interface';
import User from './user.model';
import { generateStudentId } from './user.utils';

const createStudentIntoDB = async (password: string, payload: TStudent) => {
  //create a user object
  const userData: Partial<TUser> = {};

  userData.password = password || (config.default_pass as string);

  //set student role
  userData.role = 'student';

  //find academic semester info
  const admissionSemesterID = await AcademicSemester.findById(
    payload.admissionSemester,
  );

  // //set manually generated id
  if (admissionSemesterID != null) {
    userData.id = await generateStudentId(admissionSemesterID);
  }
  // userData.id = '202310002';
  //create a user
  const newUser = await User.create(userData);

  //crate a student
  if (Object.keys(newUser).length) {
    //set id, _id as a user
    payload.id = newUser.id;
    payload.user = newUser._id; //reference _id

    //create a new student
    const newStudent = await Student.create(payload);
    return newStudent;
  }
  //   return newUser;
};

export const UserServices = {
  createStudentIntoDB,
};


Enter fullscreen mode Exit fullscreen mode

///
import { NextFunction, Request, RequestHandler, Response } from 'express';

const catchAsync = (asyncFn: RequestHandler) => {
  return (req: Request, res: Response, next: NextFunction) => {
    Promise.resolve(asyncFn(req, res, next)).catch((err) => next(err));
  };
};

export default catchAsync;
Enter fullscreen mode Exit fullscreen mode

13-8 AppError create

class AppError extends Error {
  public statusCode: number;

  constructor(statusCode: number, message: string, stack = '') {
    super(message);
    this.statusCode = statusCode;

    if (stack) {
      this.stack = stack;
    } else {
      Error.captureStackTrace(this, this.constructor);
    }
  }
}

export default AppError;

Enter fullscreen mode Exit fullscreen mode

13-9 Implement transaction & rollback

Image description

Image description

Image description

Image description

Image description

Image description

Image description

Image description


const session = await mongoose.startSession();
try {
  session.startTransaction();
  const newUser = await User.create([userData], { session }); // array
  await session.commitTransaction();
  await session.endSession();
} catch (err: any) {
  await session.abortTransaction();
  await session.endSession();
  throw new AppError(httpStatus.NOT_FOUND, err);
}

//==================================
//==>src/app/modules/user/user.service.ts
const createStudentIntoDB = async (password: string, payload: TStudent) => {
  // console.log(payload);
  //create a user object
  const userData: Partial<TUser> = {};

  userData.password = password || (config.default_pass as string);

  //set student role
  userData.role = 'student';

  //find academic semester info
  const admissionSemesterID = await AcademicSemester.findById(
    payload.admissionSemester,
  );

  const session = await mongoose.startSession();

  try {
    session.startTransaction();
    //set  generated id
    // userData.id = '202310002';
    if (admissionSemesterID != null) {
      userData.id = await generateStudentId(admissionSemesterID);
    }

    // create a user (transaction-1)
    const newUser = await User.create([userData], { session }); // array

    //create a student
    if (!newUser.length) {
      throw new AppError(httpStatus.BAD_REQUEST, 'Failed to create user');
    }
    // set id , _id as user
    payload.id = newUser[0].id;
    payload.user = newUser[0]._id; //reference _id

    // create a student (transaction-2)

    const newStudent = await Student.create([payload], { session });

    if (!newStudent.length) {
      throw new AppError(httpStatus.BAD_REQUEST, 'Failed to create student');
    }

    await session.commitTransaction();
    await session.endSession();

    return newStudent;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
  } catch (err: any) {
    await session.abortTransaction();
    await session.endSession();
    throw new AppError(httpStatus.NOT_FOUND, err);
  }
};
Enter fullscreen mode Exit fullscreen mode
//=>src/app/modules/student/student.service.ts
const deleteStudentFromDB = async (id: string) => {
  const session = await mongoose.startSession();

  try {
    session.startTransaction();

    const isUserExist = await Student.isUserExists(id);
    if (!isUserExist) {
      throw new AppError(httpStatus.BAD_REQUEST, 'User does not exist');
      return; // Handle the case where the user does not exist
    }

    const deletedStudent = await Student.findOneAndUpdate(
      { id },
      { isDeleted: true },
      { new: true, session },
    );

    if (!deletedStudent) {
      throw new AppError(httpStatus.BAD_REQUEST, 'Failed to delete student');
    }

    const deletedUser = await User.findOneAndUpdate(
      { id },
      { isDeleted: true },
      { new: true, session },
    );

    if (!deletedUser) {
      throw new AppError(httpStatus.BAD_REQUEST, 'Failed to delete user');
    }

    await session.commitTransaction();
    await session.endSession();

    return deletedStudent;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
  } catch (err: any) {
    await session.abortTransaction();
    await session.endSession();
    throw new Error(err);
  }
};
Enter fullscreen mode Exit fullscreen mode

14-1 What is error handling

14-3 How to convert zod error

export const globalErrorHandeler: ErrorRequestHandler = (
  err,
  req,
  res,
  next,
) => {
  let statusCode = err.statusCode || 500;
  let message = err.message || 'Something went wrong';

   type TErrorSources = {
    path: string | number;
    message: string;
  }[];

  let errorSources: TErrorSources = [
    {
      path: '',
      message: 'something went wrong',
    },
  ];

  const handlezoderro = (err: ZodError) => {
    const errorSources: TErrorSources = err.issues.map((issue: ZodIssue) => {
      return {
        path: issue?.path[issue.path.length - 1],
        message: issue.message,
      };
    });

    const statusCode = 400;
    return {
      statusCode,
      message: ' validtion error',
      errorSources,
    };
  };

  if (err instanceof ZodError) {
    const simpliedError = handlezoderro(err);
    statusCode = simpliedError?.statusCode;
    message = simpliedError?.message;
    errorSources = simpliedError?.errorSources;
  }

  return res.status(statusCode).json({
    success: false,
    message,
    errorSources,
    stack: config.NODE_ENV === 'development' ? err?.stack : null,
  });
};

//pattern
/*
success
message
errorSources:[
  path:'',
  message:''
]
stack
*/
Enter fullscreen mode Exit fullscreen mode
//src/app/errors/AppError.ts
class AppError extends Error {
  public statusCode: number;

  constructor(statusCode: number, message: string, stack = '') {
    super(message);
    this.statusCode = statusCode;

    if (stack) {
      this.stack = stack;
    } else {
      Error.captureStackTrace(this, this.constructor);
    }
  }
}

export default AppError;

//---

//src/app/errors/handleCastErrorError.ts
import { CastError } from 'mongoose';
import { TErrorSources, TGenericErrorResponse } from '../interface/error';

const handleCastErrorError = (err: CastError): TGenericErrorResponse => {
  const errorSources: TErrorSources = [
    {
      path: err?.path,
      message: err?.message,
    },
  ];

  const statusCode = 400;
  return {
    statusCode,
    message: 'invalid ID',
    errorSources,
  };
};

export default handleCastErrorError;

//---

//src/app/errors/handleDuplicateError.ts
import { TErrorSources, TGenericErrorResponse } from '../interface/error';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handleDuplicateError = (err: any): TGenericErrorResponse => {
  const duplicateValue = (err.message.match(/"([^"]+)"/) || [])[1];

  const errorSources: TErrorSources = [
    {
      path: err?.keyValue,
      message: `${duplicateValue} is already exists`,
    },
  ];

  const statusCode = 400;
  return {
    statusCode,
    message: 'Duplicate Value found',
    errorSources,
  };
};

export default handleDuplicateError;


//---
//src/app/errors/handleValidationError.ts
import mongoose from 'mongoose';
import { TErrorSources, TGenericErrorResponse } from '../interface/error';

const handleValidationError = (
  err: mongoose.Error.ValidationError,
): TGenericErrorResponse => {
  const errorSources: TErrorSources = Object.values(err.errors).map(
    (val: mongoose.Error.ValidatorError | mongoose.Error.CastError) => {
      return {
        path: val?.path,
        message: val?.message,
      };
    },
  );

  const statusCode = 400;

  return {
    statusCode,
    message: 'Validation Error',
    errorSources,
  };
};
export default handleValidationError;

//---
//src/app/errors/handleZodError.ts
import { ZodError, ZodIssue } from 'zod';
import { TErrorSources, TGenericErrorResponse } from '../interface/error';

const handleZodError = (err: ZodError): TGenericErrorResponse => {
  const errorSources: TErrorSources = err.issues.map((issue: ZodIssue) => {
    return {
      path: issue?.path[issue.path.length - 1],
      message: issue.message,
    };
  });

  const statusCode = 400;

  return {
    statusCode,
    message: 'Validation Error',
    errorSources,
  };
};

export default handleZodError;


Enter fullscreen mode Exit fullscreen mode
//src/app/middlewares/globalErrorhandeler.ts
export const globalErrorHandeler: ErrorRequestHandler = (
  err,
  req,
  res,
  next,
) => {
  let statusCode = 500;
  let message = 'Something went wrong';

  let errorSources: TErrorSources = [
    {
      path: '',
      message: 'something went wrong',
    },
  ];

  if (err instanceof ZodError) {
    const simpliedError = handleZodError(err);
    statusCode = simpliedError?.statusCode;
    message = simpliedError?.message;
    errorSources = simpliedError?.errorSources;
  } else if (err?.name === 'ValidationError') {
    const simpliedError = handleValidationError(err);
    statusCode = simpliedError?.statusCode;
    message = simpliedError?.message;
    errorSources = simpliedError?.errorSources;
  } else if (err?.name === 'CastError') {
    const simpliedError = handleCastErrorError(err);
    statusCode = simpliedError?.statusCode;
    message = simpliedError?.message;
    errorSources = simpliedError?.errorSources;
  } else if (err?.code === 11000) {
    const simplifiedError = handleDuplicateError(err);
    statusCode = simplifiedError?.statusCode;
    message = simplifiedError?.message;
    errorSources = simplifiedError?.errorSources;
  } else if (err instanceof AppError) {
    statusCode = err?.statusCode;
    message = err?.message;
    errorSources = [
      {
        path: 'appError path',
        message: err?.message,
      },
    ];
  } else if (err instanceof Error) {
    message = err?.message;
    errorSources = [
      {
        path: 'Error path',
        message: err?.message,
      },
    ];
  }

  //ultimate return
  return res.status(statusCode).json({
    success: false,
    message,
    errorSources,
    err,
    stack: config.NODE_ENV === 'development' ? err?.stack : null,
  });
};

Enter fullscreen mode Exit fullscreen mode

14-7 How to do raw Searching

Image description


Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

Top comments (0)