본문 바로가기

항해99_10기/105일의 TIL & WIL

[4주차] [20221205] Joi validation & Sequelize를 이용한 mySQL 설정

오늘은 기존에 빌드한 게시판 db를 mySQL로 바꾸고, 회원가입 및 로그인 인증 기능을 추가하기 위한 기초 작업을 진행했다.

지난 주차에서는, request.body를 검증하기 위해서, 별도의 함수를 만들어 처리해 주었는데, 이번 주차부터는 Joi를 사용하여 검증을 더 쉽게 구성할 수 있게 되었다.

 

Joi를 사용하여 데이터 검증하기


The most powerful schema description language and data validator for JavaScript.
- npm Joi

JavaScript를 위한 가장 강력한 schema description language이자, data validator라고 설명하고 있다. Joi에서는 데이터 검증을 위한 강력한 함수들을 제공하고 있는데, api 명세는 홈페이지에서 직접 확인하며 사용하면 된다.

그리고, github에서 Joi 실전 예제를 아주 다양하고 보기 좋게 정리한 레포지토리도 공유한다. (아래 저 github이 정말 좋다.)

 

joiSite

## Build Setup

joi.dev

 

 

GitHub - hapijs/joi: The most powerful data validation library for JS

The most powerful data validation library for JS. Contribute to hapijs/joi development by creating an account on GitHub.

github.com

 

나는 지난주에 이어, 이번주도, CustomError 클래스를 계속해서 사용 중인데, Joi에서도, .error()로 CustomError 객체를 만들어 던질 수 있어서 아주 편리하게 api를 작성할 수 있었다.

  • 아래는 회원가입에 사용할 req.body를 Joi를 사용하여 검증하는 것과, 검증 실패시, CustomError 객체를 생성해 error status code와 message를 커스텀으로 던질 수 있게 한 코드이다.
const express = require("express");
const router = express.Router();
const { Users, Posts, Comments, Likes } = require("../models");
const Joi = require("joi");

class CustomError {
  constructor(message, status) {
    this.name = "CustomError";
    this.message = message;
    this.status = status;
  }
}

//######## 회원가입 ############
const postUserSchema = Joi.object({
  nickname: Joi.string()
    .alphanum()
    .min(3)
    .required()
    .error(() => {
      throw new CustomError("ID의 형식이 일치하지 않습니다.", 412);
    }),

  password: Joi.string()
    .min(4)
    .invalid(Joi.ref("nickname"))
    .required()
    .error(() => {
      throw new CustomError("password의 형식이 일치하지 않습니다.", 412);
    }),

  confirmPassword: Joi.valid(Joi.ref("password")).error(() => {
    throw new CustomError("password가 일치하지 않습니다.", 412);
  }),
});

router.post("/users", async (req, res) => {
  try {
    const { nickname, password } = await postUserSchema.validateAsync(req.body);
    const existsUsers = await Users.findOne({ where: { nickname } });
    if (existsUsers) {
      res.status(412).send({ errorMessage: "중복된 닉네임입니다." });
      return;
    }
    await Users.create({ nickname, password });

    res.status(201).send({ message: "회원 가입에 성공하였습니다." });
  } catch (err) {
    console.log(err);
    if (err.name === "CustomError") {
      return res.status(err.status).send({ errorMessage: err.message });
    } else return res.status(400).send({ errorMessage: err.message });
  }
});

module.exports = router;

 

Node.js 환경에서 mySQL 시작하기


node.js 개발 환경에서 mySQL을 사용하기 위해서는 세가지 방법이 있다.

1. 서버 컴퓨터 로컬에 mySQL을 다운 받아 로컬호스트로 연결하여 사용

2. 도커를 사용

3. AWS RDS를 사용

현재 수강 중인 강의에서는 3번 AWS RDS를 대여하여 사용하는 방법을 알려주었기에, 나도 그렇게 진행하고 있다.

 

지난번 포스팅에서는 Sequelize(ORM), migration, models에 대해 알아보았다.

 

[4주차][20221203] node.js에서 mySQL을 사용하기 위해 알아야 하는 것들 : Sequelize, Migration, Model

오늘은 mySQL을 node.js 환경에서 사용하는 방법을 배웠다. 솔직히 세션 중간에 졸았는데.. 이해가 하나도 안됐다..ㅠㅠ 일단, mongodb는 순한맛이었다. mySQL을 사용하기 위해서는 모델(스키마)만 있어

dev-jn.tistory.com

 

오늘은 내가 직접 db를 구성하기 위해 작업을 하며 조금 더 mysql에 대해 알게 된 것들을 포스팅 하려고 한다.

 

1. migration : 요 파일은 위의 포스팅보다 조금 더 쉽게 설명하자면, db의 테이블을 구성하는 document이다. sql은 nosql과 다르게, 미리 테이블을 세팅해 놓고 데이터를 사용해야 한다. 나중에 column을 추가한다거나, 삭제하는 것은 사전에 db 설계를 마치고 사용하는 SQL 데이터의 특성상 매우 어려운 작업이다. 그래서 미리 ERD 등을 만들어 데이터 설계 검증을 마치고 db create -> migration 파일을 알맞게 수정 -> db migrate 순으로 작업을 진행해야 한다.

 

2. models : 요 파일은 실제 테이블 안에 엔티티를 입력할 때 사용되는 model document이다. 데이터를 테이블에 CRUD하는데 필요한 형태로 구현해주는 것이다. 각각의 데이터를 지정한 형식에 맞춰 검증하고 다뤄준다.

  • 모델을 설정할때는, 관계에 관한 부분을 설정해주어야 한다. => static associate(models){} 함수 스코프 안에 직접 각 변수와 테이블간의 관계를 입력해주어야 한다.
  • 아래는 오늘 작업한 일부를 예시로 가져와봤다.
    • Users table의 userId는 primary key이고, 게시판에 사용되는 모든 table에 foreign key로 사용된다.
// Models/Users.js

"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
  class Users extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      this.hasMany(models.Posts, {
        as: "Posts",
        foreignKey: "userId",
      });
      this.hasMany(models.Comments, {
        as: "Comments",
        foreignKey: "userId",
      });
      this.hasMany(models.Likes, {
        as: "Likes",
        foreignKey: "userId",
      });
    }
  }
  Users.init(
    {
      userId: {
        primaryKey: true,
        type: DataTypes.INTEGER,
      },
      nickname: {
        allowNull: false,
        type: DataTypes.STRING,
      },
      password: {
        allowNull: false,
        type: DataTypes.STRING,
      },
      createdAt: {
        allowNull: false,
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
      },
      updatedAt: {
        allowNull: false,
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
      },
    },
    {
      sequelize,
      modelName: "Users",
    }
  );
  return Users;
};


// Models/Posts.js
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
  class Posts extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      this.belongsTo(models.Users, { foreignKey: "userId" });
      this.hasMany(models.Comments, {
        as: "Comments",
        foreignKey: "postId",
      });
      this.hasMany(models.Likes, {
        as: "Likes",
        foreignKey: "postId",
      });
    }
  }
  Posts.init(
    {
      postId: {
        primaryKey: true,
        type: DataTypes.INTEGER,
      },
      userId: {
        type: DataTypes.INTEGER,
        references: {
          model: "User",
          key: "userId",
        },
        onDelete: "cascade",
      },
      title: {
        type: DataTypes.STRING,
        allowNull: false,
      },
      content: {
        type: DataTypes.STRING,
        allowNull: false,
      },
      createdAt: {
        allowNull: false,
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
      },
      updatedAt: {
        allowNull: false,
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
      },
    },
    {
      sequelize,
      modelName: "Posts",
    }
  );
  return Posts;
};