NodeJS#6 mySQL 연동
mySQL 연동하기
-> npm i sequelize sequelize-cli mysql2 필요
-> sequelize 초기화 : npx sequelize init
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.log 문자열들이 하나도 출력 안됨 오타 검수를 여러번 하였으나 못찾음
이 파일이 아닌가 싶어 app.js 를 열어보니
열자마자 보이는 오타 ㅠ ㅠ
고치고 나니 정상 작동함