-
Node.js 스터디 6주차BackEnd/Node.js 2022. 2. 22. 05:34
Node.js 교과서 7장 MYSQL
✅ 데이터 베이스란?
- 관련성을 가지며 중복이 없는 데이터들의 집합
- 데이터베이스를 관리하는 시스템 : DBMS
- 관계형 데이터 베이스를 관리하는 시스템 : RDBMS
- 관계형 데이터 베이스 : 데이터가 테이블 형태로 이루어져 있으며, 키와 값이라는 종속성을 관계로 표현하는 데이터베이스를 의미한다. Oracle, MySQL등이 대표적이다.
Create Schema `nodeStudy` DEFAULT CHARACTER SET utf8; use nodeStudy;
📌 nodeStudy라는 이름의 데이터베이스를 형성하고, 사용하는 데이터베이스를 nodeStudy로 설정한다.
📌 관련 명령어
- CREATE TABLE [데이터베이스명.테이블명] : 테이블 생성 명령어
- DESC [테이블명] : 만들어진 테이블 확인
- DROP TABLE [테이블명] : 테이블 제거
- SHOW TABLES : 생성된 테이블 전체 확인
CREATE TABLE nodeStudy.users ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(20) NOT NULL, age INT UNSIGNED NOT NULL, married TINYINT NOT NULL, comment TEXT NULL, created_at DATETIME NOT NULL DEFAULT now(), PRIMARY KEY(id), UNIQUE INDEX name_UNIQUE (name ASC)) COMMENT = '사용자 정보' DEFAULT CHARACTER SET = utf8 ENGINE = InnoDB;
✅ SQL 관련 자료형, 옵션 정리
칼럼 관련 설정 테이블 자체 설정 자료형 옵션 COMMENT : 테이블 관한
보충설명, 필수 XINT : 정수형 NULL : NULL허용 FLOAT , DOUBLE : 실수형 NOT NULL : NULL 허용 X VARCHAR(자릿수) : 가변 길이(0~자료형) AUTO_INCREMENT : 숫자 자동 증가 DEFAULT CHARACTER SET : 한글 입력 위해 설정 CHAR(자릿수) : 반드시 길이가 자릿수인 문자열 UNSIGNED : 음수 저장 X TEXT : 긴 글 (수백자 넘어가는 경우) ZEROFILL : 숫자의 자릿수 고정(0001처럼) TINYINT : 1바이트 정수, boolean처럼 사용 가능 PRIMARY KEY : 기본 키 설정 ENGINE : InnoDB DATETIME : 날짜 + 시간 UNIQUE INDEX : 고유한 값 설정 DATE : 날짜, TIME : 시간 DEFAULT : 값이 없다면 기본값 자동 넣음 🔍 InnoDB vs MyISAM
트랜잭션이 많고 대용량 데이터 다룰 경우, 즉 데이터의 변화가 잦은 경우 InnoDB가 효율적
트랜잭션 적고 read작업, 즉 select가 많은 경우 MyISAM이 효율적
사용자의 댓글 저장용 테이블도 만든다.
CREATE TABLE nodeStudy.comments( id INT NOT NULL AUTO_INCREMENT, commenter INT NOT NULL, comment VARCHAR(100) NOT NULL, created_at DATETIME NOT NULL DEFAULT now(), PRIMARY KEY(id), INDEX commenter_idx (commenter ASC), CONSTRAINT commenter FOREIGN KEY (commenter) REFERENCES nodeStudy.users (id) ON DELETE CASCADE ON UPDATE CASCADE) COMMENT = '댓글' DEFAULT CHARSET=utf8mb4 ENGINE = InnoDB;
📌 FOREIGN KEY : 외래키
- 다른 테이블의 기본 키를 저장하는 컬럼
- CONSTRAINT [제약조건명] FOREIGN KEY [컬럼명] REFERENCES [참고하는 컬럼명]
📌 CASCADE 옵션
- ON UPDATE : 수정 되었을 경우, ON DELETE : 삭제 되었을 경우
- CASCADE : 연결된 정보도 함께 수정 OR 삭제 (데이터 불일치를 방지하기 위해)
✅ CRUD 작업하기
📌 CREATE : 데이터를 생성해서 데이터베이스에 넣는 작업
INSERT INTO nodestudy.users (name, age, married, comment) values ('zero', 24, 0, '자기소개1'); INSERT INTO nodestudy.users (name, age, married, comment) values ('nero', 32, 1, '자기소개2'); INSERT INTO nodestudy.comments (commenter, comment) values (1, '안녕하세요 zero의 댓글이에요');
📌 READ : 데이터베이스 내의 데이터를 조회하는 작업
1. 칼럼 전체 조회 (SELECT * )
SELECT * FROM nodestudy.users; SELECT * FROM nodestudy.comments;
2. 특정 칼럼 조회
SELECT name, married FROM nodestudy.users; SELECT name, age FROM nodestudy.users WHERE married = 1 AND age > 30;
3. 기타 옵션
SELECT id, name FROM nodestudy.users ORDER BY age DESC; SELECT id, name FROM nodestudy.users ORDER BY age DESC LIMIT 1;
📌 UPDATE : 데이터베이스의 데이터 수정
- UPDATE [테이블명] SET [컬럼명 = 바꿀 값] WHERE [조건] 의 형태
UPDATE nodestudy.users SET comment = '바꿀 내용' WHERE id = 2;
📌 DELETE : 데이터베이스의 데이터 삭제
- DELETE FROM [테이블명] WHERE [조건]
DELETE FROM users WHERE id = 2;
✅ 시퀄라이즈 사용하기
- ORM : 자바스크립트 객체와 데이터베이스의 릴레이션을 매핑해주는 도구
- 사용 이유 : 자바스크립트 구문을 자동으로 sql문으로 바꿔주기 때문
📌 squelize 및 모듈 설치
npm i express morgan nunjucks sequelize sequelize-cli mysql2 npm i -D nodemon
📌 models/index.js 수정
const Sequelize = require('sequelize'); const User = require('./user'); const Comment = require('./comment'); const env = process.env.NODE_ENV || 'development'; const config = require('../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; User.init(sequelize); Comment.init(sequelize); User.associate(db); Comment.associate(db); module.exports = db;
📌 MYSQL 연결하기
const express = require('express'); const path = require('path'); const morgan = require('morgan'); const nunjucks = require('nunjucks'); const { sequelize } = require('./models'); //models폴더 내 index.js 불러옴. const app = express(); app.set('port', process.env.PORT || 3001); app.set('view engine', 'html'); nunjucks.configure('views', { express : app, watch : true, }); sequelize.sync({ force : false }) // sync 메서드 사용 ~ 실행시 mysql과 연동 .then(() => { console.log('데이터 베이스 연결 성공'); }) .catch((err) => { console.error(err); }); app.use(morgan('dev')); app.use(express.static(path.join(__dirname, 'public'))); app.use(express.json()); app.use(express.urlencoded({ extended : false })); 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'); }); app.listen(app.get('port'), () => { console.log(app.get('port'), '번 포트에서 대기중'); });
📌 config.json 파일 수정 (비밀번호, db 이름 입력)
{ "development": { "username": "root", "password": "[root 비밀번호] "database": "nodestudy", "host": "127.0.0.1", "dialect": "mysql" }, ...
📌 시퀄라이즈 모델 정의
- ❗ 기본적으로 모델 이름은 단수형, 테이블 이름은 복수형을 사용
- user.js
const Sequelize = require('sequelize'); module.exports = class User extends Sequelize.Model { static init(sequelize) { // init : 테이블에 대한 설정 return super.init({ // 테이블 칼럼에 대한 설정 name : { type : Sequelize.STRING(20), allowNull : false, // null 값 허용 여부 unique : true, // unique(고유값) 여부 }, age : { type : Sequelize.INTEGER.UNSIGNED, allowNull : false, }, married : { type : Sequelize.BOOLEAN, allowNull : false, }, comment : { type : Sequelize.TEXT, allowNull : true, }, created_at : { type : Sequelize.DATE, allowNull : false, defaultValue: Sequelize.NOW, // 기본값 }, }, { // 이 아래부터는 테이블 자체 설정 sequelize, // db.sequelize 객체 timestamps : false, // true일시 createdAt, updatedAt 칼럼 생성 underscored : false, // 캐멀케이스(createdAt)를 스네이크케이스(created_at)로 변경 modelName : 'User', // 모델 이름 tableName : 'users', // 테이블 이름 paranoid : false, // deletedAt 칼럼 생성(지운시간 기록), 후에 복원 가능 charset: 'utf8', collate : 'utf8_general_ci', // 한글 입력 위한 설정 }); } static associate(db) {} // 다른 테이블과의 참조 관계 };
- comment.js
const Sequelize = require('sequelize'); module.exports = class User extends Sequelize.Model { static init(sequelize) { return super.init({ comment : { type : Sequelize.STRING(100), allowNull : false, }, created_at : { type : Sequelize.DATE, allowNull : true, defaultValue: Sequelize.NOW, }, }, { sequelize, timestamps : false, modelName : 'Comment', tableName : 'comments', paranoid : false, charset: 'utf8mb4', collate : 'utf8mb4_general_ci', }); } static associate(db) {} };
- index.js에 연결 코드 추가
db.User = User; db.Comment = Comment; User.init(sequelize); Comment.init(sequelize); User.associate(db); Comment.associate(db);
📌 시퀄라이즈와 mysql 차이 간단 정리
MYSQL 시퀄라이즈 VARCHAR(100) STRING(100) INT INTEGER TINYINT BOOLEAN DATETIME DATE INT UNSIGNED INTEGER.UNSIGNED NOT NULL allownull : false UNIQUE unique : true DEFAULT now() defaultValue : Sequelize.NOW
✅ 테이블 간의 관계 정의
- 📌 1 : N = hasMany : User 1명은 여러 개의 Comment 생성이 가능
- User.hasMany
db.User.hasMany(db.Comment, { foreignKey : 'commenter', sourceKey : 'id' });
- Comment.belongsTo
db.Comment.belongsTo(db.User, { foreignKey : 'commenter', targetKey : 'id' });
- 📌 1 : 1 = hasOne : User 1명은 하나의 Info를 갖는다
db.User.hasOne(db.Info, { foreignKey : 'UserId', sourceKey : 'id' }); db.Info.belongsTo(db.User, { foreignKey : 'UserId', targetKey : 'id' });
- 📌 N : M = belongsToMany : Post는 여러개의 Hashtag 가질 수 있고, Hashtag는 여러개의 Post에 쓰일 수 있음
db.Post.belongsToMany(db.Hashtag, { through : 'PostHashtag' }); db.Hashtag.belongsToMany(db.Post, { through : 'PostHashtag' });
✅ 시퀄라이즈 CRUD 쿼리
- 📌 CREATE
INSERT INTO nodestudy.users (name, age, married, comment) values ('zero', 24, 0, '자기소개1');
const { User } = require('../models'); User.create({ name : 'zero', age : 24, married : false, comment : '자기소개1', });
- 📌 READ
SELECT * FROM nodestudy.users; SELECT * FROM nodestudy.users LIMIT 1;
User.findAll({}); User.findOne({});
SELECT name, age FROM nodestudy.users WHERE married = 1 AND age > 30;
const { Op } = require('sequelize'); const { User } = require('../models'); User.findAll({ attributes : ['name', 'age'], where : { married : true, age : { [Op.gt] : 30 }, // Op.gt : Op객체의 gt 함수, 초과를 의미 }, });
- 📌 UPDATE
UPDATE nodestudy.users SET comment = '바꿀 내용' WHERE id = 2;
User.update({ comment : '바꿀 내용', }, { where : { id : 2 }, });
- 📌 DELETE
DELETE FROM users WHERE id = 2;
User.destory({ where : { id : 2 }, });
✅ 시퀄라이즈 관계 쿼리
const user = await User.findOne({ include : [{ model : Comment, }] // 특정 사용자를 가져오면서 그 사용자의 댓글까지 가져오고 싶은 경우 사용하는 옵션 }); // 혹은 아래처럼 관계 설정시 자동 생성되는 동사+모델이름 형태의 메소드 사용가능 const user = await User.findOne({}); const comments = await user.getComments(); // 조회
- ❗ 그 밖에 set(수정), add(생성), remove(삭제)등의 메서드도 지원
const user = await User.findOne({ include : [{ model : Comment, where : { id : 1, }, }] }); const comments = await user.getComments({ where : { id : 1, }, attributes : ['id'] });
- 쿼리에도 where, attributes등의 옵션 사용도 가능
const [ result, metadata ] = await sequelize.query('SELECT * from comments');
- 직접 SQL 쿼리 사용하는 방법도 있음
✅ 실습 코드 (프론트 코드 생략)
// 사용자 이름을 누르면 댓글 로딩 document.querySelectorAll('#user-list tr').forEach((el) => { el.addEventListener('click', function() { const id = el.querySelector('td').textContent; getComment(id); }); }); // 사용자 가져오기 async function getUser() { try { const res = await axios.get('/users'); const users = res.data; console.log(users); const tbody = document.querySelector('#user-list tbody'); tbody.innerHTML = ''; users.map(function (user) { const row = document.createElement('tr'); row.addEventListener('click', () => { getCommnet(user.id); }); 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); }) } catch (err) { console.error(err); } } // 댓글 가져오기 async function getComment(id) { try { const res = await axios.get(`/users/${id}/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; row.appendChild(td); td = document.createElement('td'); td.textContent = comment.comment; row.appendChild(td); const edit = document.createElement('button'); edit.textContent = '수정'; edit.addEventListener('click' , async () => { const newComment = prompt('바꿀 내용을 입력하세요'); if(!newComment) { return alert('반드시 내용을 입력해야 합니다. '); } try { await axios.patch(`/comments/${comment.id}`, { comment : newComment }); getComment(id); } catch (err) { console.error(err); } }); const remove = document.createElement('button'); remove.textContent = '삭제'; remove.addEventListener('click', async () => { try { await axios.delete(`/comments/${comment.id}`); getComment(id); } catch (err) { console.error(err); } }); td = document.createElement('td'); td.appendChild(edit); row.appendChild(td); td = document.createElement('td'); td.appendChild(remove); row.appendChild(td); tbody.appendChild(row); }); } catch (err) { console.error(err); } } //사용자 등록 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}); getUser(); } 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 }); getComment(id); } catch (err) { console.error(err); } e.target.userid.value = ''; e.target.comment.value = ''; });
- app.js 연결
const indexRouter = require('./routes'); const usersRouter = require('./routes/users'); const commentsRouter = require('./routes/comments'); . . . app.use('/', indexRouter); app.use('/users', usersRouter); app.use('/comments', commentsRouter);
- routes 폴더 생성 및 index.js 파일 작성
const express = require('express'); const User = require('../models/user'); const router = express.Router(); router.get('/', async(req, res, next) => { // '/'로 Get 요청 들어왔을 경우 try { const users = await User.findAll(); // 모든 유저 찾기 res.render('sequelize', { users }); // users를 렌더링 } catch (err) { console.error(err); next(err); } }); module.exports = router;
- routes/user.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) => { // 사용자 조회 요청(get) try { const users = await User.findAll(); res.json(users); // json형식의 데이터 반환 } catch (err) { console.error(err); next(err); } }) .post(async (req, res, next) => { // 사용자 등록 요청(post) 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;
- routes/comment.js
const express = require('express'); const { User, 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;
- 실습 화면
'BackEnd > Node.js' 카테고리의 다른 글
Node.js 프로젝트 리팩토링(1) (0) 2022.02.27 Node.js 스터디 5주차 (0) 2022.02.15 Node.js 스터디 4주차 (0) 2022.02.07 Node.js 스터디 3주차 (0) 2022.02.05 Node.js 스터디 2주차 (0) 2022.01.25