업무에서 MySQL을 쓰기 때문에 쿼리 처리를 위해 knex를 사용하고 있다. knex는 SQL query builder 라이브러리인데 사용이 쉽고 직관적이기 때문에 업무에서 사용해도 무리가 없다. 자세한 내용은 https://knexjs.org 에서 찾아볼 수 있다.
설치
우선 다음 명령으로 설치한다.
npm install knex --save
우선 DB 정보를 이용해 knex 객체를 생성한다.
1 2 3 4 5 6 7 8 9 | const knex = require('knex')({ client: 'mysql', connection: { host : HOST_ADDR, user : DB_USER, password : DB_PASSWORD, database : DB_NAME } }) | cs |
CRUD
이 때 생성한 knex 객체를 이용해서 쿼리를 생성하면 되는데, 유저 테이블에서 유저 정보를 읽는 쿼리는 아래 코드처럼 간단하다.
1 | knex.select('id', 'name', 'age').from('users') | cs |
1 | knex('users').insert({ name:'James Kook', age: 20 }) | cs |
1 | knex('users').update({ age: 21 }).where('id', 2) | cs |
유저를 삭제하는 코드는 다음과 같다.
1 | knex('users').del().where('id', 2) | cs |
JOIN
유저 테이블과 문서 테이블을 조인해 아이디 2 유저의 문서를 변경 시간 역순으로 나열하는 쿼리는 다음과 같다. 이 때 리턴되는 값은 JSON 배열이며 map() 함수를 이용해 각 레코드를 대상으로 함수를 호출할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | const { userId } = params const userDocs = await knex('users AS u') .select('u.id', 'u.name', 'u.age', 'd.id AS doc_id', 'd.name AS doc_name', 'd.created_at', 'd.updated_at') .join('documents as d', 'u.id', 'd.user_id') .where('u.id', userId) .orderBy('d.updated_at', 'DESC') .map(r => ({ userId: r.id, userName: r.name, userAge: r.age, docId: r.doc_id, docName: r.doc_name, docCreatedAt: r.created_at, docUpdatedAt: r.updated_at })) | cs |
Migration
스키마를 변경하거나 데이터베이스를 업그레이드하려고 할 때에는 knex migration 명령을 이용할 수 있다. 우선 knex를 글로벌로 설치하고 knex init 명령을 실행하여 knexfile.js 파일을 생성한다. 이후 knexfile.js 파일을 열어 데이터베이스 정보를 입력한다.
npm install knex -g
knex init
아래 예제는 development 환경에서 사용할 목적으로 만든 예제이다. 서버 환경에 따라 staging, production의 데이터베이스 정보를 입력해야 할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 | module.exports = { development: { client: 'mysql', connection: { timezone: 'UTC', host: HOST_ADDR, database: DATABASE_NAME, user: DB_USER, password: DB_PASSWORD } } } | cs |
다음은 migration을 통해 새로운 테이블을 추가하는 예제이다.
knex migrate:make add-attractions-table
1 2 3 4 5 6 7 8 9 10 | const table = 'attractions' exports.up = async knex => knex.schema.createTable(table, t => { t.increments('id').primary() t.string('name') t.string('address') }) .then(() => knex(table).insert({ id: 1, name: 'Big Ben', address: 'Westminster, London SW1A 0AA'})) exports.down = async knex => knex.schema.dropTableIfExists(table) | cs |
knex migration이 정상적으로 완료되면 해당 데이터베이스의 knex_migrations 테이블에 실행된 migration이 추가되는 것을 확인할 수 있다. 이 테이블을 참고하면 보유한 migration 파일들 중에서 실행되지 않은 migration을 찾을 수 있다.
만일 문제가 발생하여 migration을 취소해야 할 경우 migration 파일의 down() 함수를 실행하여 취소할 수 있는데 다음은 이 때 실행하는 명령이다.
knex migrate:rollback
좀 복잡해 보이는 서브 쿼리도 조금만 익숙해지면 쉽게 만들 수 있고, raw query도 실행할 수 있다. Response 시 호출될 함수를 등록할 수 있는데, 이 기능을 이용해 긴 response time을 갖는 쿼리를 찾아 최적화할 수도 있다. 2년 넘게 사용해왔는데 딱히 불만이 없는 라이브러리다. 다음 사용하게 될 데이터베이스가 MySQL이라면 또 이 라이브러리를 최우선으로 고려할 생각이다.