본문 바로가기
개발 Study/GraphQL

[GraphQL] GraphQL 사용법 - (2) Sequelize + MSSQL 연동

by jiyoon_92 2022. 9. 29.
반응형

Sequelize + MSSQL

GraphQL

 

1. Sequelize란?

Node 에서 자주 사용되는 ORM 라이브러리다. ORM(Object-Relational Mapping)이란 관계형 데이터베이스와 자바스크립트 객체를 서로 연결해주는 도구로 좀 더 DB작업을 쉽게 처리할 수 있도록 도와준다.

타입스크립트를 이용하여 구현할 거기 때문에 sequelize-typescript를 설치한다.

$npm install sequelize-typescript

 


2. sequelize 연결 설정

DB 정보를 저장할 .env파일을 root에 생성하고 DB_HOST, DB_PORT 등 사용할 변수들을 선언해 놓고 값을 입력해 놓으면, process.env.[변수명] 으로 접근 가능하다.

여러 파일들 에서도 하나의 instance에 접근가능하도록 하기위해 ORM 클래스를 만들고 singleton 패턴으로 구현하였다.

참조 : https://sequelize.org/docs/v6/

 

Sequelize v6 | Sequelize

npm version

sequelize.org

import { Sequelize } from "sequelize-typescript";

class ORM {
    private static instance: Sequelize
    private constructor () {
    }
	
    // 메소드 이름은 달라도 상관없다.
    public static getInstance () {
        return this.instance || (this.instance = new Sequelize({
            host: process.env.DB_HOST,
            database: process.env.DB_NAME,
            dialect: "mssql",
            timezone: "Asia/Seoul",
            dialectOptions: {
              options: {
                charset: 'utf8mb4',
                dateStrings: true,
                typeCast: true,
                encrypt: false,
              },
            },
            define : {
              timestamps : true,
            },
            username: process.env.DB_USER,
            password: process.env.DB_PASSWORD,
            storage: ":memory:",
            port: process.env.DB_PORT as unknown as number,
            models: [__dirname + "/models"],
            modelMatch: (filename, member) => {
              return (
                filename.substring(0, filename.indexOf(".model")) === member.toLowerCase()
              );
            }
          }))
    }
}
export default ORM;

그런 다음 서버에서 sequelize를 불러와 아래와 같이 인증하고 싱크를 맞춰준다.

import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import depthLimit from 'graphql-depth-limit';
import { createServer } from 'http';
import compression from 'compression';
import cors from 'cors';
import loadAllSchema from './schema';
import ORM from './sequelize';
require("dotenv").config();

const app = express();
const sequelize = ORM.getInstance();
(async () => {
    await sequelize.authenticate().then(() => {
      console.log("Authentication successful.");
    });
    await sequelize.sync().then(() => {
      console.log("Sync ok!")
    });
    const schema = await loadAllSchema();
    const server = new ApolloServer({
        schema,
        validationRules: [depthLimit(7)],
      });
    await server.start();
    
    app.use('*', cors());
    app.use(compression());
    
    server.applyMiddleware({ app, path: '/graphql' });
    
    const httpServer = createServer(app);
    httpServer.listen(
      { port: 8000 },
      (): void => console.log(`server Start`)
    );
})();

sequelize는 연동만 해주고 그 다음 코드들은 ApolloServer와 만들어진 schema를 연결해주고 8000번 포트를 통해 주고 받는다.


3. Model 생성하기

Member라는 DB테이블과 연동하기 위해 member.model.ts파일을 만들어 준다. 해당 파일에 아래와 같이 작성해주면 된다.

interface로 생성할때 사용될 MemberDto Type을 선언해준다. @Table 데코레이터에선 테이블 공통 설정에 대한 정보를 넣어준다. 't_member'는 실제 DB에 연결된 테이블 명을 뜻한다. createdAt/updatedAt은 false로 하지않으면 자동으로 insert된다.

@Column 데코레이터를 이용해 ColumnAttribute를 설정할 수 있는데 이 때, 컬럼 속성은 DB에서 설정된 속성과 일치해야하므로 주의해야한다. 특히 DataType은 아래 링크를 참조해 각 DB유형마다 다를 수 있으므로 체크해야한다.

https://sequelize.org/docs/v7/other-topics/other-data-types/

 

Data Types | Sequelize

The generation of values for DataTypes.UUIDV1 and DataTypes.UUIDV4 is done by the Sequelize layer, in JavaScript. For this reason, it is only used when interacting with Models. It cannot be used in migrations. If your dialect provides a built-in SQL functi

sequelize.org

반응형
import moment from "moment";
import { DataTypes } from "sequelize";
import { Table, Column, Model } from "sequelize-typescript";

export interface MemberDto {
  user_id: string;
  password: string;
  member_type: string;
  reg_type: string;
  reg_channel: string;
  member_status : string;
  info_period : string;
  sms_receive : boolean;
  email_receive : boolean; 
}

@Table({
  tableName: "t_member",
  createdAt: false,
  updatedAt: false,
})

export class Member extends Model<Member, MemberDto> {
  @Column({ type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true })
  member_id!: number; //회원id
  @Column({ type: DataTypes.STRING(20) })
  user_id!: string; //아이디
  @Column({ type: DataTypes.STRING(20) })
  password!: string; //비밀번호
  @Column({ type: DataTypes.STRING(), defaultValue: moment(new Date()).format('YYYY-MM-DD HH:mm:ss.SSS'), allowNull: true })
  password_date!: string; //비밀번호 변경일 : 6개월 주기로 변경 필요
  @Column({ type: DataTypes.CHAR(1) })
  member_type!: string; //회원유형 : 개인/학원
  @Column({ type: DataTypes.DATEONLY, defaultValue: DataTypes.NOW })
  reg_date!: Date; //가입일
  @Column({ type: DataTypes.CHAR(1) })
  reg_type!: string; //가입유형 - 1 : 쌤잡, 2 : 네이버, 3 : 카카오, 4 : 구글
  @Column({ type: DataTypes.CHAR(1) })
  reg_channel!: string; //가입채널 - 1 : 웹, 2 : 핸드폰, 3 : 탭
  @Column({ type: DataTypes.DATEONLY, allowNull: true })
  rest_date!: Date; //휴면일
  @Column({ type: DataTypes.DATEONLY, allowNull: true })
  out_date!: Date; //탈퇴일
  @Column({ type: DataTypes.CHAR(1), defaultValue: '0' })
  member_status!: string; //회원상태 - 0 : Live, 1 : 휴면, 2 : 탈퇴
  @Column({ type: DataTypes.CHAR(1) })
  info_period!: string; //개인정보 보유기간 - 1년/3년/5년
  @Column({ type: DataTypes.BOOLEAN })
  sms_receive!: boolean; //문자 동의
  @Column({ type: DataTypes.BOOLEAN })
  email_receive!: boolean; //이메일 동의
}

mssql을 사용하는 분들은 그냥 DateTime을 넣을 시 에러가 날 수 있으므로 string DataType으로 넣어주되 string format을 위와 같이 해주면 된다. 'YYYY-MM-DD HH:mm:ss.SSS'


4. Controller 작성하기

만든 Model을 Create/Read/Update/Delete 할 수 있도록 Controller를 작성해 보도록 하자

  • getItems() : 전체 Member list 가져오기
  • getItemByUserId() : user_id로 해당하는 member 가져오기
  • insertItem() : new member 만들기
  • updateItem() : 기존 member 정보 업데이트하기
  • deleteItem() : user_id로 해당 member 삭제하기
import { Member, MemberDto } from "../models/member.model";
const getItems = async () => {
  const members = await Member.findAll();
  return {res : true, msg : `All members found.`, data : members};
};

const getItemByUserId = async (user_id : string) => {
  const member = await Member.findOne({where : { user_id : user_id}});
  if (!member)
  {
    throw Error(`member not existed. id: ${user_id}`);
  }
  return {res : true, msg : `${user_id} found.`, data : [member]};
}

const insertItem = async (args : any) => {
  const newMember : MemberDto = {
    user_id : args.user_id,
    password : args.password,
    member_type : args.member_type,
    reg_type: args.reg_type,
    reg_channel: args.reg_channel,
    member_status : args.member_status,
    info_period : args.info_period,
    sms_receive : args.sms_receive,
    email_receive : args.email_receive
  }
  try {
    const member : Member = await Member.create(newMember);
    return {res : true, msg : `created ${args.user_id}.`, data : [member]};
  } catch(error) {
    return {res : false, msg : error};
  }
}

const updateItem = async (user_id : string, args : any) => {
  const member = await Member.findOne({where: {user_id : user_id}});
  if (!member) {
    throw Error(`member not updated. user_id: ${user_id}`);
  }

  member.password = args.password;
  member.member_type = args.member_type;
  member.reg_type = args.reg_type;
  member.reg_channel = args.reg_channel;
  member.member_status = args.member_status;
  member.info_period = args.info_period;
  member.sms_receive = args.sms_receive;
  member.email_receive = args.email_receive;
  try {
    await member.save();
    return {res : true, msg : `updated ${user_id}.`, data : [member]};
  } catch (error) { 
    return {res : false, msg : error};
  }
}

const deleteItem = async(user_id : string) => {
  const member = await Member.findOne({where: {user_id : user_id}});
  if (!member) {
    throw Error(`member not existed. user_id: ${user_id}`);
  }
  try {
    await Member.destroy({where : {user_id : user_id}});
    return {res : true, msg : `delete ${user_id}.`};
  } catch (error) { 
    return {res : false, msg : error};
  }
}
export default { getItems, getItemByUserId, insertItem, updateItem, deleteItem };

각 함수들은 return 값을 객체 형태로 반환하며 data에 DB로 부터 받은 데이터를 넣는다.

해당 함수들은 추후 GraphQL의 resolver에서 호출되어 사용된다.

2022.09.21 - [개발 Study/Node, js, react] - [GraphQL] GraphQL과 REST

 

[GraphQL] GraphQL과 REST

GraphQL을 사용하는 이유 1. GraphQL이란? GraphQL은 2012년도에 페이스북에 의해 고안된 API용 쿼리 언어이자 쿼리를 수행하기 위한 런타임이다. 2015년에 오픈소스로 공개되었다. GraphQL은 REST 및 웹서비

chuun92.tistory.com

2022.09.27 - [개발 Study/Node, js, react] - [GraphQL] GraphQL 사용법 - (1) Schema 생성 후 ApolloServer 연동하기

 

[GraphQL] GraphQL 사용법 - (1) Schema 생성 후 ApolloServer 연동하기

GraphQL + ApolloServer 1. GraphQL 타입 선언 (Schema 만들기) 1-1. Query 타입 Query 타입은 보통 fetch data를 하여 원하는 정보를 얻어올 때 쓰인다. schema.graphql을 만든 후 Schema에 Query 타입을 선언..

chuun92.tistory.com

2022.09.30 - [개발 Study/Node, js, react] - [GraphQL] GraphQL 사용법 - (3) sequelize 사용하기(1:1, ForeignKey, Join)

 

[GraphQL] GraphQL 사용법 - (3) sequelize 사용하기(1:1, ForeignKey, Join)

Sequelize 테이블 연결 및 외래키 사용 1. Sequelize 테이블 관계 테이블 관계는 1:1, 1:N, M:N 이 있는데 foreignkey를 이용하여 서로 관계를 맺으며 join을 통해 연결된 테이블의 정보까지 불러올 수 있다. 참

chuun92.tistory.com

 

반응형

댓글