보람찬 코기의 개발자 블로그
article thumbnail
반응형

지난 한 주간  2024 Winter - Spurt Project 를 진행하면서 작성한 게시글입니다.

 

프로젝트의 개요는 이전 게시글을 참고 부탁드립니다.


 

프리즈마 작성

 

 

 

 

위의 E-R-D 토대로 prisma schema를 작성하였다.

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model Student{
  student_id         Int     @id @default(autoincrement())
  student_name       String
  student_email      String
  student_age        String
  student_aspiration String
  enrollments Enrollment[]
}

model Instructor{
  instructor_id           Int @id @default(autoincrement())
  instructor_name         String
  instructor_email        String
  instructor_introduction String
  enrollments Enrollment[]
}

model Enrollment{
  student_id      Int
  instructor_id   Int
  course_id       Int
  student     Student     @relation(fields: [student_id], references: [student_id])
  instructor  Instructor  @relation(fields: [instructor_id], references: [instructor_id])
  course      Course      @relation(fields: [course_id], references: [course_id])
  @@id([student_id, instructor_id, course_id])
}

model Course{
  course_id          Int @id @default(autoincrement())
  course_name        String
  course_description String
  course_duration    String
  course_price       Int
  enrollments          Enrollment[]
}
  1. 일대다 (One-to-Many):
    • Student과 Instructor 모델은 각각 Enrollment 모델과 1:N 관계를 가지고 있다. 즉, 하나의 학생이나 강사는 여러 수강 등록 정보를 가질 수 있지만, 수강 등록 정보는 하나의 학생이나 강사에게 속한다.
    • Course 모델도 1:N 관계를 가지며, 하나의 강의는 여러 수강 등록 정보를 가질 수 있지만, 수강 등록 정보는 하나의 강의에 속한다.
  2. 다대다 (Many-to-Many):
    • Enrollment 모델은 학생(Student), 강사(Instructor), 강의(Course) 모델과 M:N 관계를 가진다. 이는 하나의 수강 등록 정보가 여러 학생과 여러 강사, 여러 강의와 연결되고 M:N의 중간 테이블을 위해 작성되었다.

학습을 위해 1차로 간단히 구현한 계층 구조는 다음과 같다.

app.js

const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');

const indexRouter = require('./routes/index');
const studentsRouter = require('./routes/students');
const instructorsRouter = require('./routes/instructors');
const coursesRouter = require('./routes/courses');
const enrollmentsRouter = require('./routes/enrollments');

const { Prisma, PrismaClient } = require('@prisma/client');

const app = express();
const prisma = new PrismaClient();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/students', studentsRouter);
app.use('/instructors', instructorsRouter);
app.use('/courses', coursesRouter);
app.use('/enrollments', enrollmentsRouter);

// main page 구현
app.get('/', (req,res) => {
  res.render('index',{message: 'Welcom to Boflearn Main Page!'});
});

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

 

/routes/studentRoutes

const express = require('express');
const router = express.Router();
const { PrismaClient } = require('@prisma/client');

const prisma = new PrismaClient();

router.get('/', async (req, res, next) => {
  try {
    const students = await prisma.student.findMany();
    res.render('students', { students });
  } catch (error) {
    next(error);
  }
});

router.post('/', async (req, res, next) => {
  try {
    const { name, email, age, aspiration } = req.body;
    const newStudent = await prisma.student.create({
      data: {
        student_name: name,
        student_email: email,
        student_age: age,
        student_aspiration: aspiration,
      },
    });
    res.redirect('/students');
  } catch (error) {
    next(error);
  }
});

module.exports = router;

 

위의 구조는 app.js에서 routes로 핸들링 하는 구조이다. 하지만 부족한점은 다음과 같다.

 

1. 모듈화 부족

모듈화 부분이 부족하다.

 

모든 라우트 처리 로직이 students.js 파일에 포함되어 있어 나중에 애플리케이션을 유지보수하고 확장하는 데 어려움이 있을 것이다.

 

코드를 라우트, 컨트롤러 및 모델과 같은 서로 다른 파일로 분리하여 모듈화를 진행해야 한다.

 

2. Prisma Client 반복 초기화 ( 튜터 피드백 )

app.js 및 students.js에서 Prisma Client의 새 인스턴스가 생성된다.( 중복 호출된다 )

 

이를 최적화하려면 별도의 파일에서 단일 인스턴스를 만들고 필요한 곳에서 가져오도록 개선해야한다.

그러면 리소스 소비와 관련된 문제를 방지하고 데이터베이스 연결의 일관성을 보장할 수 있다.

 

 


이후 개선 사항 (MVC 패턴 리팩토링 & CRUD 기능 구현)

 

구현 이전에 MVC 패턴에 대해 완벽한 이해가 필요했다.

 

아래는 학습에 참고한 자료를 링크 첨부하였다.

https://developer.mozilla.org/ko/docs/Glossary/MVC 

 

MVC - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN

MVC (모델-뷰-컨트롤러) 는 사용자 인터페이스, 데이터 및 논리 제어를 구현하는데 널리 사용되는 소프트웨어 디자인 패턴입니다. 소프트웨어의 비즈니스 로직과 화면을 구분하는데 중점을 두고

developer.mozilla.org

 

 

 

app.js

const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const { Prisma, PrismaClient } = require('@prisma/client');

const indexRouter = require('./routes/indexRoutes');
const studentsRouter = require('./routes/studentRoutes');
const instructorsRouter = require('./routes/instructorRoutes');
const coursesRouter = require('./routes/courseRoutes');
const enrollmentsRouter = require('./routes/enrollmentRoutes');

const app = express();
const prisma = new PrismaClient();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/students', studentsRouter);
app.use('/instructors', instructorsRouter);
app.use('/courses', coursesRouter);
app.use('/enrollments', enrollmentsRouter);

// main page 구현
app.get('/', (req, res) => {
  res.render('index', { message: 'Welcome to Boflearn Main Page!' });
});

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

 

routes/studuentRoutes.js

const express = require('express');
const router = express.Router();
const studentController = require('../controllers/studentController');

router.get('/', studentController.getAllStudents);
router.post('/', studentController.createStudent);
router.delete('/:id',studentController.deleteStudent);
router.put('/:id',studentController.updateStudent);

module.exports = router;

 

controllers/studentController.js

const StudentModel = require('../models/studentModel');
const studentModel = new StudentModel();

exports.getAllStudents = async (req, res, next) => {
  try {
    const students = await studentModel.getAllStudents();
    res.render('students', { students });
  } catch (error) {
    next(error);
  }
};

exports.createStudent = async (req, res, next) => {
  try {
    const { name, email, age, aspiration } = req.body;
    await studentModel.createStudent(name, email, age, aspiration);
    res.redirect('/students');
  } catch (error) {
    next(error);
  }
};

exports.deleteStudent = async (req, res, next) => {
  try{
    const studentId = parseInt(req.params.id,10);
    await studentModel.deleteStudent(studentId);

    res.status(204).send();
  }catch(error){
    next(error);
  }
};

exports.updateStudent = async (req, res, next) => {
  try {
    const studentId = parseInt(req.params.id, 10);
    const { name, email, age, aspiration } = req.body;

    await StudentModel.updateStudent(studentId, {name, email, age, aspiration});

    res.redirect('/students');
  } catch (error) {
    next(error);
  }
};

 

models/studentModel.js

const { PrismaClient } = require('@prisma/client');
const { deleteStudent } = require('../controllers/studentController');
const prisma = new PrismaClient();

class StudentModel {
  async getAllStudents() {
    return prisma.student.findMany();
  }

  async createStudent(name, email, age, aspiration) {
    return prisma.student.create({
      data: {
        student_name: name,
        student_email: email,
        student_age: age,
        student_aspiration: aspiration,
      },
    });
  }

  async updateStudent(studentId, data) {
    try {
      const existingStudent = await prisma.student.findUnique({
        where: {
          student_id: studentId,
        },
      });

      if (!existingStudent) {
        throw new Error('Student not found');
      }

      return prisma.student.update({
        where: {
          student_id: studentId,
        },
        data:{
          student_name: data.name,
          student_email: data.email,
          student_age: data.age,
          student_aspiration: data.aspiration,
        },
      });
    } catch (error) {
      throw new Error(`Error updating student: ${error.message}`);
    }
  }

  async deleteStudent(studentId){
    return prisma.student.delete({
      where:{
        student_id: studentId,
      },
    });
  }
}

module.exports = StudentModel;

 

 

Student의 이외의 다른 것은 Github을 참조하자

https://github.com/WellshCorgi/2024-Winter-express-project/commit/8b2ebb6a3cb215fa430e429659eb6d6ddb2b6bab

 

Fix : MVC Refactoring (CRUD) & · WellshCorgi/2024-Winter-express-project@8b2ebb6

decorate view

github.com

 

 


되돌아보기

 

기존의 코드를 리팩토링하면서 MVC 패턴을 직접 찾아보고,

 

이전에 작성한 코드가 초심자적인 부분이 많다는 것을 깨달았다.

 

특히, 직접 Model, Routes, Controller를 분리하고 테스트를 진행하면서 Express의 동작 흐름을 더 깊게 이해할 수 있었다.

 

이러한 리팩토링을 통해 코드의 가독성을 높이고 유지보수성을 향상시키는 것이 목표이다.

 

또한, 앞으로는 발생하는 오류에 대한 구문을 더 추가하여 어느 부분에서 문제가 발생했는지 명확하게 파악하고 수정할 계획이다.

 

다음은 JWT 인증을 관련하여 학습할 예정이다.

반응형
profile

보람찬 코기의 개발자 블로그

@BoChan

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!