Tech Blog of Pinomaker
해당 포스트는 Typescript가 셋팅 되어있어야 합니다.
 

[Node JS] Node.js를 Typescript 프로젝트로 셋팅하기.

1. Node Project 초기화 작업할 폴더를 생성하고 아래의 명령어로 Node Project로 초기화를 해준다. npm init -y 2. Module 설치 프로젝트를 위한 모듈을 설치한다. 이제 배포 할 때 사용하지 않는 모듈은 --save

pinomaker.com

 

해당 포스트 시리즈는 typescript, passport, sequelize를 이용하여, Local 로그인, Kakao 로그인, Naver Login과 JWT까지 다룰 예정 입니다.

 

1. 필요한 모듈 설치

먼저 sequelize와 유저 관련 API 개발을 위해 필요한 모듈을 아래와 같이 설치하자.

  • dotenv : 환경 변수 설정을 위한 모듈
  • bcrypt, @types/bcrypt : 비밀번호 암호화를 위한 모듈
  • mysql2, sequelize, sequelize-cli : sequelize를 위한 모듈
npm install dotenv bcrypt mysql2 sequelize sequelize-cli

npm install --save-dev @types/bcrypt

 

2. sequelize 셋팅

생각보다 생성할 파일과 폴더가 많아서 이미지를 먼저 확인하면 좋을 거 같습니다!

프로젝트 폴더 구조

 

먼저 src에 models 폴더를 생성하고, 그 안에 index.ts를 만들고 아래와 같이 작성한다.

 

하지만 DB SERVER에 대한 유출을 방지하기 위해 dotenv를 이용하여 환경 변수로 DB 설정을 입력한다.

// src/models/index.ts

import { Sequelize } from 'sequelize'
import config from '../config/sequelize.config'

const sequelize = new Sequelize(
  config.development.database,
  config.development.username,
  config.development.password,
  {
    host: config.development.host,
    dialect: 'mysql',
    timezone: '+09:00',
  },
)

export default sequelize

 

dotenv를 이용하여 환경 변수를 설정하자.

 

src에 config 폴더를 생성하고, 그 안에 sequelize.config.ts를 만들어 자신의 DB 정보를 넣어 아래와 같이 작성하자!

// src/config/sequelize.config.ts

import dotenv from 'dotenv'
dotenv.config()

const config = {
  development: {
    username: 'USER-NAME',
    password: 'PASSWORD',
    database: 'DB-NAME',
    host: 'DB-HOST',
    dialect: 'mysql',
  },
}

export default config

 

이제 Sequelize를 실행하기 위해서 src/index.ts에 서버가 켜지면 sequelize가 실행하게 설정을 해둔다.

 

추가로 json 형식의 요청을 받기 위해 express의 json parser를 이용하자!

// src/index.ts

import express, { Request, Response } from 'express'
import sequelize from './models'

const app = express()

// json-parser 사용
app.use(express.json())
app.use(express.urlencoded({ extended: false }))

const port: number = 3000
app.listen(port, async () => {
  console.log(`SERVER ON! PORT : ${port}`)
  // 서버 실행 시, 시퀄라이즈로 DB 연결
  await sequelize
    .authenticate()
    .then(async () => {
      console.log('connection success')
    })
    .catch((e) => {
      console.log(e)
    })
})

 

3. User Model 생성

sequelize 셋팅을 마쳤으니, 이제 sequelize에서 사용할 User Models을 만들어보자

일단 User Models는 아래와 같은 구조로 잡을 예정이다.

colum 설명 colum 설명
idx PK password 유저의 비밀번호, Kakao와 Naver 로그인은 Null 처리
email 유저 Email(Unique) provider enum을 이용하여 가입 경로 구분
name 유저 이름 providerId 로컬 로그인은 Null 처리

 

이제 src에 models 폴더를 만든 후, interface폴더와 domain 폴더를 각각 생성해주자.

 

interface 폴더에는 Models들의 interface 파일을 넣을 것이고, domain에는 entity 파일을 넣을 것이다.

 

먼저 interface 폴더에 User.interface.ts를 생성한다.

// src/models/interface/User.interface.ts

// User의 컬럼
export interface UserAttributes {
  // 자동 주입이기에, 옵셔널
  idx?: number
  email: string
  
  // kakao, Naver 유저는 null
  password?: string
  name: string
  provider: Provider
  
  //Local 유저는 null
  providerId?: string
}

// User enum
export enum Provider {
  LOCAL,
  KAKAO,
  NAVER,
}

해당 포스트는 로그인을 카카오와 네이버, 로컬 총 3가지 방법으로 진행할 것이기에, 가입 경로를 Provider enum을 만들어서 구분할 것이며, 카카오와 네이버 로그인을 하면 password가 null이고, 각 서비스사에서 제공 받은 providerId를 넣어주고, 로컬 로그인을 하면 password를 이용하고, providerId를 Null로 처리한다.

 

따라서 null이 들어올 수 있는 값들을 ?를 이용하여 옵셔널 처리한다.

 

그리고 domain 폴더에 User.ts 파일을 생성한다.

import { DataTypes, Model } from 'sequelize'
import sequelize from '../index'
import { Provider, UserAttributes } from '../interface/User.interface'

// User Class 정의
export class User extends Model<UserAttributes> {
  public readonly idx?: number
  public email!: string
  public password!: string
  public name!: string
  public provider!: Provider
  public providerId!: string

  // 생성 날짜, 수정 날짜 자동 생성
  public readonly createdAt!: Date
  public readonly updatedAt!: Date

  public static associations: {}
}

// DB에 초기화
User.init(
  {
    idx: {
      type: DataTypes.INTEGER,
      autoIncrement: true,
      primaryKey: true,
    },
    email: {
      type: DataTypes.STRING(50),
      unique: true,
      allowNull: false,
    },
    password: {
      type: DataTypes.STRING(120),
      allowNull: true,
    },
    name: {
      type: DataTypes.STRING(45),
      allowNull: false,
    },
    provider: {
      type: DataTypes.STRING(15),
      allowNull: false,
    },
    providerId: {
      type: DataTypes.STRING(45),
      allowNull: true,
    },
  },
  {
    modelName: 'User',
    // Table name
    tableName: 'tbl_user',
    sequelize,
    freezeTableName: true,
  },
)

 

4. Migration을 이용한 Table 생성

프로젝트를 실행하면 자동으로 Table을 생성하면 좋지만, 이번에는 Migration을 이용하여 생성해보자.

 

src에 migrations 폴더를 생성하고, create-table 폴더와 migration-all-table.ts 파일을 아래와 같이 생성한다.

 

migrations으로 Table을 생성하는 방법은 터미널에서 migration-all-table.ts 파일을 읽어서 DB에 Table을 생성 하는 데, migration-all-table.ts 파일은 create-table 폴더 안에 있는 각 Model의 Table을 생성하는 명령이 들어있는 파일들을 읽어서 Table을 생성하는 구조다.

 

해당 게시글을 따라할 것이면, 폴더와 파일 이름을 그대로 따라줘야한다.

 

 

먼저 src/migrations/migration-all-table.ts를 아래와 같이 작성한다.

// src/migrations/migration-all-table.ts

import * as fs from 'fs'
import * as path from 'path'
import { exec, execFile } from 'child_process'
import * as util from 'util'
console.log('migration-all-table')

const asyncExec = util.promisify(exec)
console.log(`
    --------------------------------------
    +++Laggard Project Migration Start+++
    --------------------------------------
`)
let migrationAllTable = async () => {
  let migrationFiles: string[] = []
  fs.readdir(path.join(__dirname, '/', 'create-table'), async (err, files) => {
    if (err) console.log('err : ', err)
    if (files) {
      files.forEach((el) => {
        if (el.substr(el.indexOf('.') + 1, 12) === 'create-table') {
          migrationFiles.push(el)
        }
      })
      migrationFiles.sort((a, b) => {
        return (
          Number(a.substr(0, a.indexOf('.'))) -
          Number(b.substr(0, b.indexOf('.')))
        )
      })
      console.log('migrationFiles : ', migrationFiles)
      for (let el of migrationFiles) {
        console.log('Migration File Name : ', el)
        const { stdout, stderr } = await asyncExec(
          `./node_modules/.bin/ts-node "${__dirname}/create-table/${el}"`,
        )
        if (stdout) console.log(stdout)
        if (stderr) console.error('Std Err : ', stderr)
      }
    }
  })
}

migrationAllTable()

src/migrations/migration-all-table.ts는 같은 경로의 create-table 폴더 안에 있는 번호 + create-table로 시작하는 파일들을 읽은 후 Table을 생성한다고 생각하면 된다!

 

그리고 User Model의 Table을 생성해줄 1.create-table-user.ts 파일을 src/migrations/create-table에 생성하자.

// src/migrations/create-table/1.create-table-user.ts

import { User } from '../../models/domain/User'

console.log('======Create User Table======')

const create_table_user = async () => {
  await User.sync({ force: true })
    .then(() => {
      console.log('✅Success Create User Table')
    })
    .catch((err) => {
      console.log('❗️Error in Create User Table : ', err)
    })
}

create_table_user()

 

마지막으로 package.json에 명령어를 추가하고 실행하면 아래와 같은 화면이 나오면서 Table이 생성된 것을 확인 할 수 있다.

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "nodemon --watch \"src/**/*.ts\" --exec \"ts-node\" src/index.ts",
    // migration 실행 스크립트
    "db": "./node_modules/.bin/ts-node ./src/migrations/migration-all-table.ts"
  },

 

migration 실행 script 명령어

npm run db

profile

Tech Blog of Pinomaker

@pinomaker

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!