Node.js로 MySQL 다루기(sequelize)

서유진·2022년 3월 24일
0

Node.js

목록 보기
6/14


오늘은 node.js교과서 에서 실제로 가장 도움됐던 것 같은,, 그런 예제를 가져왔습니다!!
별 생각 없이 친구들 이름을 넣었는데, 데이터베이스 또 만들기가 귀찮아서 가려서 사진을 넣을 예정입니다..
근데 다 쓰고 나서 느끼는건데, 이럴거면 만들걸 그랬어요

Node.jsMySQL 들이붓기

sequelize

node.jssequelize를 통해 mySQL과 연결합니다. sequelize 공식문서

간단한 데이터베이스 구조를 시각화하여 실행하는 예제

결과

사용자 등록

댓글 조회 / 작성 / 수정 / 삭제

이름가리는거 포기 미안해

실제 데이터베이스에 반영된 내용



코드 분석

폴더 구조

일단 폴더 구조는 위와 같습니다.
여기서 views 폴더는 화면을 꾸며주는 요소가 들어있어 설명에 생략하겠습니다. 데이터 베이스 연동에 활용되는 부분만 짤막하게 하려고 합니다.

전체 코드는 하단에 깃허브 링크로 올려두었습니다.

app.js

const {sequelize} = require('./models');

...

sequelize.sync({force:false})
  .then(()=>{
    console.log('데이터베이스 연결 성공');
  })
  .catch((err)=>{
    console.error(err);
  });

/* middle ware */
...

app.use((err, req, res, next) => {
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV === 'development' ? err : {}; //config 파일과 동일해야 함
  res.status(err.status || 500);
  res.render('error');
});

sequelize.sync(...) : 시퀄라이즈를 통해 mySQL과 연결해주는 부분입니다.
app.use(...) : 미들웨어를 이용해서 config에 지정되어있는 데이터베이스 이름(실제 mysql에서 선언한 이름과 다릅니다)과 같은지 비교하는 부분입니다.

그럼 config 파일을 안 볼 수 없겠죠??

config.json

항상 느끼는건데 json에도 주석을 달 수 있으면 좋겠습니다......

{
  "development": {
    "username": "root",
    "password": "1234", 
    "database": "nodejs",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
 ...
}

"development" : app.js에 이 이름으로 된게 들어가있어야 합니다. 둘 중 하나가 다르다면 통일해주면 됩니다.
"username" : 데이터베이스 구조를 root에 만들어서 "root"로 적었습니다.
"password" : 제가 패스워드를 1234로 설정해서 " "로 묶지 않고 실행하니까 오류가 떠서 고생했던 경험이 있습니다. 🎃 꼭 따옴표로 묶어주세요!
"database" : 이 부분은 데이터베이스 구조가 들어가있는 schema 이름을 넣으시면 됩니다.

models/users.js

모델 구조에 대해 정의한 부분입니다. 여기서 모델은 하나의 table을 뜻합니다.

const Sequelize = require('sequelize');

module.exports = class User extends Sequelize.Model{
    static init(sequelize){
        return super.init({
            name:{
                type:Sequelize.STRING(20),
                allowNull:false,
                unique:true,
			...
              
            },
        }, {
            sequelize,
            timestamps:false,
            underscored:false,
            modelName:'User',
            tableName:'users',  //mySQL 테이블 명 들어가는 곳
            paranoid:false,
            charset:'utf8',
            collate:'utf8_general_ci',
        });

    }
    //관계설정 1:N = USER : COMMENT
    static associate(db) {
        //hasMany : 현재 모델의 정보가 다른 모델로 들어갈 때
        db.User.hasMany(db.Comment,{foreignKey:'commenter',sourceKey:'id'});
    }
};

init : 여기 들어가는 내용은 처음에 만들었던 mySQL 데이터베이스 구조와 같아야 합니다.
associate : 만약 테이블 간에 관계가 성립되어 있다면 들어가야 하는 부분입니다. 내용은 주석과 같습니다. 쿼리문을 읽을 줄 아는 분이라면 대충 해석 가능할 거라고 판단됩니다. db.Comment는 models/comments.js 에서 exports합니다.

models/comments.js

const Sequelize = require('sequelize');

module.exports = class Comment extends Sequelize.Model {
    static init(sequelize) {
        return super.init({
            comment:{
                type:Sequelize.STRING(30),
                allowNull:false,
            },
            created_at:{
                type:Sequelize.DATE,
                allowNull:true,
                defaultValue:Sequelize.NOW,
            },
        },{
            sequelize,
            timestamps:false,
            modelName:'Comment',
            tableName:'comments',    //mySQL 테이블 명 들어가는 곳
            paranoid:false,
            cherset:'utf8mb4',
            collate:'utf8mb4_general_ci',
        });
    }
    //관계설정 1:N = USER : COMMENT
    static associate(db){
        //belongsTo:현재 모델에서 다른 모델의 정보를 받아올 때 == 다른 모델의 정보가 들어갈 때
        db.Comment.belongsTo(db.User,{foreignKey:'commenter',targetKey:'id'});
    }
}

마찬가지로 init에 속성들이 정의되고associate 에는 아까와 다른 메서드가 들어갑니다.


nodejs 스키마에 들어가는 테이블은 users와 comments 두가지 입니다.
이 두 테이블은 1:N의 구조를 하고 있어서 hasManybelongsTo 메서드를 사용했는데요.
다른 관계에서는 메서드 이름이 조금 달라집니다. (표로 정리해 봤어요)

(데이터를)제공해주는 메서드제공받는 메서드
1:NhasManybelongsTo
1:1hasOnebelongsTo
N:MbelongsToManybelongsToMany

아 그리구 N:M 구조는 새로운 모델(테이블)이 생성됩니다...... 왜그런지는 모름 교수님이 그랬음
{ through : 'tablename'} 이걸 제공하는, 제공받는 모델의 belongsToMany 뒤에 붙이면 됩니다.

models/index.js

sequelize-cli 가 자동으로 만들어주는 코드에서 일부 삭제 및 수정한 코드입니다.
모르는 부분이 많다는 말

const Sequelize = require('sequelize');
const User = require('./user'); 
const Comment = require('./comment')
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config')[env];
const db = {};


const sequelize = new Sequelize(config.database, config.username, config.password, config);


db.sequelize = sequelize; 
db.User = User;
db.Comment = Comment;

/* init : 이게 있어야 테이블이 모델이 됨 */
User.init(sequelize);  
Comment.init(sequelize);

/* associate : 모델간 관계 연결 */
User.associate(db);
Comment.associate(db);

module.exports = db;

new Sequelize(..)를 통해 MySQL과 연결 객체를 생성하였습니다.
db라는 객체에 User과 Comment 모델, 그리고 나중에 재사용을 위한 sequelize를 담아두었습니다.

routes/index.js

라우트 부분입니다. 라우트는 우리가 주소창에 naver/com/뭐라뭐라/ 이런식으로 뜨는거에서 /../ 사이 문자열과 어떤 메소드로 보냈는지에 따라 해야하는 행동이 바뀌는 것을 제어합니다.

var express = require('express');
const { User } = require('../models');
var router = express.Router();

/* GET home page. */
router.get('/', async (req, res, next) => {
  try{
    const users = await User.findAll();
    res.render('sequelize', { users });
  } catch(err){
    console.error(err);
    next(err);
  }
});

module.exports = router;

시퀄라이즈는 기본적으로 프로미스 객체를 지원합니다. 따라서 프로미스의 뿌리에서 나온 async/await 으로 각각 조회 성공/실패 시의 정보를 얻을 수 있습니다.
User.findAll(): 모든 사용자 찾기
res.render(..) : 데이터베이스 조회 후 탬플릿 렌더링에 사용

routes/users.js

const express = require('express');
const User = require('../models/user');
const Comment = require('../models/comment');

const router = express.Router();

router.route('/')
  .get(async (req, res, next) => {
    try {
      const users = await User.findAll();
      res.json(users);
    } catch (err) {
      console.error(err);
      next(err);
    }
  })
  .post(async (req, res, next) => {
    try {
      const user = await User.create({
        name: req.body.name,
        age: req.body.age,
        married: req.body.married,
      });
      console.log(user);
      res.status(201).json(user);
    } catch (err) {
      console.error(err);
      next(err);
    }
  });

router.get('/:id/comments', async (req, res, next) => {
  try {
    const comments = await Comment.findAll({
      include: {
        model: User,
        where: { id: req.params.id },
      },
    });
    console.log(comments);
    res.json(comments);
  } catch (err) {
    console.error(err);
    next(err);
  }
});

module.exports = router;

route('/'): get 메서드인지 post메서드인지에 따라 해야하는 행동이 달라집니다. 저는 get은 출력 post는 입력이라고 생각하면서 코드를 봅니다. 물론 정의가 정확하지 않지만, 즉각적으로 이해하기 편하니까요.
res.json(..) : 아까와는 다르게 결과를 json 형태로 요청합니다.
include : inner Join 할 때 씁니다. 우리는 눈치있는 사람이니까, model인 User에 있는 id: req.params.id인, comment들을 조회하고 있네요. 사실 정확하게는 잘 모르겟습니다. 도저히 공식문서에서 include를 찾을 수가 없네요. 휴

routes/comments.js

const express = require('express');
const { Comment } = require('../models');

const router = express.Router();

router.post('/', async (req, res, next) => {
  try {
    const comment = await Comment.create({
      commenter: req.body.id,
      comment: req.body.comment,
    });
    console.log(comment);
    res.status(201).json(comment);
  } catch (err) {
    console.error(err);
    next(err);
  }
});

router.route('/:id')
  .patch(async (req, res, next) => {
    try {
      const result = await Comment.update({
        comment: req.body.comment,
      }, {
        where: { id: req.params.id },
      });
      res.json(result);
    } catch (err) {
      console.error(err);
      next(err);
    }
  })
  .delete(async (req, res, next) => {
    try {
      const result = await Comment.destroy({ where: { id: req.params.id } });
      res.json(result);
    } catch (err) {
      console.error(err);
      next(err);
    }
  });

module.exports = router;

.patch : 리소스를 일부만 변경하는 http 메서드 입니다. 댓글의 수정 부분에 해당합니다.
.delete : 리소스를 삭제하라는 메서드 입니다. 댓글의 삭제 부분에 해당합니다.
res.status(201) : 여기서 201은 http 상태코드 입니다. 완전한 성공이라는 뜻인데 이것도 한번 정리하면 좋을 것 같네요

http 상태 코드는 클라이언트가 보낸 요청의 처리 상태를 응답에서 알려주는 기능입니다.

상태코드번호의 미
1xx (Informational)요청이 수신되어 처리중
2xx (Successful)요청 정상 처리
3xx (Redirection)요청을 완료하려면 추가 행동이 필요
4xx (Client Error)클라이언트의 잘못된 문법등으로 서버가 요청을 수행할 수 없음
5xx (Server Error)서버가 정상 요청을 처리하지 못함

그 유명한 404 NOT FOUND 가 바로 이겁니다.....!



모든 코드 내용은 여기 에서 확인 가능합니다.
제가 잘못 설명한 부분이 있다면 꼭 알려주세요!!!

벨로그가 뭐라고 지금 3시 46분이네요.......................... 주변의 갓생 인간들이 절 더 의지있게 만듭니다.. 화이팅 나 자신..

profile
Backend Dev.

0개의 댓글