CRUD
CRUD란 Create, Read, Update, Delete의 첫 글자를 딴 두문자어이다.
우선 CRUD를 하려면 테이블이 있어야 하니 테이블을 만들자.
Create
데이터를 생성해서 데이터베이스에 넣는 작업이다. users 테이블에 데이터를 몇 개 넣어보자.
INSERT INTO users (name, age, married, comment) VALUES ('홍길동', 24, 0, '자기소개1');
INSERT INTO users (name, age, married, comment) VALUES ('김철수', 32, 1, '자기소개2');
comments 테이블에도 넣어보자.
워크벤치를 사용하면 위와 같이 테이블에 직접 적고 하단에 apply를 누르면 insert가 완료된다.
Read
조회는 데이터 베이스에 있는 데이터를 조회하는 작업이다.
SELECT문을 사용하면 된다. 워크벤치는 테이블에 세 번째 아이콘을 클릭하면 간편하게 조회 가능하다.
Update
update는 데이터베이스에 있는 데이터를 수정하는 작업이다.
Delete
삭제는 데이터베이스에 있는 데이터를 삭제하는 작업이다.
워크벤치 사용 시에는 로우를 우클릭하여 Delete row를 클릭하면 된다.
시퀄라이즈
노드에서 MySQL 데이터베이스에 접속해보자.
쉽게 작업을 할 수 있게 도와주는 라이브러리인 시퀄라이즈(Sequelize)를 이용해보자.
시퀄라이즈는 ORM(Object-relation Mapping)으로 분류된다. ORM은 자바스크립트 객체와 데이터베이스의 릴레이션을 매핑해주는 도구이다.
시퀄라이즈는 자바스크립트 구문을 알아서 SQL로 바꿔준다. 따라서 SQL 언어를 직접 사용하지 않아도 자바스크립트만으로 MySQL을 조작할 수 있다.
다른 패키지까지 함께 설치하여 학습해보자.
npm i express morgan nunjucks sequelize sequelize-cli mysql2
npm i -D nodemon
sequelize-cli는 시퀄라이즈 명령어를 실행하기 위한 패키지이고, mysql2는 MySQL과 시퀄라이즈를 이어주는 드라이버이다.
npx sequelize init
위와 같은 명령어를 호출하면 된다.
성공적으로 완료가 된다면 위와 같은 폴더들이 생성된다.
models 폴더 안의 index.js가 있는지 확인하자.
index.js를 생성해주는 그대로 사용하면 에러가 발생한다.
다음과 같이 수정하자.
const Sequelize = require('sequelize');
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;
module.exports = db;
Sequelize로 시퀄라이즈를 생성한 뒤 cofig로 설정을 불러와 MySQL 연결 객체를 생성한다.
이제 MySQL과 연결하자.
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const nunjucks = require('nunjucks');
const { sequelize } = require('./models');
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 })
.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.set(err.status || 500);
res.render('error');
});
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 대기중....');
});
db.sequelize를 불러와서 sync 메서드를 사용해 서버 실행 시 MySQL과 연동되도록 했다.
force: false 옵션을 true로 설정하면 서버 실행 시마다 테이블을 재생성한다.
config를 조금 수정하면 정상적으로 동작할 것이다.
{
"development": {
"username": "root",
"password": "비밀번호",
"database": "node.js",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": null,
"database": "database_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
NODE_ENV에 따라 적용되는 config가 달라진다.
모델 정의하기
MySQL에서 정의한 테이블을 시퀄라이즈에서도 정의해야 한다. MySQL의 테이블은 시퀄라이즈의 모델과 대응된다.
시퀄라이즈는 모델과 MySQL의 테이블을 연결해주는 역할을 한다.
User와 Comment 모델을 만들어 users 테이블과 comments 테이블에 연결해보자.
시퀄라이즈는 기본적으로 모델 이름은 단수형, 테이블 이름은 복수형으로 사용한다.
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,
},
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,
timestamps: false,
underscored: false,
modelName: 'User',
tableName: 'users',
paranoid: false,
charset: 'utf8',
collate: 'utf8_general_ci',
});
}
static associate(db) {
db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id' });
}
};
models/user.js
User 모델을 만들고 모듈로 exports 했다. User 모델은 Sequelize.Model을 확장한 클래스로 선언한다.
모델은 크게 static init과 static associate로 나뉜다.
init은 테이블에 대한 설정을 하고, associate는 다른 모델과의 관계를 적는다.
paranoid를 true로 설정하면 deletedAt이라는 컬럼이 생긴다. 로우를 삭제할 때 완전히 지워지지 않고 deletedAt에 지운 시각이 기록된다. 로우를 복원해야 하는 상황이 생길 것을 대비한다.
const Sequelize = require('sequelize');
module.exports = class Comment 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) {
db.Comment.belongsTo(db.User, { foreignKey: 'commenter', targetKey: 'id' });
}
};
model/comment.js
이제 정의한 모델을 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;
관계 정의하기
user 테이블과 comments 테이블은 관계가 있는 테이블이다. 사용자 한 명은 댓글을 여러 개 작성할 수 있다. 하지만 댓글 하나에 사용자가 여러 명일 수는 없다. 이러한 관계를 일대다 관계라고 한다.
시퀄라이즈에서는 1:N 관계를 hasMany라는 메서드로 표현한다. users 테이블의 로우 하나를 불러올 때 연결된 comments 테이블의 로우들도 같이 불러올 수 있다. 반대로 belongsTo 메서드로는 comments 테이블의 로우를 불러올 때 users 테이블의 로우도 가져온다.
static associate(db) {
db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id' });
}
static associate(db) {
db.User.belongsTo(db.User, { foreignKey: 'commenter', targetKey: 'id' });
}
앞서서 정의한 associate에 채워 넣자.
서버를 실행해보자.
위와 같은 쿼리가 생성된다.
hasMany, belongsTo 말고도 hasOne, belongsToMany 메서드도 있다.
각각은 1:1, N:M 관계에 사용한다.
시퀄라이즈 쿼리
쿼리는 프로미스를 반환하므로 then을 붙여 결괏값을 받을 수 있다.
create 메서드로 insert 할 수 있다.
const { Uesr } = require('../models');
User.create({
name: 'GR8',
age: 25,
married: false,
comment: '자기소개',
});
주의할 사항은 MySQL의 자료형이 아니라 시퀄라이즈 모델에 정의한 자료형태로 넣어야 한다.
그러면 시퀄라이즈가 알아서 MySQL 자료형으로 바꾼다.
findAll 메서드로는 조회할 수 있다.
User.findAll({});
{} 안에 attributes 옵션을 넣어 원하는 컬럼만 가져올 수도 있다. where 옵션으로는 조건을 부여할 수 있다.
const { Op } = require('sequelize');
cosnt { User } = require('../models');
User.findAll({
attirbutes: ['name', 'age'],
where: {
married: true,
age: { [Op.gt]: 30 },
},
});
Op객체는 연산자들을 나타내는 객체이다. gt는 grater than이다.
attributes, where 옵션 이외에도 limit, offset 등의 옵션이 있다.
update 메서드로 수정할 수 있다.
User.update({
comment: '바꿀 내용',
}, {
where: {id: 2},
});
destory로 삭제할 수 있다.
User.destory({
where: { id: 2 },
});
관계 쿼리를 이용하면 더욱 편리하게 서버를 구현할 수 있다.
현재 User모델은 Comment 모델과 hasMany-belongsTo 관계가 있다.
include 속성을 사용하면 Join을 할 수 있다.
const user = await User.findOnd({
include: [{
model: Commnet,
}]
});
console.log(user.Comments);
이 방법 외에도 미리 관계가 있는 모델끼리는 메서드를 지원해준다.
db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id', as: 'Answers' });
const user = await User.findOne({});
const comments = await user.getAnswers();
console.log(comments);
as는 별칭을 설정하는 것이다.
수정, 생성, 삭제는 다음과 같이 할 수 있다.
const user = await User.findOne({});
const comment1 = await Comment.create();
const comment2 = await Comment.create();
await user.addComment([comment1, commet2]);
SQL을 직접 날릴 수도 있다.
const [result, metadata] = await sequelize.query('SELECT * from comments');
console.log(result);