GitHub link: https://github.com/Apollo-Level2-Web-Dev/first-project/tree/first-project-3
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,
},
11-10 Create User as Student
//create a user object
const userData: Partial<TUser> = {}; // partial interface used from TUser, so use Partial
//student.interface.ts
import { Model, Types } from 'mongoose';
user: Types.ObjectId;
//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
//app.ts
app.use(globalErrorHandeler);
//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
npm i http-status
//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: '',
});
};
//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;
//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
//app.ts
// all application routes
app.use('/api/v1', router);
//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
===>>> RequestHandler
const createStudent: RequestHandler = async (req, res, next) => {
//===> 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
//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;
//user.touter.ts
router.post(
'/create-student',
validateRequest(createStudentValidationSchema),
userControllers.createStudent,
);
//student.interface.ts
admissionSemester: Types.ObjectId;
//student.model.ts
admissionSemester: {
type: Schema.Types.ObjectId,
ref: 'AcademicSemester',
},
//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
//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;
};
//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);
//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,
};
///
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
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
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);
}
};
//=>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
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
*/
//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;
//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
Top comments (0)