728x90
반응형

기존에 mysql로 dbms를 사용하여 서비스를 만들어둔 상태인데, postgresql의 장점이 만들고자 하는 서비스에 적합하다고 생각하여 바꾸게되었다!

똑같이 Sequelize 사용이 가능하며 연결 옵션만 수정해주면 바로 연결과 sync가 가능했다.

 

기존에 사용하던 mysql과 postgresql 둘다 AWS RDS를 사용했고, 옵션을 크게 바꿔준 것은 없었다.

 

기존 : Mysql 사용

// config

    "mysql": {
        "host": "", //mysql host
        "username": "", //mysql user
        "password": "", //mysql password
        "database": "", //mysql database
        "dialect": "mysql",
        "timezone": "+09:00"
    },
// sequelize.js

import Sequelize from 'sequelize';
import config from 'config';
import {models} from '../src/models/index.js';

const sequelize = new Sequelize(
    config.get('mysql.database'),
    config.get('mysql.username'),
    config.get('mysql.password'),
    {
      host: config.get('mysql.host'),
      dialect: config.get('mysql.dialect'),
      timezone: config.get('mysql.timezone'), // 예시: 대한민국 시간대에 맞는 옵션 설정
    },
);

 

변경 : Postgresql 사용

// config

    "postgres": {
        "host": "", //postgres host
        "username": "", //postgres user
        "password": "", //postgres password
        "database": "", //postgres database
        "dialect": "postgres",
        "timezone": "+09:00"
    },
// sequelize.js

import Sequelize from 'sequelize';
import config from 'config';
import {models} from '../src/models/index.js';

const sequelize = new Sequelize(
    config.get('postgres.database'),
    config.get('postgres.username'),
    config.get('postgres.password'),
    {
      host: config.get('postgres.host'),
      dialect: config.get('postgres.dialect'),
      timezone: config.get('postgres.timezone'), // 예시: 대한민국 시간대에 맞는 옵션 설정
      dialectOptions: {
        ssl: {
          require: true,
          rejectUnauthorized: false,
        },
      },
    },
);

특징적으로는 dialectOptions 옵션이 추가됐다. 저 옵션이 없으면 아래 에러와 같이 SSL에서 막혀서 연결이 되지 않는다

Unable to connect to the database: ConnectionError [SequelizeConnectionError]: no pg_hba.conf entry for host "222.109.176.119", user "test", database "postgres", no encryption at Client._connectionCallback (C:\Users\sowon\project\kkosunae\node_modules\sequelize\lib\dialects\postgres\connection-manager.js:143:24) at Client._handleErrorWhileConnecting (C:\Users\sowon\project\kkosunae\node_modules\pg\lib\client.js:327:19) at Client._handleErrorMessage (C:\Users\sowon\project\kkosunae\node_modules\pg\lib\client.js:347:19) at Connection.emit (node:events:513:28) at Connection.emit (node:domain:489:12) at C:\Users\sowon\project\kkosunae\node_modules\pg\lib\connection.js:117:12 at Parser.parse (C:\Users\sowon\project\kkosunae\node_modules\pg-protocol\dist\parser.js:40:17) at Socket.<anonymous> (C:\Users\sowon\project\kkosunae\node_modules\pg-protocol\dist\index.js:11:42) at Socket.emit (node:events:513:28) at Socket.emit (node:domain:489:12) { 
...

rejectUnauthorizedfalse로 설정하면, 서버가 self-signed 인증서를 사용하는 경우에도 연결 가능해진다.

보안상의 이유로 실제 운영에서는 rejectUnauthorizedtrue로 설정하고 서버의 인증서를 신뢰할 수 있는 것으로 구성하는 것이 좋다!

 

시행착오

문제 : 처음에 Postgresql을 연결하는 과정에서 위 에러가 발생

시도1 : RDS로 만든 postgresql은 pg_hba.conf를 직접 수정하기 번거로워, 파라미터 그룹으로 설정하려함.


여러 레퍼런스를 찾아서 RDS의 파라미터 그룹을 생성하고 rds.force_ssl을 0으로 설정하는 등 적용을 해봤지만 에러는 사라지지 않음

시도2 : 파라미터 그룹 원복 후 dialectOptions 을 추가하여 재시도. -> 성공 !

728x90
반응형
728x90
반응형

기존의 백엔드에서 스키마 변경이 이루어지면 DB에서 수정 후, 코드를 수정했다.

나는 여태까지 그렇게 수정해왔는데, Sequelize의 sync기능을 이용하면 model만 수정해도 DB가 알아서 수정되는 마술이 있다는 것을 발견했다.

 

Sequelize는 DML(데이터 조작)뿐만 아닌 DDL(데이터 정의)도 지원한다는 특징 덕분인데 


이러한 특징 덕분에 이미 만들어진 테이블을 모델에 매핑할 수 있을 뿐 아니라, 작성한 모델을 바탕으로 테이블을 생성하는 등 다룰 수 있다.

아래는 sync를 적용하는 방법이다.

'use strict';

const _sequelize = require('sequelize');
const _config = require('config');

const models = require('../models/index');

const sequelize = new _sequelize(
    _config.get('mysql_local.database'),
    _config.get('mysql_local.username'),
    _config.get('mysql_local.password'),
    {
        host: _config.get('mysql_local.host'),
        dialect: _config.get('mysql_local.dialect'),
        logging: (message) => {
            console.log(message);
        },
        timezone: _config.get('mysql_local.timezone'), // Asia/Seoul
    },
);

// DB 스키마 Sync
sequelize.sync({force: true})

 

이런 방법이 운영중인 서비스에 적용하는 것은 당연히 문제가 있고, 서비스를 만들어가는 과정에서 스키마 구조를 변경하거나 개편할 때 편의에 따라 사용하면 될 것 같다.

기존 테이블을 지우고 재생성하면 데이터를 모두 지운다는 점에서 치명적일 수 있으니 꼭 작업 전에 고려하고 진행해야한다!

728x90
반응형
728x90
반응형

이미 winston으로 error나 warning은 추적이 되도록 셋팅되어있지만, 발생한 정확한 쿼리는 알 수 없다.

그래서 쿼리를 저장하고자 하는데,
경험상 파일시스템에 쿼리를 로깅하는것이 빠르고 부하도 적긴 하지만 분석이 쉽지 않았다. 

때문에 사이즈가 작은 프로젝트에는 DB에 저장하는 방식을 사용해서 큰 문제가 없을 것이라고 예상되어, 쿼리를 DB에 로깅하는 방식을 구현해보고자 한다.

쿼리를 DB에 로깅하는 방식은 흔하지 않을 것이라고 생각되지만, 프로젝트의 특징에 따라 필요한 경우가 있다고 생각한다. 이번에 직접 경험해보면서 장단점을 파악해보려고 한다 !!

 

1. 로그 테이블 생성

가장 기본적인 데이터인 id, 쿼리, 시간만 담을 수 있도록 로그 테이블을 생성했다.

CREATE TABLE `logs` (
  `id` int NOT NULL AUTO_INCREMENT,
  `message` text NOT NULL,
  `createdAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `updatedAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

 

2. Sequelize 셋팅

const sequelize = new _sequelize(
    _config.get('mysql_local.database'),
    _config.get('mysql_local.username'),
    _config.get('mysql_local.password'),
    {
        host: _config.get('mysql_local.host'),
        dialect: _config.get('mysql_local.dialect'),
        logging: (message) => {
            console.log(message);
	    // 로깅을 위한 로깅이 안되도록 예외처리
            if (!message.includes('INSERT INTO `logs`')) {
                logToDatabase(message);
            }
        },
        timezone: _config.get('mysql_local.timezone'), // Asia/Seoul
    },
);

// 로그 테이블 생성
const Log = sequelize.define('log', {
    message: {
        type: _sequelize.STRING,
        allowNull: false,
    },
});

// DB에 로그 저장
const logToDatabase = (message) => {
    Log.create({ message });
};

기존 Sequelize 셋팅의 logging 프로퍼티만 일부 수정했다. 기존에는 콘솔에 로깅하는 작업만 되어있었지만, logToDatabase 함수가 추가되어 이제 발생하는 쿼리를 모두 DB에 로깅한다. 

처음에는 로그를 삽입하는 부분을 예외처리 안했더니 아래 사진처럼 로그를 로깅하기위한 무한루프가 발생했다. 때문에 INSERT INTO `logs' 를 포함한 쿼리는 로깅되지 않도록 예외처리했다.

 

728x90
반응형
728x90
반응형

이전 게시물에서 JS 시간대를 바꾸는 방법을 써봤을 때, console.log를 하면 한국 현재 시간으로 나왔다.

근데 그렇게 한국시간으로 적용된 값을 삽입해도 DB에는 9시간 전으로 삽입이됐다 ㅜㅜ

 

OS, MySQL, Node.js 모두 시간대를 맞춰도 이상해서 .. 마지막 원인은 DB를 사용하기 위한 모듈인 Sequelize 에게 있을 것이라고 추측했다 ㅜ 역시나 관련해서 찾아본 결과 Sequelize 기본 시간대가 utc라는 레퍼런스를 확인했다..

 

Sequelize timezone를 한국 시간으로 바꾸기 위해 Sequelize 선언부에 없었던의 타임존 시간을 바꿔주었다.

 

// sequelize 선언부(mysql 연결 객체)에 없었던 timezone 설정을 추가하였다.
const sequelize = new _sequelize(
    _config.get('mysql_local.database'),
    _config.get('mysql_local.username'),
    _config.get('mysql_local.password'),
    {
        host: _config.get('mysql_local.host'),
        dialect: _config.get('mysql_local.dialect'),
        logging: _config.get('server.state') === 'production' ? false : console.log,
        timezone: _config.get('mysql_local.timezone'), // 기존에 없었던 줄
    },
);

// config 부분
    "mysql_local": {
        "host": "localhost",
        "username": "root",
        "password": "1111",
        "database": "test",
        "dialect": "mysql",
        "timezone": "Asia/Seoul"
    },

이제 진짜 해결 ! 인코딩, 시간대 이런부분은 항상 까다롭다 ㅜㅜ

728x90
반응형
728x90
반응형

express-validator는 express에서 유효성 검사를 제공하는 미들웨어다.

POST API를 개발할 때, body값의 유효성 검증은 필수적이다. express-validator는 notEmpty, isIn, isLength 와 같이 빈값, 길이, 정규식 체크 등 간편하고 직관적으로 검증할 수 있는 기능을 제공해서 프로젝트에 적용하고자 한다.

 

유효성 검증하기

유효성을 검증하기위해 express-validator에서 제공하는 함수는 body, check 등이 있는데 대표적인 이 둘의 차이는 다음과 같다.

check : 검증과 확인이 실패했을때 에러메시지를 따로 설정할 수 있다.

body : 검증만 한다. 

check('password', "Password is required").notEmpty();
body('password').notEmpty();

위와 같이 check와 body는 인자가 다르지만, check, body 둘다 메시지를 설정하기 위해 withMessage를 하는 방법도 있다. 둘 중 무엇을 사용하느냐는 취향차이고 에러메시지를 따로 설정하지 않아도 기본적으로 "Invalid value"로 설정되어있기 때문에 메시지 설정이 필수는 아니다.

다만, check의 두번째 인자인 error message는 선택 사항이라 error message를 입력하지 않아도 body와 동일하게 사용할 수 있기때문에 나는 check를 더 편하게 사용할 것 같다!


아래는 기존 라우터에 express-validator를 적용하는 방법이다. 전후 비교는 게시물 하단에 있다.

1. 설치 : Node.js 버전이 8 인지 확인 후 설치한다

npm install --save express-validator

 

2. 선언을 라우터에 추가한다

const { body, validationResult } = require('express-validator');

 

3. 에러 노출을 위한 미들웨어 추가

const validator = (req, res, next) => {
    const errors = validationResult(req);
    if (errors.isEmpty()) {
        return next();
    }
    return res.status(400).json({ message: errors.array()[0].msg });
};

 

4. 라우터 두번째 인자에 검증 추가

// body 를 사용할 경우
_router.post('/', [body('type').notEmpty().withMessage('Type is required'), validator], ctrl.createReservation);

// check 를 사용할 경우
_router.post('/', [check('type', 'Type is required').notEmpty(), validator], ctrl.createReservation);

 

적용 전

const { isLoggedIn, isNotLoggedIn } = require('../middlewares/auth');
const ctrl = require('../controllers/reservation');

_router.post('/create', isLoggedIn, ctrl.createReservation);

module.exports = _router;

적용 후

const { isLoggedIn, isNotLoggedIn } = require('../middlewares/auth');
const { check, body, validationResult } = require('express-validator');

// 검증 미들웨어
const validator = (req, res, next) => {
    const errors = validationResult(req);
    if (errors.isEmpty()) {
        return next();
    }
    return res.status(400).json({ message: errors.array()[0].msg });
};
const ctrl = require('../controllers/reservation');

// 예약 추가
_router.post('/', [
        isLoggedIn,
        check('type')
            .notEmpty()
            .withMessage('Type is required')
            .isIn(['MT', 'PT'])
            .withMessage('Type must be MT or PT.'),
        check('duration', 'Duration is required').notEmpty(),
        check('proposed_start1', 'More than one proposed reservation time is required').notEmpty(),
        validator,
    ],
    ctrl.createReservation,
);
728x90
반응형
728x90
반응형

기존에 잘 되던 POST  API를 테스트했는데, 이전과 달리 다음과 같은 Sequelize 오류가 발생했다. 

 error : SequelizeForeignKeyConstraintError: Cannot add or update a child row: a foreign key constraint fails (`respec`.`reservation`, CONSTRAINT `reservation_FK_2` FOREIGN KEY 
(`id`) REFERENCES `mentorinfo` (`id`) ON DELETE CASCADE ON UPDATE CASCADE)

동작한 API는 reservation 테이블에 한 행을 INSERT 하는 기능을 하고
reservation 테이블은 mentorinfo의 id와 연결된 외래키 mentorkey를 갖고있다.

reservation.mentorkey = mentorinfo.id

오류 내용은 외래키의 제약과 관련한 에러인데, 정확히는 참조 무결성 제약이 위배된 것이다.

참조 무결성 제약은 reservation 테이블의 mentorkey 컬럼이 mentorinfo 테이블의 id칼럼을 참조하고 있다면, 이 두 칼럼의 값이 항상 일관되어야 한다는 것이다.

 

이 참조 무결성 오류를 해결하기 위해 나는 아래 3가지 과정을 거쳐 원인을 알아냈다.

1. 외래키 정책이 설정되어있는지 확인

오류에서 나왔듯이 외래키 정책은 설정되어있었다.

 

2. 삽입하려는 데이터가 참조 테이블에 있는지 확인

내가 삽입하려는 mentorkey 값은 2였고 mentorinfo 테이블에 id가 2인 데이터가 있는지 확인했다.

만약 없다면 mentorinfo에 id가 2인 데이터를 삽입하면 된다. 그치만 이미 데이터가 있었다.. 그래서 PASS!

 

3. 아무리 생각해도 이상해서 외래키 정책 다시 확인

무엇이 문제인지 감이 안잡혀서 외래키 정책을 다시 보니까, 외래키가 mentorkey가 아닌, 그냥 id로 설정되어있었다.

1번에서는 외래키가 mentorinfo 테이블에 연결되어있는지만 확인하고, 컬럼끼리 잘 연결되어있는지 확인을 안했었는데, 문제는 컬럼에 있었다.ㅠ 외래키 정책 설정할때 졸았나보다 .. 앞으로는 꼼꼼히 보고 설정해야지 !

 

해결 : 외래키 정책 정상적으로 설정하여 다시 시도하니 정상적으로 작동했다!

 

728x90
반응형

'IT > Node.js' 카테고리의 다른 글

Node.js express-validator 사용하기 check VS body  (0) 2023.03.19
Node.js express-session  (0) 2022.10.21
Node.js cookie  (0) 2022.10.17

+ 최근 게시글