This project is a comprehensive school management system built with Express.js and Sequelize ORM, designed to streamline educational institution operations.
The School Management System is a robust web application that provides a centralized platform for managing various aspects of school administration. It offers features for student and teacher management, course scheduling, attendance tracking, and more. Built with modern web technologies, it ensures efficient data handling and a smooth user experience.
Key features include:
- User management (students and teachers)
- Database integration with MySQL
- RESTful API endpoints for CRUD operations
- Secure authentication and authorization
- Data validation and error handling
.
├── package.json
├── pnpm-lock.yaml
├── src
│ ├── app.ts
│ ├── config
│ │ ├── database.ts
│ │ └── seed.ts
│ ├── controllers
│ │ └── UserController.ts
│ ├── data
│ │ └── mockData.ts
│ ├── index.ts
│ ├── models
│ │ ├── Teacher.ts
│ │ └── User.ts
│ └── routes
│ └── UserRoutes.ts
└── tsconfig.json
Key Files:
src/index.ts
: Application entry pointsrc/app.ts
: Express application setup and configurationsrc/config/database.ts
: Database connection configurationsrc/models/
: Sequelize model definitionssrc/controllers/
: Request handlers for different routessrc/routes/
: API route definitionstsconfig.json
: TypeScript configuration file
Prerequisites:
- Node.js (v18 or later)
- pnpm (v7 or later)
- MySQL (v8 or later)
Steps:
-
Clone the repository
-
Navigate to the project directory
-
Install dependencies:
pnpm install
-
Set up environment variables: Create a
.env
file in the root directory and add the following:MYSQL_PASSWORD=your_mysql_password PORT=3000
-
Start the development server:
pnpm start
-
The server will start running on
http://localhost:3000
Database configuration can be modified in src/config/database.ts
. Adjust the following parameters as needed:
database
username
host
port
-
Creating a new user:
POST /users Content-Type: application/json { "firstName": "John", "lastName": "Doe", "email": "[email protected]", "dateOfBirth": "1990-01-01", "gender": "Male", "phoneNo": "1234567890", "address": "123 Main St", "studentId": "S12345", "guardianName": "Jane Doe", "guardianCNIC": "1234567890123", "CNIC": "1234567890123", "class": 10, "enrollmentDate": "2023-09-01" }
-
Retrieving all users:
GET /users
To ensure the quality of the codebase, consider implementing unit tests for controllers and models using a testing framework like Jest.
-
Database Connection Issues:
-
Problem: Unable to connect to the database
-
Error message: "Unable to connect to the database"
-
Diagnostic steps:
- Check if MySQL service is running
- Verify database credentials in
.env
file - Ensure the database exists and is accessible
-
Debug command:
DEBUG=sequelize* pnpm start
-
Expected outcome: Detailed Sequelize logs should appear in the console
-
-
Server Start-up Failure:
-
Problem: Server fails to start
-
Error message: "Error: listen EADDRINUSE: address already in use :::3000"
-
Diagnostic steps:
- Check if another process is using port 3000
- Change the port in the
.env
file if needed
-
Debug command:
lsof -i :3000
-
Expected outcome: List of processes using port 3000, if any
-
- Monitor database query performance using Sequelize's logging feature
- Implement database indexing for frequently queried fields
- Use connection pooling in database configuration for better resource utilization
The School Management System follows a typical client-server architecture with a RESTful API. Here's an overview of the data flow:
- Client sends HTTP request to the server
- Express.js middleware processes the request (parsing, CORS, security headers)
- Router directs the request to the appropriate controller
- Controller interacts with the Sequelize models to perform database operations
- Sequelize models communicate with the MySQL database
- Database returns results to the Sequelize models
- Controller processes the data and sends the HTTP response back to the client
Client <-> Express Middleware <-> Router <-> Controller <-> Sequelize Models <-> MySQL Database
Note: Ensure proper error handling and data validation at each step of the flow to maintain data integrity and provide meaningful feedback to the client.
The School Management System uses the following key infrastructure components:
Database:
- Type: MySQL
- Configuration: Defined in
src/config/database.ts
- Purpose: Stores all application data including user information, courses, and other school-related data
Web Server:
- Type: Express.js
- Configuration: Set up in
src/app.ts
- Purpose: Handles HTTP requests, routes them to appropriate controllers, and sends responses back to clients
ORM:
- Type: Sequelize
- Configuration: Models defined in
src/models/
- Purpose: Provides an abstraction layer for database operations and defines data models
Note: This project does not have a dedicated infrastructure stack defined in CloudFormation, CDK, or Terraform. The infrastructure is primarily managed through code and configuration files within the application itself.
Table,
Column,
Model,
DataType,
ForeignKey,
BelongsTo,
BeforeCreate,
} from 'sequelize-typescript';
import { User } from '@/models/User';
import { Teacher } from '@/models/Teacher';
import * as crypto from 'crypto';
import { Op } from 'sequelize';
type EntityType = 'USER' | 'TEACHER';
interface PasswordResetTokenAttributes {
id?: number;
token: string;
entityId: number;
entityType: EntityType;
expiryDate: Date;
isUsed: boolean;
createdAt?: Date;
updatedAt?: Date;
}
@Table({
tableName: 'password_reset_tokens',
timestamps: true,
})
export class PasswordResetToken
extends Model<PasswordResetTokenAttributes>
implements PasswordResetTokenAttributes
{
@Column({
type: DataType.INTEGER,
primaryKey: true,
autoIncrement: true,
})
id?: number;
@Column({
type: DataType.STRING(100),
allowNull: false,
unique: true,
})
token!: string;
@Column({
type: DataType.INTEGER,
allowNull: false,
})
entityId!: number;
@Column({
type: DataType.ENUM('USER', 'TEACHER'),
allowNull: false,
})
entityType!: EntityType;
@Column({
type: DataType.DATE,
allowNull: false,
})
expiryDate!: Date;
@Column({
type: DataType.BOOLEAN,
defaultValue: false,
})
isUsed!: boolean;
@Column({ type: DataType.DATE })
createdAt?: Date;
@Column({ type: DataType.DATE })
updatedAt?: Date;
@BeforeCreate
static async generateToken(instance: PasswordResetToken) {
instance.token = crypto.randomBytes(32).toString('hex');
instance.expiryDate = new Date(Date.now() + 3600000); // 1 hour from now
}
// Helper methods
static async createToken(entityId: number, entityType: EntityType): Promise<PasswordResetToken> {
// Invalidate any existing tokens
await this.update(
{ isUsed: true },
{
where: {
entityId,
entityType,
isUsed: false,
expiryDate: {
[Op.gt]: new Date(),
},
},
}
);
return await this.create({
entityId,
entityType,
});
}
static async findValidToken(token: string): Promise<PasswordResetToken | null> {
return await this.findOne({
where: {
token,
isUsed: false,
expiryDate: {
[Op.gt]: new Date(),
},
},
});
}
async markAsUsed(): Promise<void> {
this.isUsed = true;
await this.save();
}
isExpired(): boolean {
return new Date() > this.expiryDate;
}
isValid(): boolean {
return !this.isUsed && !this.isExpired();
}
}
// Password Reset Service
export class PasswordResetService {
static async generateResetToken(
email: string,
entityType: EntityType
): Promise<string> {
let entity;
if (entityType === 'USER') {
entity = await User.findOne({ where: { email } });
} else {
entity = await Teacher.findOne({ where: { email } });
}
if (!entity) {
throw new Error(`${entityType.toLowerCase()} not found`);
}
const resetToken = await PasswordResetToken.createToken(entity.id, entityType);
return resetToken.token;
}
static async resetPassword(
token: string,
newPassword: string
): Promise<boolean> {
const resetToken = await PasswordResetToken.findValidToken(token);
if (!resetToken || !resetToken.isValid()) {
throw new Error('Invalid or expired token');
}
let entity;
if (resetToken.entityType === 'USER') {
entity = await User.findByPk(resetToken.entityId);
} else {
entity = await Teacher.findByPk(resetToken.entityId);
}
if (!entity) {
throw new Error(`${resetToken.entityType.toLowerCase()} not found`);
}
// Update password
entity.password = newPassword; // Assuming you have password hashing middleware
await entity.save();
// Mark token as used
await resetToken.markAsUsed();
return true;
}
static async verifyToken(token: string): Promise<boolean> {
const resetToken = await PasswordResetToken.findValidToken(token);
return !!resetToken && resetToken.isValid();
}
}
// Controller Example
export class PasswordController {
async requestReset(req: Request, res: Response) {
try {
const { email, entityType } = req.body;
if (!['USER', 'TEACHER'].includes(entityType)) {
throw new Error('Invalid entity type');
}
const token = await PasswordResetService.generateResetToken(
email,
entityType as EntityType
);
// Send email with reset link
await sendResetEmail(email, token, entityType);
res.json({
success: true,
message: 'Password reset instructions sent to email'
});
} catch (error) {
res.status(400).json({
success: false,
message: error.message
});
}
}
async resetPassword(req: Request, res: Response) {
try {
const { token, newPassword } = req.body;
await PasswordResetService.resetPassword(token, newPassword);
res.json({
success: true,
message: 'Password successfully reset'
});
} catch (error) {
res.status(400).json({
success: false,
message: error.message
});
}
}
async verifyToken(req: Request, res: Response) {
try {
const { token } = req.query;
const isValid = await PasswordResetService.verifyToken(token as string);
res.json({
success: true,
isValid
});
} catch (error) {
res.status(400).json({
success: false,
message: error.message
});
}
}
}
// Email helper function
async function sendResetEmail(email: string, token: string, entityType: EntityType) {
const resetLink = `${process.env.FRONTEND_URL}/reset-password?token=${token}`;
// Implement your email sending logic here
// You might want to use different email templates for users and teachers
const template = entityType === 'USER'
? 'user-reset-password'
: 'teacher-reset-password';
// Send email using your preferred email service
// await emailService.send({
// to: email,
// template,
// context: { resetLink }
// });
}