학원/NodeJS

NodeJS#6 mySQL 연동

링규 2022. 4. 26. 16:15

mySQL 연동하기

-> npm i sequelize sequelize-cli mysql2 필요 

-> sequelize 초기화 : npx sequelize init

 

init 하고 나면 폴더 4개가 새로 생긴다

 

 

config 폴더 안에 config.json -> 개발중, 테스트중, 제품 단계로 나눠서 연결상태를 지정할 수 있게 되어있다. 

 

 

현재는 개발중이기 때문에 표시된 부분만 수정 해주기

(비밀번호와 데이터베이스(스키마명)는 설정된 대로 하면 된다 ) 

 

 

models 폴더의 index.js 수정

-> 필요한 부분은 남기고 수정할 부분은 수정

 

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

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

db.sequelize = sequelize;
//db에 연결하기 위한 정보가 담긴 연결객체를 db객체에 담는다.
db.Sequelize = Sequelize;
//현재 파일에 require한 Sequelize를 db객체에 담는다 
// ==> db = {sequelize, Sequelize}
module.exports = db;

 

테스트이므로 이정도 코드만 있으면 된다 

 

 

models 폴더에 user.js, comment.js 파일 추가 

-> node가 sequelize를 이용해서 mysql에 테이블을 생성하거나 조작할 수 있는 테이블 모델을 만든다 

(user.js 를 index.js가 require하고 index.js를 app.js가 require 함 -> User.init(Sequelize)와 같은 식으로 호출)

 

 

user.js 

const Sequelize = require('sequelize');

//
module.exports = class User extends Sequelize.Model{
    //테이블을 생성하고 초기화하는 함수 
    static init( sequelize ){
        return super.init({
            //init 함수에 각 필드이름, 속성이 객체로 전달
            //각 필드를 객체 멤버 형식으로 나열 
            name: { type: Sequelize.STRING(20), allowNull: false, unique: false },
            age: { type: Sequelize.INTEGER.UNSIGNED, allowNull: false },
            married: { type: Sequelize.BOOLEAN, allowNull: true },
            comment: { type: Sequelize.TEXT, allowNull: true },
            created_at: { type: Sequelize.DATE, allowNull: true, defaultValue: Sequelize.NOW }
            //따로 기술하지 않아도 auto increment 필드가 추가됨
        },{
            //테이블의 옵션들이 멤버 형식으로 정의됨
            sequelize,
            timestamp: false, //속성이 true이면 createAt, updateAt 필드를 자동 생성
            underscored: false, //예시 : createAt을 create_at으로 바꿔줌
            modelName: 'User', //sequelize가 사용할 테이블(모델) 이름
            tableNameName: 'users', //DB의 실제 테이블 이름
            paranoid: false, // deletedAt(실제 데이터를 삭제하지 않고 시간만 기록) 필드 자동생성
            charset: 'utf8mb4',
            collate: 'utf8mb4_general_ci'
        });
    }

    //테이블관 관계 설정 함수
    static associate(db){
        db.User.hasMany(db.Comment); //1:n 관계로 설정
        //User 모델의 id 필드를 Comment모델에 commenter필드로 복사, 관계를 설정
        
    }
};

 

 

comment.js

const Sequelize = require('sequelize')
const { sequelize } = require('.')

module.exports = class Comment extends Sequelize.Model {
    static init(sequelize){
        return super.init({
            //id 필드는 이 테이블에 PK로 자동 생성됨
            //누가 댓글을 썼는지 저장할 필드 : commenter 
            //외래키 설정도 자동으로 가능 => user 테이블의 id가 복사 되어 현재테이블의 필드로 삽입 생성
            //=> 외래키로 설정하기만 하면 됨 (associate에서)
            comment: { type: Sequelize.STRING(100), allowNull: false},
            create_at: { type: Sequelize.DATE, allowNull: true, defaultValue: Sequelize.NOW}
        }, {
            sequelize,
            timestamp: false,
            underscored: false,
            modelName: 'Comment',
            tableNameName: 'comments',
            paranoid: false,
            charset: 'utf8mb4',
            collate: 'utf8mb4_general_ci'
        })
    }

    static associate(db){
        db.Comment.belongsTo(db.User, { foreingKey:'commenter', targetKey: 'id'});
        //Comment 테이블의 commenter 필드가 User테이블의 id를 참조하도록 설정
    }
}

 

 

-> index.js 도 수정

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

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

db.sequelize = sequelize;
db.Sequelize = Sequelize;

//require한 User 모델에 Comment 모델도 db에 담는다 
db.User = User; 
db.Comment = Comment; 

//모델 객체를 초기화하는 함수, 관계 형성 함수 실행
User.init(sequelize);
Comment.init(sequelize);
User.associate(db);
Comment.associate(db);

module.exports = db;

 

만든 User와 Comment 가 정상 실행될 수 있도록 실행 함수를 추가 !

-> db가 exports되고 app.js에 require되면  require된 db에서 sequelize를 꺼내서 sync함수를 실행하게되고 이때 테이블도 생성된다

 

 

 

필요한 폴더를 3개 더 만든다 

 

Nunjucks를 이용하기 위한 views(html 파일 저장)

라우터들을 담을 routers

파일을 업로드 하기 위한 public 

 

 

 

 

nodejs 스키마 생성

-> 스키마 창 빈 공간에 마우스 우클릭 create schema 

 

 

 

 

app.js 생성 

 

const express = require('express')
const nunjucks = require('nunjucks')

const app = express()
app.set('port', process.env.PORT || 3000)
app.use(express.json())
app.use(express.urlencoded({extended: false}))

const {sequelize} = require('./models') //index.js는 파일명 안적어도 됨
sequelize.sync({force:false})
.then(()=>{console.log('데이터베이스 연결 성공')})
.catch((err)=>{console.error(err)})

app.set('view engine', 'html') 
nunjucks.configure('views',{ express: app, watch: true })
app.use(express.static(path.join(__dirname, 'public')))

app.get('/', (req, res) => {
    res.send('<h1>Hello MySQL</h2>')
})

app.listen(app.get('port'), () => {
    console.log('the Server is ready in port', app.get('port'))
})

 

 

 

여기까지 하고 npm start 했을 때 

 

테이블이 생성되는 메시지와 함게 '데이터베이스 연결 성공'(app.js try에서 지정한 문자열)이 나오면 성공 

 

 

테이블도 정상 생성 됨

(fk컬럼을 userid 로 잘못만들어서 나중에 수정함)

 

 

화면 출력도 정상 

 

 

테스트 끝


 

DB 활용하기 

 

 

routers에 파일 3개를 추가한다 

 

 

app.js에 app.get('/') ~~ 을 지우고 3파일을 required / 에러처리

 

const indexRouter = require('./routers')
const usersRouter = require('./routers/users')
const commentsRouter = require('./routers/comments')

app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/comments', commentsRouter);

app.use((req, res, next) => {
    const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`)
    error.status = 404;
    next(error)
})
app.use((err, req, res, next) => {
    res.locals.message = err.message;
    res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
    res.status(err.status || 500);
    res.render('error');
})

 

 

routers/index.js

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

const router = express.Router()

router.get('/', (req, res) => {
    res.render('sequelize', {}) //서버 실행 첫페이지 : sequelize.html 
})


module.exports = router;

-> 시작 페이지를 sequelize.html 로 지정

 

 

sequelize.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sequelize</title>
</head>
<body>
    <!-- 사용자 -->
    <div>
        <form id="user-form">
            <fieldset>
                <legend>사용자 등록</legend>
                <div> <input name="username" type="text" placeholder="이름"></div>
                <div> <input name="age" type="text" placeholder="나이"></div>
                <div> 
                    <input name="married" type="checkbox">
                    <label for="married"> 기혼 </label>
                </div>
                <button type="submit"> 등록 </button>
            </fieldset>
        </form>
    </div><br>
    <table id="user-list">
        <thead>
            <tr>
                <th>아이디</th>
                <th>이름</th>
                <th>나이</th>
                <th>기혼여부</th>
            </tr>
        </thead>
        <tbody>
            {%for user in users %}
            <tr>
                <td>{{user.id}}</td>
                <td>{{user.name}}</td>
                <td>{{user.age}}</td>
                <td>{{'기혼' if user.married else '미혼'}}</td>
            </tr>
            {% endfor %}
        </tbody>
        
    </table>

    <!-- 댓글 -->
    <div>
        <form id="comment-form">
            <fieldset>
                <legend>댓글등록</legend>
                <div><input name="userid" type="text" placeholder="아이디"></div>
                <div><input name="comment" type="text" placeholder="댓글"></div>
                <button type="submit"> 등록 </button>
            </fieldset>
        </form>
    </div><br>
    <table id="comment-list">
        <thead>
            <tr>
                <th>아이디</th>
                <th>작성자</th>
                <th>댓글</th>
                <th>수정</th>
                <th>삭제</th>
            </tr>
        </thead>
        <tbody>
            
        </tbody>
    </table>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="/sequelize.js"></script> 
    <!-- app.use('/', express.static(path.join(__dirname, 'public'))) -->
    <!-- 에서 / <- 가 public 폴더로 설정되었으므로 sequelize.js는 public 폴더에 있어야 함-->
</body>
</html>

 

 

sequelize.js  -> 저번에 쿠키 이용해서 로그인 한 사람 이름 출력하는 거랑 비슷


document.getElementById('user-form').addEventListener('submit', async (e)=>{
    e.preventDefault()
    const name = e.target.username.value
    const age = e.target.age.value
    const married = e.target.married.checked
    
    if(!name) {return alert('이름을 입력하세요')}
    if(!age) {return alert('나이를 입력하세요')}

    try{
        await axios.post('/users', {name, age, married})
        //레코드 추가 후, user-list를 새로고침 
        //레코드 조최 후 행단위로 추가하는 함수 -> getUsers()
        getUsers()
    } catch(err) { console.error(err) }

    e.target.username.value = ''
    e.target.age.value = ''
    e.target.married.checked = false

}) //사용자 등록

document.getElementById('comment-form').addEventListener('submit', async (e)=>{
    e.preventDefault()
    const id = e.target.userid.value
    const comment = e.target.comment.value

    if(!id) { return alert('아이디를 입력하세요') } 
    if(!comment) { return alert('내용을 입력하세요') }

    try {
        await axios.post('/comments', {id, comment})
        console.log('어웨이트 끝남')
        getComments();
    } catch(err) { console.error(err)}

    e.target.userid.value = ''
    e.target.comment.value = ''
}) //댓글 등록


async function getUsers() {
    try{
        const res = await axios.get('/users')
        const users = res.data
        const tbody = document.querySelector('#user-list tbody')
        tbody.innerHTML = ''

        users.map(function(user){
            const row = document.createElement('tr')
            let td = document.createElement('td')
            td.textContent = user.id
            row.appendChild(td)
            
            td = document.createElement('td')
            td.textContent = user.name
            row.appendChild(td)

            td = document.createElement('td')
            td.textContent = user.age
            row.appendChild(td)

            td = document.createElement('td')
            td.textContent = user.married ? '기혼' : '미혼'
            row.appendChild(td)

            tbody.appendChild(row) //완성된 tr을 tbody에 추가 
        })

    } catch(err) { }
}

async function getComments() {
    try{
        const res = await axios.get('/comments')
        const comments = res.data
        const tbody = document.querySelector('#comment-list tbody')
        tbody.innerHTML = ''

        comments.map(function(comment){
            const row = document.createElement('tr')
            let td = document.createElement('td')
            td.textContent = comment.id
            row.appendChild(td)
            
            td = document.createElement('td')
            td.textContent = comment.User.name 
            //comment에 include한 User.name
            row.appendChild(td)

            td = document.createElement('td')
            td.textContent = comment.comment
            row.appendChild(td)

            tbody.appendChild(row) //완성된 tr을 tbody에 추가 

            const edit = document.createElement('button');
            edit.textContent = '수정';

            const remove = document.createElement('button')
            remove.textContent = '삭제'

            td = document.createElement('td')
            td.appendChild(edit)   //버튼을 td에 추가
            row.appendChild(td);    // td를 tr에 추가

            td = document.createElement('td')
            td.appendChild(remove)   
            row.appendChild(td);    

            tbody.append(row);
        })

    } catch(err) { }
}

 

 

라우터에서 레코드 추가/조회하기 

 

1. 레코드 삽입 (insert)

모델명.create({ 필드명:값, 필드명2:값2, 필드명3:값3 ... })

 

-> 비동기 함수 이기 때문에 await 필요

 

 

2. 일반 레코드 조회(모든 필드, 모든 레코드 / select * )

모델명.findAll( { } )

 

 

3. 일부 필드만 조회 ( selct 필드명 )

모델명.findAll( { attributes : [ '필드명1', '필드명2' ... ] } )

 

 

4. 일부 필드 & 일부 레코드 (where 조건) 조회

모델명.findAll( { attributes : [ '필드명1', '필드명2 ... ], where: {  필드명: 조건, 필드명:  조건 ...  } )

 

-> where 절에 두 개의 조건의 별도 언급 없이 ' , '로 이어져 있다면 그 둘은 and 관계

-> or의 경우 where: { [Op.or] : [ { married: 1 }, { age: { [ Op.lt ]: 30 } } ]  } 과 같은 형식으로 사용한다 

(married 가 1이거나 age가 30이하) - Op = Option

 

https://pjt3591oo.github.io/sequelizejs_translate/build/html/CoreConcepts/Querying.html

 

쿼리(조회) — Sequelize Of Node.js translate V5 5 documentation

JSON 데이터 타입은 PostgreSQL, SQLite, MySQL 그리고 MariaDB에서 제공합니다. PostgreSQL PostgreSQL의 JSON 데이터 형식은 이진 표현이 아닌 일반 텍스트로 값을 저장합니다. 단순히 JSON 표현을 저장하고 검색

pjt3591oo.github.io

operator 참고 

 

 

5.  정렬 

모델명.findAll (  { attributes: [ '필드명1', '필드명2' ... ], order: [ [ '필드명', '(desc/asc)' ] ] } )

-> order에 정렬조건이 한개 이상 올 수 있다 ex) order [ [ 'age', 'desc' ], [ 'id', 'asc' ] ]

 

 

6.조인

모델명.findAll ( { include: { model: 조인할테이블 } } )

 

 

 

user.js 

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

const router = express.Router()

router.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) //test용
        res.json(user) //test용
    } catch(err) { 
        console.error(err);
        next(err);
    }
})

router.get('/', async (req, res)=>{
    try{
        const users = await User.findAll({ }) //전부 조회
        res.json(users)
    } catch(err) { console.error(err); next(err); }
})

module.exports = router;

 

 

comments.js 

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

const router = express.Router()

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


router.get('/', async (req, res, next)=>{
    try{
        const comments = await Comment.findAll({
            include: { model: User } // user 테이블과 join 
        })
        res.json(comments);
    } catch(err) {
        console.error(err)
        next(err)
    }
})


module.exports = router;

 

 

입력 후 등록을 누르면 목록에 추가된다 

 

 

 


 

 

에러 흔적 남기기

 

오늘은 다 오타 때문에 난 에러 .... 

 

 

1. Cannot set properties of undefined (setting 'message')

 

 

 

err.message 이부분이 문제인줄 알고 여러 수정을 해봤으나...

문제는 locals 를 local로 써놓은 거였다 

 

-> 이거 고친 후 에러페이지 등장

 

 

2. unexpected end of file 

Nunjucks 에러도 같이 나는 걸 봐서 문법이 어딘가 틀렸겠거니 하고 찾아봤다.

 

 

 

.... 급하게 하지말자 ^^....

 

 

3. {message: 'Request failed with status code 404', name: 'AxiosError', code: 'ERR_BAD_REQUEST', config: {…}, request: XMLHttpRequest, …}

 

 

 

 

-> 변수 틀린거 없나 확인하고 commnets.js의 '/' 부분을 테스트함 

 

 

여기보면 console을 colsole 로 오타냈는데 이거 때문에 또 오류가 났다 ㅎ

 

 

console.log 문자열들이 하나도 출력 안됨 오타 검수를 여러번 하였으나 못찾음

이 파일이 아닌가 싶어 app.js 를 열어보니

 

 

열자마자 보이는 오타 ㅠ ㅠ

 

고치고 나니 정상 작동함