ORM이란?
- 데이터베이스를 객체로 추상화한 것
- 쿼리를 직접 작성하지 않고 ORM의 메서드로 데이터를 관리할 수 있음
- 노드에서의 SQL ORM은 시퀄라이져(Sequelize)
메서드 예시)
- insert users ('name') values ('alice');
→ User.create({name:'alice');
- select * from users;
→ User.findAll();
- update users set name = 'bek' where id = 1;
→ User.update({name:'bek'}, {where: {id:1}});
- delete from users where id = 1;
→ destroy({where: {id: 1}});
모델
- 데이터베이스 테이블을 ORM으로 추상화한 것
- 모델 정의: sequelize.define()
- 데이터베이스 연동: sequelize.sync()
모델 정의 및 DB 연동 과정
- npm i sequelize sqlite3
(ORM과 DB 모듈 추가) - 참고로 sqlite3는 경로에 한글이 포함되어 있으면 설치가 안됨 - models.js 파일 추가
const Sequelize = require('sequelize'); const sequelize = new Sequelize({ dialect: 'sqlite', storage: './db.sqlite' }); const User = sequelize.define('User', { name: Sequelize.STRING //varchar 255 }); module.exports = {Sequelize, sequelize, User};
- require('sequelize')로 sequelize 모듈을 가져옴
- sequelize = new Sequelize로 객체를 만듦 -> 디비 연동에 필요한 속성 정보 입력
- sequelize.define으로 User라는 모델을 정의
- 사용할 모델(User), sequelize, Sequelize exports!
- sync-db.js 추가
const models = require('../models'); module.exports = () =>{ return models.sequelize.sync({force: true}); }
- 모델을 가져와 디비와 sync 하는 코드
- 속성 값 force를 true를 주게 되면 기존에 DB가 존재하더라도 지우고 새로 만든다는 의미
- DB sync 시점 잡기
- index.js가 starting point인 경우 index.js에서 sync-db를 실행해야 함
- index.js가 app 역할을 하고 www.js에서 서버를 돌려주도록 한 경우 starting point는 www.js
const app = require('../'); //서버를 돌리기 전에 db 연동 const syncDb = require('./sync-db'); syncDb().then(_=>{ console.log('Sync database!'); app.listen(3000, ()=>{ console.log("Server is running on 3000 port") }); });
API - DB 연동
예시 코드
user.spec.js
//test 코드
const request = require('supertest');
const should = require('should');
const app = require('../../');
const models = require('../../models');
describe.only('GET /users는', () => {
describe('성공시', () => {
const users = [{name: 'alice'}, {name: 'bek'}, {name: 'chris'}];
before(() => models.sequelize.sync({force: true}));
before(() => models.User.bulkCreate(users));
it('유저 객체를 담은 배열로 응답한다 ', (done) => {
request(app)
.get('/users')
.end((err, res) => {
res
.body
.should
.be
.instanceOf(Array);
done();
});
});
it('최대 limit 갯수만큼 응답한다 ', (done) => {
request(app)
.get('/users?limit=2')
.end((err, res) => {
res
.body
.should
.have
.lengthOf(2);
done();
});
});
});
describe('실패시 ', () => {
it('limit이 숫자형이 아니면 400을 응답한다', (done) => {
request(app)
.get('/users?limit=two')
.expect(400)
.end(done);
});
});
});
- before(mocker)를 사용하여 db-sync를 해줌
(before: it 실행되기 전에 실행되는 후커(hooker) 함수) - before에 done을 가지고 있는 콜백 함수를 넣어준 이유:
- db-sync는 비동기로 작동하기 때문(파일 접근) - model.sequelize.sync({force: true}). then(_=> done());
-> mocker에서는 Promise를 리턴하면 자동으로 비동기의 완료를 보장해줌
-> before(()=> models.sequalize.sync({force: true})); - bulkCreate로 샘플데이터를 입력해줌
- it.only: 이 테스트 메서드만 실행
user.ctrl.js
const models = require('../../models');
const index = function (req, res){
req.query.limit = req.query.limit || 10;
const limit = parseInt(req.query.limit,10);
if(Number.isNaN(limit)){
return res.status(400).end();
}
models.User
.findAll({
limit: limit
})
.then(users=>{
res.json(user);
})
//res.json(users.slice(0, limit));
};
- 파라미터로 받은 limit를 findAll의 limit 속성에 넣어줌 -> select 개수 제한
- User테이블에 있는 정보를 모두 가져와 json형태로 응답
결과
※ sequalize의 logging 속성을 false로 주면 더 깔끔하게 볼 수 있다.
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: './db.sqlite',
logging: false,
});
그 외의 API 테스트
컨트롤러
//api 로직
const models = require('../../models');
const index = function (req, res) {
req.query.limit = req.query.limit || 10;
const limit = parseInt(req.query.limit, 10);
if (Number.isNaN(limit)) {
return res.status(400).end();
}
models
.User
.findAll({limit: limit})
.then(users => {
res.json(users);
})
//res.json(users.slice(0, limit));
};
const show = function (req, res) {
const id = parseInt(req.params.id, 10);
if (Number.isNaN(id))
return res.status(400).end();
//const user = users.filter(user => user.id === id)[0];
models
.User
.findOne({
where: {id}
})
.then(user => {
if (!user)
return res
.status(404)
.end();
res.json(user);
})
}
const destory = (req, res) => {
const id = parseInt(req.params.id, 10);
if (Number.isNaN(id))
return res.status(400).end();
//users = users.filter(user => user.id !== id);
models.User.destroy({
where: {id}
}).then(() => {
res.status(204).end();
});
}
const create = (req, res) => {
const name = req.body.name;
if (!name)
return res.status(400).end();
// const isConflict = users.filter(user => user.name === name).length
// if (isConflict)
// return res.status(409).end();
// const id = Date.now();
// const user = {
// id,
// name
// };
// users.push(user);
models.User.create({name})
.then(user =>{
res.status(201).json(user);
})
.catch(err=>{
if(err.name === 'SequelizeUniqueConstraintError'){
return res.status(409).end();
}
res.status(500).end();
})
}
const update = (req, res) => {
const id = parseInt(req.params.id, 10);
if (Number.isNaN(id))
return res
.status(400)
.end();
const name = req.body.name;
if (!name)
return res
.status(400)
.end();
// const isConflict = users.filter(user => user.name === name).length
// if (isConflict) return res.status(409).end();
// const user = users.filter(user => user.id === id)[0];
// if (!user) return res.status(404).end();
//user.name = name;
models.User.findOne({where: {id}})
.then(user =>{
if(!user) return res.status(404).end();
user.name = name;
user.save()
.then(_=>{
res.json(user);
})
.catch(err =>{
if (err.name == 'SequelizeUniqueConstraintError'){
return res.status(409).end();
}
res.status(500).end();
});
});
}
module.exports = {
// index:index, show:show, destory:destory, create:create, update:update,
//ES6 문법
index,
show,
destory,
create,
update
};
테스트 코드
//테스트 코드
const request = require('supertest');
const should = require('should');
const app = require('../../');
const models = require('../../models');
describe('GET /users는', () => {
const users = [{name: 'alice'}, {name: 'bek'}, {name: 'chris'}];
before(() => models.sequelize.sync({force: true}));
before(() => models.User.bulkCreate(users));
describe('성공시', () => {
it('유저 객체를 담은 배열로 응답한다 ', (done) => {
request(app)
.get('/users')
.end((err, res) => {
res
.body
.should
.be
.instanceOf(Array);
done();
});
});
it('최대 limit 갯수만큼 응답한다 ', (done) => {
request(app)
.get('/users?limit=2')
.end((err, res) => {
res
.body
.should
.have
.lengthOf(2);
done();
});
});
});
describe('실패시 ', () => {
it('limit이 숫자형이 아니면 400을 응답한다', (done) => {
request(app)
.get('/users?limit=two')
.expect(400)
.end(done);
});
});
});
describe('GET /users/1는', () => {
const users = [{name: 'alice'}, {name: 'bek'}, {name: 'chris'}];
before(() => models.sequelize.sync({force: true}));
before(() => models.User.bulkCreate(users));
describe('성공시 ', () => {
it('id가 1인 유저 객체를 반환한다', (done) => {
request(app)
.get('/users/1')
.end((err, res) => {
res
.body
.should
.have
.property('id', 1);
done();
});
});
});
describe('실패시 ', () => {
it('id가 숫자가 아닐 경우 400으로 응답한다.', (done) => {
request(app)
.get('/users/one')
.expect(400)
.end(done)
});
it('id로 유저를 찾을 수 없는 경우 404로 응답한다.', (done) => {
request(app)
.get('/users/999')
.expect(404)
.end(done)
});
});
});
describe('DELETE /users/1', () => {
const users = [{name: 'alice'}, {name: 'bek'}, {name: 'chris'}];
before(() => models.sequelize.sync({force: true}));
before(() => models.User.bulkCreate(users));
describe('성공시', () => {
it('204를 응답한다', (done) => {
request(app)
.delete('/users/1')
.expect(204)
.end(done)
});
});
describe('실패시', () => {
it('id가 숫자가 아닌 경우 400으로 응답한다', (done) => {
request(app)
.delete('/users/one')
.expect(400)
.end(done)
});
});
});
describe('POST /users', () => {
const users = [{name: 'alice'}, {name: 'bek'}, {name: 'chris'}];
before(() => models.sequelize.sync({force: true}));
before(() => models.User.bulkCreate(users));
describe('성공시', () => {
let name = 'bigring';
let body;
before(done => {
request(app)
.post('/users')
.send({name})
.expect(201)
.end((err, res) => {
body = res.body;
done();
})
})
it('생성된 유저 객체를 반환한다 ', () => { //비동기 테스트가 아니므로 done 필요x
body
.should
.have
.property('id');
});
it('입력한 name을 반환한다 ', () => {
body
.should
.have
.property('name', name)
});
});
describe('실패시', () => {
it('name 파라미터 누락시 400을 반환한다', (done) => {
request(app)
.post('/users')
.send({})
.expect(400)
.end(done)
});
it("name이 중복일 경우 409를 반환한다", done => {
request(app)
.post('/users')
.send({name: 'bigring'})
.expect(409)
.end(done)
});
});
});
describe('PUT /users/:id', () => {
const users = [{name: 'alice'}, {name: 'bek'}, {name: 'chris'}];
before(() => models.sequelize.sync({force: true}));
before(() => models.User.bulkCreate(users));
describe('성공시', () => {
it('변경된 name을 응답한다', (done) => {
const name = 'den';
request(app)
.put('/users/3')
.send({name})
.end((err, res) => {
res
.body
.should
.have
.property('name', name);
done();
});
});
});
describe('실패시', () => {
it('정수가 아닌 id일 경우 400을 응답한다 ', (done) => {
request(app)
.put('/users/one')
.expect(400)
.end(done);
});
it('name이 없는 경우 400을 응답한다 ', (done) => {
request(app)
.put('/users/1')
.expect(400)
.end(done);
});
it('없는 유저일 경우 404을 응답한다 ', (done) => {
request(app)
.put('/users/999')
.send({name: 'foo'})
.expect(404)
.end(done);
});
it('이름이 중복일 경우 409을 응답한다 ', (done) => {
request(app)
.put('/users/3')
.send({name: 'bek'})
.expect(409)
.end(done);
});
});
});
모델
const Sequelize = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: './db.sqlite',
logging: false,
});
const User = sequelize.define('User', {
name: {
type: Sequelize.STRING, //varchar 255
unique: true
}
});
module.exports = {Sequelize, sequelize, User};
- name속성에 unique를 주어 이름이 중복인 경우에 catch로 빠지게 하였다.
Reference
'Javascript > Node.js' 카테고리의 다른 글
passport 적용 (2) | 2020.07.08 |
---|---|
SuperTest 연습 (0) | 2020.06.11 |
테스트 주도 개발(TDD) (0) | 2020.06.10 |
익스프레스JS (0) | 2020.06.10 |
NodeJS의 특징 (0) | 2020.06.09 |
댓글