Sequelize에서 날짜에 대한 타임존 처리 하기

Sequelize에서 날짜형(date)을 저장시 -9:00 된 값으로 저장이 됩니다.

EX) 6월 4일 00:00 => 6월 3일 15:00

서버의 타임존도 KST이고, DBMS의 타임존도 KST임에도 저장시 -9시간이 됩니다.

이럴때는 connection에 timezone을 설정하면 처리가 됩니다.

아래는 처리 하는 Sequelize Connection 예제입니다.

const Sequelize = require("sequelize");

// Option 1: Passing parameters separately
const sequelize = new Sequelize(
  process.env.MYSQL_DB,
  process.env.MYSQL_USER,
  process.env.MYSQL_PASS,
  {
    host: process.env.MYSQL_URL,
    dialect: "mysql",
    dialectOptions: { charset: "utf8mb4", dateStrings: true, typeCast: true }, // 날짜의 경우 문자열로 타입 변경 처리
    timezone: "+09:00", // 타임존을 설정
    pool: {
      max: 5,
      min: 0,
      acquire: 30000,
      idle: 10000,
    },
    define: {
      // The `timestamps` field specify whether or not the `createdAt` and `updatedAt` fields will be created.
      // This was true by default, but now is false by default
      timestamps: false,
      supportBigNumbers: true,
    },
  }
);

module.exports = sequelize;

주요 설정으로 타임존과 날짜를 외부에 보낼때 처리만 추가 해주면 됩니다.

참고자료

Posted by lahuman

댓글을 달아 주세요

Nodejs에서 Typescript 사용해보기!

nodejs를 하면서 Typescript에 대하여 어려번 들었습니다.

기본적으로 nosejs에 대한 개발을 REPL 기반으로 하고 있습니다.

TDD를 하기 위해서는 타입에 대한 검증등 많은 것들을 처리 해야 하는데요. 이는 Typescript와 더 잘 맞습니다.

한번 사용해보기 위해서 nodejs에서 Typescript를 사용하는 방법을 설명드리겠습니다.

Typescript 사용해 보기

평소에는 npm을 사용하지만, 이번에는 yarn을 사용해보려고 합니다.

프로젝트 생성하기

먼저 프로젝트 디렉토리를 생성하고 yarn 명령어를 이용해서 초기화 합니다.

$ mkdir learn-typescript
$ cd learn-typescript
$ yarn init -y

Typescript 사용을 위한 모듈 설치하기

  • typescript : Typescript 라이브러리
  • ts-node : node에서 Typescript를 사용하기 위한 라이브러리
$ yarn add typescript ts-node

Typescript 설정 파일 생성하기

타입스크립트에 대한 정보를 가지고 있는 설정파일을 생성합니다.

$ yarn run tsc --init

실행을 하면 디렉토리에 tsconfig.json 파일이 생성됩니다.

해당 파일의 내용은 아래와 같습니다.

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */

    /* Basic Options */
    // "incremental": true,                   /* Enable incremental compilation */
    "target": "es2016" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
    "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
    // "lib": [],                             /* Specify library files to be included in the compilation. */
    // "allowJs": true,                       /* Allow javascript files to be compiled. */
    // "checkJs": true,                       /* Report errors in .js files. */
    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
    // "sourceMap": true,                     /* Generates corresponding '.map' file. */
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    "outDir": "./dist/" /* Redirect output structure to the directory. */,
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "composite": true,                     /* Enable project compilation */
    // "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */
    // "removeComments": true,                /* Do not emit comments to output. */
    // "noEmit": true,                        /* Do not emit outputs. */
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true /* Enable all strict type-checking options. */,
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                /* Report errors on unused locals. */
    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */

    /* Module Resolution Options */
    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
    // "types": [],                           /* Type declaration files to be included in compilation. */
    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
    // "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */

    /* Source Map Options */
    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */

    /* Advanced Options */
    "skipLibCheck": true /* Skip type checking of declaration files. */,
    "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
  }
}

물론 명령어 없이 추가해도 됩니다.

Typescript 컴파일

다음 명령어로 .ts 파일을 컴파일 하여 .js 파일로 변환합니다.

$ yarn run tsc

컴파일 후 기본적으로 같은 디렉토리에 .js파일로 생성됩니다.

tsconfig.json 파일의 outDir 값을 이용해서 다른 디렉토리에 생성되도록 변경이 가능합니다.

nodejs에서 Typescript 실행하기

매번 컴파일을 하지 않고 ts-node 명령어를 이용해서 실행할 수 있습니다.

# run ts file
$ yarn run ts-node ./src/index.ts

여기까지 기본적은 Typescript를 nodejs에서 사용하는 명령어를 확인하였습니다.

Typescript에 대하여 자세히 알고 싶다면 TypeScript Documentation 문서를 확인하면 됩니다. :)

마지막으로 마틴 아저씨가 REPL로 개발한 경험을 블로그에 올리셨습니다.

So I’ve learned my lesson. REPL driven development feels easier and faster than TDD; but it is not. Next time, it’s back to TDD for me.

내 맘대로 직역 :

내가 배운 교훈은 REPL개발은 TDD에 비해 쉽고 빠르게 보이지만, 실제로는 그렇지 않다. 여러 부분에서 더 많은 비용을 들이게 된다.
그래서 나는 TDD를 다시 사용한다.

nodejs에서 명확히 구조화된 프로그램 작성을 위해서는 Typescript를 사용하는 것을 추천합니다. :)

참고자료

Posted by lahuman

댓글을 달아 주세요

swagger-node에서 express-session 설정 & 사용하기

기본적으로 swagger-nodeexpress를 기반으로 동작합니다.

swagger-node는 API 용으로 이용하기 때문에 기본적으로 session을 발행하지 않습니다.

다만 필요에 따라 로그인 프로세스 구현을 하게 될 경우 다음 라이브러리를 이용해야 합니다.

app.js 코드에 다음과 같이 설정을 하면 세션을 이용할수 있습니다.

express 4.X 부터는 body-parser를 사용하지 않고 express에서 제공되는 것을 이용합니다.
또한 app.use(express.urlencoded({ extended: true })); 을 사용하면 fileupload 처리를 이전과 같이 사용할 수 없습니다. 아래 multer를 이용한 예제를 참고 하셔요.

app.use(
  require("express-session")({
    secret: "keyboard cat",
    cookie: {
      maxAge: 1000 * 60 * 60 , // 1 hour
    },
    resave: false,
    saveUninitialized: false,
  })
);
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

추가로 session 관리를 passport를 이용해서 쉽게 관리 할수 있습니다.

다음 예제는 SSO의 방식중 하나인 SAML으로 처리한 예제를 준비 하였습니다.

예제 코드 바로 보기

주요점은 passport는 express-session에서만 동작 하기 때문에 세션 설정을 잘해야 합니다.

Multer를 이용한 첨부 파일 uploade

app.use(express.urlencoded({ extended: true }));를 설정하면, formData를 바로 접근하지 못하게 됩니다. 이때 다음 샘플을 기준으로 첨부파일을 처리 할 수 있습니다.

// multer 설정
const multer = require("multer");
const storage = multer.diskStorage({
  // upload path
  destination: function (req, file, cb) {
    cb(null, "tempfiles");
  },
  // 업로드된 파일 이름
  filename: function (req, file, cb) {
    cb(null, `${moment().format("YYYYMMDDHHmmssSSS")}${file.originalname}`);
  },
});

const upload = multer({
  storage: storage,
  limits: { fileSize: 10 * 1024 * 1024 },
});

... 설정 처리

SwaggerExpress.create(config, function (err, swaggerExpress) {
  if (err) {
    throw err;
  }
  // swagger-node에서 미들웨어 처리 방법
  app.use(
      "/jira/attachments/upload",
      upload.single("filepond"),
      async (req, res, next) => {
        try {
          const file = req.file;
          res.status(201).json({
            status: "OK",
            file_nm: file.originalname.normalize("NFC"),
            file_path: file.filename,
          });
        } catch (e) {
          logger.error(e);
          res.status(400).json({ status: "ERROR", message: e });
        }
      }
    );
});

주요 코드만 표기 하였습니다.
예제 코드 바로 보기app.js를 참고 하면 전체 코드를 작성하실수 있습니다.

참고 자료

Posted by lahuman

댓글을 달아 주세요

첨부파일을 다른 서버로 포워딩 하기

첨부 파일을 아래 그림과 같이 API server에서 받아서 다른 내부 서버에 전달을 하려고 합니다.

API 서버에는 따로 첨부파일 정보를 저장하지 않고, buffer를 바로 내부 서버로 전달을 합니다.

이를 처리하는 방식으로는 2가지가 있습니다.

  1. Proxy 사용
  2. axios와 다른 라이브러리를 이용하여 post 통신

1. Proxy 사용

필요 라이브러리

사용법

express-http-proxy를 다음과 같이 설정하면 해당 주소로 오는 요청의 통신을 설정된 서버로 포워딩 해줍니다.

# 예제
const proxy = require('express-http-proxy');

const app = require('express')();

app.use('/proxy', proxy('http://proxy_target_domain', {
  proxyReqPathResolver: async (req) => {
    return '/proxy/target/url?id='+req.params.id;
  },
  proxyReqOptDecorator: (proxyReqOpts) => {
    const newProxyReqOpts = { ...proxyReqOpts };
    newProxyReqOpts.headers = { host: 'spacial hosts', apikey: 'spacial key' };
    newProxyReqOpts.rejectUnauthorized = false;
    return newProxyReqOpts;
  },
}));

2. axios와 다른 라이브러리를 이용하여 post 통신

필요 라이브러리

  1. axios
  2. multer
  3. form-data

사용법

우선 첨부파일을 multer를 이용해서 객체로 만들고, 이를 form-data로 감싸서 axios를 통해 전송합니다.

const axios = require("axios");
const https = require("https");
const multer = require("multer");
const FormData = require("form-data");
const express = require('express');

const app = express();
const router = express.Router();

app.use('/', router);

const upload = multer();
router.post(
  "/upload",
  upload.single("filepond"), // multer를 이용하여 업로드 파일 처리
  async (req, res, next) => {
    try {
      // buffer를 FormData로 감쌈
      const formData = new FormData();
      formData.append("filepond", req.file.buffer, {
        filename: req.file.originalname,
      });

      // 다른 서버로 전송
      const result = await axios.post(
        'http://proxy_target_domain/proxy/target/url',
        formData,
        {
          headers: {
            ...formData.getHeaders(),
            "Content-Length": formData.getLengthSync(),
            apikey: "apikey",
            host: "hosts",
          },
          httpsAgent: new https.Agent({
            rejectUnauthorized: false,
          }),
        }
      );
      res.status(200).json(result.data);
    } catch (err) {
      logger.error(err);
      res
        .status(500)
        .send(`${err}`);
    }
  }
);

첨부 파일을 다른 서버로 전송할때 주의 하는 부분이 있습니다.

formData.append("filepond", req.file.buffer, {
        filename: req.file.originalname,
      });

이 부분에서 3번째 옵션 파라메터에 filename 이외의 값을 함부로 추가 하면 400 Bad request 가 발생합니다.

파일을 전달하는 것이 어려운게 아닌데 명확한 인터페이스를 확인하지 못해서 시간이 많이 지체 되었습니다.

Posted by lahuman

댓글을 달아 주세요

PM2 save / startup을 해두어도, reboot 시 실행되지 않을시 처리 방안

PM2에서 startup 을 root 가 아닌 계정으로 실행할 경우 아래와 같은 메시지가 표기 됩니다


$ pm2 startup

[PM2] You have to run this command as root. Execute the following command:

      sudo su -c "env PATH=$PATH:/home/unitech/.nvm/versions/node/v4.3/bin pm2 startup <distribution> -u <user> --hp <home-path>

이때 당황하지 말고 sudo 이하의 명령어를 실행하면 됩니다.

(꺽쇠로 나온 값에 대하여 셋팅되여 명령어에 표기 됩니다.)

root 계정이 아닐 경우 발생하는 문제로 설정을 해야 합니다

이후에 startup / service 기동 / save 를 하고 reboot을 해도 문제가 없습니다


$ pm2 startup

# 서비스 기동

$ pm2 start app.js --name=serviceName

# 상태 저장

$ pm2 save

예제

상황 : ubuntu 계정으로 재부팅시 pm2 프로세스를 기동하려고 합니다.


$> pm2 startup ubuntu

$> sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup ubuntu -u ubuntu --hp /home/ubuntu



$> cd service

$> pm2 start app.js --name=servicename

$> pm2 save

늘 그렇지만 문서만 잘읽어도 금방 해결될 문제다. 문서 읽기를 생활화 하자!

참고 자료

Posted by lahuman

댓글을 달아 주세요

프로젝트 초반에 셋팅을 잘해놓고 한참 쓰고 나서 다시 새 프로젝트를 시작할때면 나는 아무런 기억이 나지 않습니다.

나중에 프로젝트 시작할때 참고 하기위해서 예제 코드를 정리해보았습니다.

기본 Base는 swagger-node에 로그, dotenv, helmet 등을 설정 하였고, mongodb에 대하여 CRUD 하는 예제를 만들었습니다.

예제 코드 보기

주요 내용

app.js 에서 처음 앱이 기동될때, connect.js를 호출하여 DB 연결을 합니다.

require(`${__basedir}/config/connect`);

connect.js 내부를 보면 models 라는 디렉터리 밑에 존재하는 모든 모델을 자동으로 읽어 들여서 처리 합니다.

const models = join(__basedir, 'models');

// Bootstrap models
fs.readdirSync(models)
  .filter(file => ~file.search(/^[^.].*\.js$/))
  .forEach(file => require(join(models, file)));

기동 방법

$> npm install
$> swagger project start

주요 샘플은 서버를 설치 후 다음 주소에 접근해서 확인 할 수 있습니다.

Swagger test URL

http://localhost:10010/api/docs

예저 정보

Book

Mongoose 를 이용한 샘플

Schedule

Swagger 사용법을 알기 위핸 샘플

참고 자료

Posted by lahuman

댓글을 달아 주세요

JAVA 개발자가 시작하는 NodeJS

이번에 회사에서 지원하는 사내강사 프로그램에 참가하였다.

발표 주제로 입사하고 nodejs를 사용하면서 느낀 점을 공유 하는 것으로 잡았다.

주요 내용은 nodejs를 사용하여 좋은 점과 아쉬운 점을 담았다.

Posted by lahuman

댓글을 달아 주세요

NODEJS] AWS의 S3에 파일 업로드 다운로드 구현

AWS EC2 서버에서 공지사항에 들어가는 첨부 파일을 업로드 하고 다운로드 하는 기능이 필요 했다.

다음의 순서로 처리하면 쉽게 된다.

1. 사용자 생성 하기

1. AWS IAM에서 사용자를 생성 하고, 애세스 유형을 프로그램 방식으로 선택

사용자 추가
{: .image-left}

이후 결과로 Access key ID, Secret access key를 알고 있어야 한다.

2. 권한으로 AmazonS3FullAccess를 할당

사용자 권한
{: .image-left}

2. nodejs에서 aws-sdk 사용하기

# 프로젝트 초기화
$> npm init

# aws-sdk 모듈 설치
$> npm install aws-sdk --save

3. bucket 생성 하기

createBucket.js

const AWS = require('aws-sdk');
const ID = 'Access key Id';
const SECRET = '';
const BUCKET_NAME = '';
const s3 = new AWS.S3({
  accessKeyId: ID,
  secretAccessKey: SECRET
});
const params = {
  Bucket: BUCKET_NAME,
  CreateBucketConfiguration: {
      // Set your region here
      LocationConstraint: "ap-northeast-2"
  }
};

s3.createBucket(params, function(err, data) {
  if (err) console.log(err, err.stack);
  else console.log('Bucket Created Successfully', data.Location);
});

4. 파일 업로드

uploadFile.js

const fs = require('fs');
const AWS = require('aws-sdk');
const BUCKET_NAME = '';
const s3 = new AWS.S3({
  accessKeyId: '',
  secretAccessKey: ''
});
const uploadFile = (fileName) => {
  const fileContent = fs.readFileSync(fileName);
  const params = {
      Bucket: BUCKET_NAME,
      Key: 'test.txt', // File name you want to save as in S3
      Body: fileContent
  };
  s3.upload(params, function(err, data) {
      if (err) { throw err; }
      console.log(`File uploaded successfully. ${data.Location}`);
  });
};
uploadFile('./uploads/test.txt');

5. 파일 다운로드

download.js

const fs = require('fs');
const AWS = require('aws-sdk');
const BUCKET_NAME = '';
const s3 = new AWS.S3({
  accessKeyId: '',
  secretAccessKey: ''
});
const downloadFile = (fileName) => {
  const params = {
      Bucket: BUCKET_NAME,
      Key: 'test.txt', // File name you want to save as in S3
  };
  s3.getObject(params, function(err, data) {
      if (err) {
          throw err;
      }
      fs.writeFileSync(fileName, data.Body.toString());
  });
};
downloadFile('../../uploads/test-download.txt');

참고자료

Posted by lahuman

댓글을 달아 주세요

pm2 로그를 관리하는 모듈을 이용하자

pm2는 Nodejs에서 프로세스를 관리 하는 모듈이다.

아주 많이 사용되고 있으며 주요 기능은 프로세스 관리 및 모니터링을 제공한다. 또한 사용법이 간단하다.

# 설치 
$> npm install pm2 -g 
# 사용
$> pm2 start app.js

6개월 가량 운영하던 서비스에서 디스크 FULL 메시지를 받고 가장 많이 사용중인 파일을 확인하였는데, pm2 로그 파일이었다.

기본 설정을 사용시, 로그 파일은 한개만 생성되고 계속 사이즈가 증량되는 방식으로 제공한다.

pm2 로그를 관리 하기 위하여 검색 결과 pm2-logrotate를 찾았다.
pm2 모듈로, 설치는 다음과 같이 하면 된다.

#설치
$> pm2 install pm2-logrotate

[PM2][Module] Module downloaded
[PM2][WARN] Applications pm2-logrotate not running, starting...
[PM2] App [pm2-logrotate] launched (1 instances)
== pm2-logrotate ==
┌────────────────┬─────────────────────┐
│ key            │ value               │
├────────────────┼─────────────────────┤
│ max_size       │ 10M                 │
│ retain         │ 30                  │
│ compress       │ false               │
│ dateFormat     │ YYYY-MM-DD_HH-mm-ss │
│ workerInterval │ 30                  │
│ rotateInterval │ 0 0 * * *           │
│ rotateModule   │ true                │
└────────────────┴─────────────────────┘

설치를 하면 다음과 같은 기본 설정이 반영 된다.

파일 한개의 최대 크기는 10M 이고 파일은 30개만 남는다. 파일명은 dateFormat을 기반으로 표시 되고 매일 1개의 파일이 생성된다.

설정을 변경하려면 다음과 같은 명령어를 이용하면 된다.

# (1KB)
$> pm2 set pm2-logrotate:max_size 1K 
#  (compress logs when rotated)
$> pm2 set pm2-logrotate:compress true
#  (force rotate every minute)
$> pm2 set pm2-logrotate:rotateInterval '*/1 * * * *'

설치하고 사용이 참 쉬웠다.

참고 자료

Posted by lahuman

댓글을 달아 주세요

package-lock.json은 왜 필요 한가?

npm install을 이용해서 package를 인스톨하면 자동으로 package-lock.json이라는 파일이 생성된다.

용도를 몰라서 그냥 두고 있었는데, 가끔씩 이 파일이 형상관리 과정에서 충돌이 일어나 .gitignore에 추가해버렸었다.

함께 일하시는 분이 package-lock.json은 꼭 필요하다는 이야기를 해주셔서 검색을 해보니 다음과 같은 경우에 사용된다.

  1. npm의 버전의 다른 경우
  2. 의존성을 가진 패키지의 버전이 업데이터 되는 경우
  3. 의존성을 가진 패키지가 의존하는 패키지의 버전이 업데이트되는 경우

node_modules의 폴더의 스냅샷을 저장하여, 다른 곳에서 npm install 명령어를 실행시 package-lock.json에 명시된 의존 패키지들을 통해 node_modules를 만들어 낸다. 다만, package.json의 변경은 package-lock.json 보다 우선 된다.

주요 사항은 package-lock.json은 꼭 형상 관리에 포함 시켜야 한다.

그러하다

참고자료

Posted by lahuman

댓글을 달아 주세요