Mission 3: Be a NoSQL Backend Brainiac -1
Tags: webdev, javascript, programming, beginners
Published: 2023-12-07 20:45:30

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

```js
  {
    timestamps: true,
  },
```

## 11-10 Create User as Student

```ts
  //create a user object
  const userData: Partial<TUser> = {}; // partial interface used from TUser, so use Partial
```
```js
//student.interface.ts
import { Model, Types } from 'mongoose';
  user: Types.ObjectId;
```

```js
//student.model.ts
    user: {
      type: Schema.Types.ObjectId,
      required: [true, 'USER Id is required'],
      unique: true,
      ref: 'User',
    },
```

## 11-11 Fix bugs and setup basic global error handler

```ts
//app.ts
app.use(globalErrorHandeler);
```

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

```

## 11-12 Create not found route & sendResponse utility

```ts
npm i http-status
```

```ts
//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: '',
  });
};

```

```ts
//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;

```

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

```

## 11-13 Create index route and module summary

```ts
//app.ts

// all application routes
app.use('/api/v1', router);
```

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

```

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

12-1 Avoid try-catch repetition , use catchAsync

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2bip3ft8202pi5kkvdpd.png)


```js
===>>> RequestHandler
const createStudent: RequestHandler = async (req, res, next) => {
```


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

## 12-3 Implement validateRequest Middleware


```js
//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;
```



```js
//user.touter.ts
router.post(
  '/create-student',
  validateRequest(createStudentValidationSchema),
  userControllers.createStudent,
);
```



```js
//student.interface.ts
  admissionSemester: Types.ObjectId;
//student.model.ts
    admissionSemester: {
      type: Schema.Types.ObjectId,
      ref: 'AcademicSemester',
    },
```



```js
//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;
};
```
## 12-11 Complete generateStudent() utility

```js
//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;
};

```

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

```

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


```

---

```js
///
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;
```

## 13-8 AppError create

```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;

```
## 13-9 Implement transaction & rollback

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ae5mojpiatzroq0dky3k.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2phpdilw6m6201m5tzvc.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w3hk0iuzgbaiwcmc3ege.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ogtjn5rxbs6tbe4dxwzq.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ry7k8xzbos81h0mw0yzf.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7svjgah6pn6u7jeeuyee.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9nrxhcvk33odg8bwo074.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l85ibrr09flo71vw3wrk.png)


```js

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);
  }
};
```

```js
//=>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);
  }
};
```

## 14-1 What is error handling

## 14-3 How to convert zod error

```js
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
*/
```


```ts
//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;


```


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

```

## 14-7 How to do raw Searching

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/90iv6iiz3w5qkzxscxpw.png)



```js

```


```js

```


```js

```


```js

```


```js

```


```js

```


```js

```


```js

```


```js

```


```js

```


```js

```