Node knex Migration
Schema Alter
Migration하는 방법
1 2 3
| $ npm install knex -g
$ knex init
|
- knexfile.js가 생성된다 이것으로 앞으로 migration을 한다.(초기모습)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
module.exports = {
development: { client: 'sqlite3', connection: { filename: './dev.sqlite3' } },
staging: { client: 'postgresql', connection: { database: 'my_db', user: 'username', password: 'password' }, pool: { min: 2, max: 10 }, migrations: { tableName: 'knex_migrations' } },
production: { client: 'postgresql', connection: { database: 'my_db', user: 'username', password: 'password' }, pool: { min: 2, max: 10 }, migrations: { tableName: 'knex_migrations' } }
};
|
development, staging, production 3가지로 구성되어있다.
NODE_ENV=production node src/index.js
형식으로 실행을 한다 production값을 변경가능하며 default값은 development이다.
debug는 devlopment에서는 사용하고 product에서는 사용하지 않는다.
각자의 환경에서 설치할 수 있는데 개발에서 사용되는 패키지를 운영에다가 넣을 필요가없으니 개발환경에서만 사용하는 것은 --save-dev로 설치해둔다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| require('dotenv').config()
module.exports = {
development: { client: 'mysql', connection: { host: process.env.DATABASE_HOST, user: process.env.DATABASE_USER, password: process.env.DATABASE_PASSWORD, database: process.env.DATABASE_NAME }, debug: true },
staging: { client: 'postgresql', connection: { database: 'my_db', user: 'username', password: 'password' }, pool: { min: 2, max: 10 }, migrations: { tableName: 'knex_migrations' } },
production: { client: 'postgresql', connection: { database: 'my_db', user: 'username', password: 'password' }, pool: { min: 2, max: 10 }, migrations: { tableName: 'knex_migrations' } }
};
|
Knex Migration 생성법
1
| $ knex migrate:make migration_name
|
migrations라는 폴더에 migration_name 파일이 생긴다.
up
: 데이터베이스를 변경하는데 사용한다.
down
: 변경된 값을 되돌리는데 사용한다.
migration을 사용하여서 table을 생성하는 방법
- 1번 더 같은 명령어를 실행하면 이미 저장되어있다고 알려준다.
migration 취소방법
migration은 한번 할 때 한번씩 하는 것이 좋다.(작은 단위로 실행을 추천)
- 이제 schema.js는 필요가 없게 되었다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const knex = require('./knex')
knex.schema.createTable('user', t => { t.string('id').primary() t.string('password').notNullable() }).then(() => knex.schema.createTable('url_entry', t => { t.string('id', 8).primary() t.string('long_url').notNullable() t.string('user_id') t.foreign('user_id').references('user.id') t.timestamp('created_at').defaultTo(knex.fn.now()) })).then(process.exit)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| exports.up = function(knex, Promise) { return knex.schema.createTable('user', t => { t.string('id').primary() t.string('password').notNullable() }) };
exports.down = function(knex, Promise) { return knex.schema.dropTable('user') };
exports.up = function(knex, Promise) { return knex.schema.createTable('url_entry', t => { t.string('id', 8).primary() t.string('long_url').notNullable() t.string('user_id') t.foreign('user_id').references('user.id') t.timestamp('created_at').defaultTo(knex.fn.now()) }) };
exports.down = function(knex, Promise) { return knex.schema.dropTable('url_entry') };
|
Database 동시성 문제
접속한 링크에 count를 1씩 올릴때 동시에 1000번의 요청을 보낼 경우에 값을 제대로 반영을 못한다는 문제가 발생한다. 아래의 예를 보면 알 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
saveClickCountById(id, click_count) { return knex('url_entry') .where({id}) .update({click_count}) }
app.get('/:id', (req, res, next) => { query.getUrlById(req.params.id) .then(urlEntry => { if(urlEntry){ query.saveClickCountById(urlEntry.id, urlEntry.click_count+1) .then(() => { res.redirect(urlEntry.long_url) }) }else { next() } }) })
|
값을 업데이트하기전에 계속 값이 덮어씌어지는 문제가 발생한다.
해결방법
- 잠금 => 성능이 느려진다. DeadLock이 발생할 확률이 생긴다.
Atomic Update
원자적 갱신
- 명령을 내릴 때 자동으로 증가시킨다.
- knex의
.increment
를 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| incrementClickCountById(id) { return knex('url_entry') .where({id}) .increment('click_count', 1) }
app.get('/:id', (req, res, next) => { query.getUrlById(req.params.id) .then(urlEntry => { if(urlEntry){ query.incrementClickCountById(urlEntry.id) .then(() => { res.redirect(urlEntry.long_url) }) }else { next() } }) })
|
Atomic Update를 하면 데이터베이스에 접속해서 가져오는 것이 아닌 바로 값을 올려버리기 때문에 제대로 값이 나타난다.
bcrypt를 사용한 보안
bcrypt
1 2 3 4 5 6 7 8 9 10 11 12 13
| > npm install --save bcrypt
> node > bcrypt = require('bcrypt') > bcrypt.hashSync('campus', 10) > hash = bcrypt.hashSync('campus', 10)
> bcrypt.compareSync('campus', hash) true > bcrypt.compareSync('campus1', hash) false
|
To check a password
1 2 3
| bcrypt.compareSync(myPlaintextPassword, hash); bcrypt.compareSync(someOtherPlaintextPassword, hash);
|
동기의 방식을 사용하는 위에 script보다 아래의 방식이 좋다.
1 2 3 4 5 6 7
| bcrypt.compare(myPlaintextPassword, hash).then(function(res) { }); bcrypt.compare(someOtherPlaintextPassword, hash).then(function(res) { });
|
npm validator
많은 종류의 validation이 생성되어있으며 값이 맞는지 검증해주는 역할을 한다.
1 2
| > npm install validator --save
|
Error 처리방법
Flash : error를 session 저장해두었다가 다음 요청이 왔을 때 error 메세지를 보여준다.
1 2
| > npm install connect-flash --save
|
session을 사용하면 여러 요청에 걸친 정보를 유지할 수 있다.