DEV Community

Nadim Chowdhury
Nadim Chowdhury

Posted on • Updated on

Detail implementation of Academic Management

Below is an implementation for Academic Management, including features for class and subject management, timetable creation and management, and attendance tracking and reporting using Next.js, NestJS, and GraphQL.

Backend (NestJS)

1. Entities

Class Entity:

// class.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Teacher } from './teacher.entity';

@Entity()
export class Class {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @ManyToOne(() => Teacher, (teacher) => teacher.classes)
  teacher: Teacher;
}
Enter fullscreen mode Exit fullscreen mode

Subject Entity:

// subject.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Class } from './class.entity';

@Entity()
export class Subject {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @ManyToOne(() => Class, (cls) => cls.subjects)
  class: Class;
}
Enter fullscreen mode Exit fullscreen mode

Timetable Entity:

// timetable.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Class } from './class.entity';
import { Subject } from './subject.entity';

@Entity()
export class Timetable {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => Class, (cls) => cls.timetables)
  class: Class;

  @ManyToOne(() => Subject, (subject) => subject.timetables)
  subject: Subject;

  @Column()
  day: string;

  @Column()
  startTime: string;

  @Column()
  endTime: string;
}
Enter fullscreen mode Exit fullscreen mode

Attendance Entity:

// attendance.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { Class } from './class.entity';
import { Student } from './student.entity';

@Entity()
export class Attendance {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => Class, (cls) => cls.attendances)
  class: Class;

  @ManyToOne(() => Student, (student) => student.attendances)
  student: Student;

  @Column()
  date: string;

  @Column()
  status: string;  // Present or Absent
}
Enter fullscreen mode Exit fullscreen mode

2. Services

Class Service:

// class.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Class } from './class.entity';

@Injectable()
export class ClassService {
  constructor(
    @InjectRepository(Class)
    private classRepository: Repository<Class>,
  ) {}

  findAll(): Promise<Class[]> {
    return this.classRepository.find();
  }

  findOne(id: number): Promise<Class> {
    return this.classRepository.findOne(id);
  }

  create(name: string, teacherId: number): Promise<Class> {
    const newClass = this.classRepository.create({ name, teacher: { id: teacherId } });
    return this.classRepository.save(newClass);
  }
}
Enter fullscreen mode Exit fullscreen mode

Subject Service:

// subject.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Subject } from './subject.entity';

@Injectable()
export class SubjectService {
  constructor(
    @InjectRepository(Subject)
    private subjectRepository: Repository<Subject>,
  ) {}

  findAll(): Promise<Subject[]> {
    return this.subjectRepository.find();
  }

  findOne(id: number): Promise<Subject> {
    return this.subjectRepository.findOne(id);
  }

  create(name: string, classId: number): Promise<Subject> {
    const newSubject = this.subjectRepository.create({ name, class: { id: classId } });
    return this.subjectRepository.save(newSubject);
  }
}
Enter fullscreen mode Exit fullscreen mode

Timetable Service:

// timetable.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Timetable } from './timetable.entity';

@Injectable()
export class TimetableService {
  constructor(
    @InjectRepository(Timetable)
    private timetableRepository: Repository<Timetable>,
  ) {}

  findAll(): Promise<Timetable[]> {
    return this.timetableRepository.find();
  }

  create(classId: number, subjectId: number, day: string, startTime: string, endTime: string): Promise<Timetable> {
    const newTimetable = this.timetableRepository.create({ class: { id: classId }, subject: { id: subjectId }, day, startTime, endTime });
    return this.timetableRepository.save(newTimetable);
  }
}
Enter fullscreen mode Exit fullscreen mode

Attendance Service:

// attendance.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Attendance } from './attendance.entity';

@Injectable()
export class AttendanceService {
  constructor(
    @InjectRepository(Attendance)
    private attendanceRepository: Repository<Attendance>,
  ) {}

  findAll(): Promise<Attendance[]> {
    return this.attendanceRepository.find();
  }

  create(classId: number, studentId: number, date: string, status: string): Promise<Attendance> {
    const newAttendance = this.attendanceRepository.create({ class: { id: classId }, student: { id: studentId }, date, status });
    return this.attendanceRepository.save(newAttendance);
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Resolvers

Class Resolver:

// class.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { ClassService } from './class.service';
import { Class } from './class.entity';

@Resolver(() => Class)
export class ClassResolver {
  constructor(private classService: ClassService) {}

  @Query(() => [Class])
  async classes() {
    return this.classService.findAll();
  }

  @Mutation(() => Class)
  async createClass(@Args('name') name: string, @Args('teacherId') teacherId: number) {
    return this.classService.create(name, teacherId);
  }
}
Enter fullscreen mode Exit fullscreen mode

Subject Resolver:

// subject.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { SubjectService } from './subject.service';
import { Subject } from './subject.entity';

@Resolver(() => Subject)
export class SubjectResolver {
  constructor(private subjectService: SubjectService) {}

  @Query(() => [Subject])
  async subjects() {
    return this.subjectService.findAll();
  }

  @Mutation(() => Subject)
  async createSubject(@Args('name') name: string, @Args('classId') classId: number) {
    return this.subjectService.create(name, classId);
  }
}
Enter fullscreen mode Exit fullscreen mode

Timetable Resolver:

// timetable.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { TimetableService } from './timetable.service';
import { Timetable } from './timetable.entity';

@Resolver(() => Timetable)
export class TimetableResolver {
  constructor(private timetableService: TimetableService) {}

  @Query(() => [Timetable])
  async timetables() {
    return this.timetableService.findAll();
  }

  @Mutation(() => Timetable)
  async createTimetable(
    @Args('classId') classId: number,
    @Args('subjectId') subjectId: number,
    @Args('day') day: string,
    @Args('startTime') startTime: string,
    @Args('endTime') endTime: string,
  ) {
    return this.timetableService.create(classId, subjectId, day, startTime, endTime);
  }
}
Enter fullscreen mode Exit fullscreen mode

Attendance Resolver:

// attendance.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { AttendanceService } from './attendance.service';
import { Attendance } from './attendance.entity';

@Resolver(() => Attendance)
export class AttendanceResolver {
  constructor(private attendanceService: AttendanceService) {}

  @Query(() => [Attendance])
  async attendances() {
    return this.attendanceService.findAll();
  }

  @Mutation(() => Attendance)
  async markAttendance(
    @Args('classId') classId: number,
    @Args('studentId') studentId: number,
    @Args('date') date: string,
    @Args('status') status: string,
  ) {
    return this.attendanceService.create(classId, studentId, date, status);
  }
}
Enter fullscreen mode Exit fullscreen mode

Frontend (Next.js)

1. Apollo Client Setup

// apollo-client.js
import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'http://localhost:3000/graphql',
  cache: new InMemoryCache(),
});

export default client;
Enter fullscreen mode Exit fullscreen mode

2. Class Management Page

// pages/classes.js
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_CLASSES = gql`
  query GetClasses {
    classes {
      id
      name
      teacher {
        name
      }
    }
  }
`;

const CREATE_CLASS = gql`
  mutation CreateClass($name: String!, $teacherId: Int

!) {
    createClass(name: $name, teacherId: $teacherId) {
      id
      name
    }
  }
`;

export default function Classes() {
  const { loading, error, data } = useQuery(GET_CLASSES);
  const [createClass] = useMutation(CREATE_CLASS);
  const [name, setName] = useState('');
  const [teacherId, setTeacherId] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    await createClass({ variables: { name, teacherId: parseInt(teacherId) } });
    setName('');
    setTeacherId('');
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Classes</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Class Name"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
        <input
          type="number"
          placeholder="Teacher ID"
          value={teacherId}
          onChange={(e) => setTeacherId(e.target.value)}
        />
        <button type="submit">Create Class</button>
      </form>
      <ul>
        {data.classes.map((cls) => (
          <li key={cls.id}>
            {cls.name} - {cls.teacher.name}
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

3. Subject Management Page

// pages/subjects.js
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_SUBJECTS = gql`
  query GetSubjects {
    subjects {
      id
      name
      class {
        name
      }
    }
  }
`;

const CREATE_SUBJECT = gql`
  mutation CreateSubject($name: String!, $classId: Int!) {
    createSubject(name: $name, classId: $classId) {
      id
      name
    }
  }
`;

export default function Subjects() {
  const { loading, error, data } = useQuery(GET_SUBJECTS);
  const [createSubject] = useMutation(CREATE_SUBJECT);
  const [name, setName] = useState('');
  const [classId, setClassId] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    await createSubject({ variables: { name, classId: parseInt(classId) } });
    setName('');
    setClassId('');
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Subjects</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Subject Name"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
        <input
          type="number"
          placeholder="Class ID"
          value={classId}
          onChange={(e) => setClassId(e.target.value)}
        />
        <button type="submit">Create Subject</button>
      </form>
      <ul>
        {data.subjects.map((subject) => (
          <li key={subject.id}>
            {subject.name} - {subject.class.name}
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

4. Timetable Management Page

// pages/timetable.js
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_TIMETABLES = gql`
  query GetTimetables {
    timetables {
      id
      class {
        name
      }
      subject {
        name
      }
      day
      startTime
      endTime
    }
  }
`;

const CREATE_TIMETABLE = gql`
  mutation CreateTimetable($classId: Int!, $subjectId: Int!, $day: String!, $startTime: String!, $endTime: String!) {
    createTimetable(classId: $classId, subjectId: $subjectId, day: $day, startTime: $startTime, endTime: $endTime) {
      id
      day
      startTime
      endTime
    }
  }
`;

export default function Timetable() {
  const { loading, error, data } = useQuery(GET_TIMETABLES);
  const [createTimetable] = useMutation(CREATE_TIMETABLE);
  const [classId, setClassId] = useState('');
  const [subjectId, setSubjectId] = useState('');
  const [day, setDay] = useState('');
  const [startTime, setStartTime] = useState('');
  const [endTime, setEndTime] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    await createTimetable({ variables: { classId: parseInt(classId), subjectId: parseInt(subjectId), day, startTime, endTime } });
    setClassId('');
    setSubjectId('');
    setDay('');
    setStartTime('');
    setEndTime('');
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Timetable</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="number"
          placeholder="Class ID"
          value={classId}
          onChange={(e) => setClassId(e.target.value)}
        />
        <input
          type="number"
          placeholder="Subject ID"
          value={subjectId}
          onChange={(e) => setSubjectId(e.target.value)}
        />
        <input
          type="text"
          placeholder="Day"
          value={day}
          onChange={(e) => setDay(e.target.value)}
        />
        <input
          type="text"
          placeholder="Start Time"
          value={startTime}
          onChange={(e) => setStartTime(e.target.value)}
        />
        <input
          type="text"
          placeholder="End Time"
          value={endTime}
          onChange={(e) => setEndTime(e.target.value)}
        />
        <button type="submit">Create Timetable</button>
      </form>
      <ul>
        {data.timetables.map((tt) => (
          <li key={tt.id}>
            {tt.class.name} - {tt.subject.name} - {tt.day} - {tt.startTime} - {tt.endTime}
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

5. Attendance Tracking Page

// pages/attendance.js
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_ATTENDANCES = gql`
  query GetAttendances {
    attendances {
      id
      class {
        name
      }
      student {
        name
      }
      date
      status
    }
  }
`;

const MARK_ATTENDANCE = gql`
  mutation MarkAttendance($classId: Int!, $studentId: Int!, $date: String!, $status: String!) {
    markAttendance(classId: $classId, studentId: $studentId, date: $date, status: $status) {
      id
      date
      status
    }
  }
`;

export default function Attendance() {
  const { loading, error, data } = useQuery(GET_ATTENDANCES);
  const [markAttendance] = useMutation(MARK_ATTENDANCE);
  const [classId, setClassId] = useState('');
  const [studentId, setStudentId] = useState('');
  const [date, setDate] = useState('');
  const [status, setStatus] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    await markAttendance({ variables: { classId: parseInt(classId), studentId: parseInt(studentId), date, status } });
    setClassId('');
    setStudentId('');
    setDate('');
    setStatus('');
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Attendance</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="number"
          placeholder="Class ID"
          value={classId}
          onChange={(e) => setClassId(e.target.value)}
        />
        <input
          type="number"
          placeholder="Student ID"
          value={studentId}
          onChange={(e) => setStudentId(e.target.value)}
        />
        <input
          type="date"
          placeholder="Date"
          value={date}
          onChange={(e) => setDate(e.target.value)}
        />
        <select value={status} onChange={(e) => setStatus(e.target.value)}>
          <option value="Present">Present</option>
          <option value="Absent">Absent</option>
        </select>
        <button type="submit">Mark Attendance</button>
      </form>
      <ul>
        {data.attendances.map((att) => (
          <li key={att.id}>
            {att.class.name

} - {att.student.name} - {att.date} - {att.status}
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

GraphQL Schema

Define your GraphQL schema to match the resolver functions:

type Class {
  id: ID!
  name: String!
  teacher: Teacher!
}

type Subject {
  id: ID!
  name: String!
  class: Class!
}

type Timetable {
  id: ID!
  class: Class!
  subject: Subject!
  day: String!
  startTime: String!
  endTime: String!
}

type Attendance {
  id: ID!
  class: Class!
  student: Student!
  date: String!
  status: String!
}

type Query {
  classes: [Class!]!
  subjects: [Subject!]!
  timetables: [Timetable!]!
  attendances: [Attendance!]!
}

type Mutation {
  createClass(name: String!, teacherId: Int!): Class!
  createSubject(name: String!, classId: Int!): Subject!
  createTimetable(classId: Int!, subjectId: Int!, day: String!, startTime: String!, endTime: String!): Timetable!
  markAttendance(classId: Int!, studentId: Int!, date: String!, status: String!): Attendance!
}
Enter fullscreen mode Exit fullscreen mode

This setup covers the backend and frontend code for academic management in a School Management System. You can expand on this by adding more details, such as validations, error handling, and additional features as needed.

If you enjoy my content and would like to support my work, you can buy me a coffee. Your support is greatly appreciated!

Disclaimer: This content in generated by AI.

Top comments (0)