본문 바로가기
JavaScript/Node.js

Node) 시퀄라이즈 정리

by 박채니 2023. 1. 3.
안녕하세요, 코린이의 코딩 학습기 채니 입니다.
개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.

 

초기 세팅

$ yarn add express morgan sequelize sequelize-cli mysql2
$ yarn add typescript
$ yarn add -D nodemon
$ npx sequelize init
$ yarn init -y
$ npx tsc --init

models, seeders, config, migrations 파일 생성이 되고, package.json 수정

 

package.json

{
  "name": "sequelize-test",
  "version": "1.0.0",
  "description": "시퀄라이즈 연습하기",
  "main": "app.js",
  "scripts": {
    "start": "nodemon app"
  },
  "author": "chany",
  "license": "MIT",
  "dependencies": {
    "express": "^4.18.2",
    "morgan": "^1.10.0",
    "mysql2": "^2.3.3",
    "sequelize": "^6.28.0",
    "sequelize-cli": "^6.5.2",
    "typescript": "^4.9.4"
  },
  "devDependencies": {
    "nodemon": "^2.0.20"
  }
}

 

config/config.json 파일의 development에 username, password, database 등을 수정한 후 데이터베이스 생성

$ npx sequelie db:create

Sequelize CLI [Node: 18.12.1, CLI: 6.5.2, ORM: 6.28.0]

Loaded configuration file "config/config.json".
Using environment "development".
Database nodeBird created.

 

config/config.json → config/config.ts 로 수정

// 객체 config에 대한 타입 정의
type Config = {
  username: string;
  password: string;
  database: string;
  host: string;
  [key: string]: string;
};

interface IConfigGroup {
  development: Config;
  test: Config;
  production: Config;
}

const config: IConfigGroup = {
  development: {
    username: "node",
    password: "test123",
    database: "nodeBird",
    host: "127.0.0.1",
    dialect: "mysql",
  },
  test: {
    username: "root",
    password: "test123",
    database: "database_test",
    host: "127.0.0.1",
    dialect: "mysql",
  },
  production: {
    username: "root",
    password: "test123",
    database: "database_production",
    host: "127.0.0.1",
    dialect: "mysql",
  },
};

export default config;

 

model/index.ts 수정

아래 코드는 참고만!

"use strict";

import { Sequelize } from "sequelize";
import config from "../config/config";

interface dbType {
  sequelize?: Sequelize;
}

const env =
  (process.env.NODE_ENV as "production" | "test" | "development") ||
  "development";
const db: dbType = {};

// config 파일에서 데이터베이스 설정을 불러와 new Sequelize를 통해 MySQL 연결 객체 생성
const { database, username, password } = config[env];
let sequelize = new Sequelize(database, username, password, config[env]);

// 연결 객체를 추후 재사용을 위해 db 객체에 담아 export 처리
db.sequelize = sequelize;

export default db;

 

models/index.ts

"use strict";

import { Sequelize } from "sequelize";
import config from "../config/config";
import User from "./user";
import Post from "./post";
import Tag from "./tag";

interface IDb {
  sequelize: Sequelize;
}

// config 파일에서 데이터베이스 설정을 불러와 new Sequelize를 통해 MySQL 연결 객체 생성
const { database, username, password } = config["development"];
let sequelize = new Sequelize(
  database,
  username,
  password,
  config["development"]
);

// 연결 객체를 추후 재사용을 위해 db 객체에 담아 export 처리
const db: IDb = { sequelize };

export default db;

 

 

시퀄라이즈를 통해 익스프레스 앱과 MySQL 연결하기

app.ts

아래 코드 중 미들웨어, 에러 렌더링 등등이 필요하지 않으므로 참고만!

import express, { NextFunction, Request, Response } from "express";
import morgan from "morgan";
import path from "path";
// 시퀄라이즈 객체가 담긴 db를 import
import db from "./models";

const app = express();
app.set("port", process.env.PORT || 8001); // 포트 설정

// 시퀄라이즈 초기화
db.sequelize
  ?.sync({ force: false })
  .then(() => {
    console.log("데이터베이스 연결 성공!");
  })
  .catch((error) => {
    console.error(error);
  });

// 미들웨어
app.use(morgan("dev"));
app.use(express.static(path.join(__dirname, "public")));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

interface Error {
  message?: string;
  status?: number;
}

app.use((req: Request, res: Response, next: NextFunction) => {
  const error: Error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  next(error);
});

app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV !== "production" ? err : {};
  res.status(err.status || 500);
  res.render("error");
});

// 포트 연결
app.listen(app.get("port"), () => {
  console.log(app.get("port"), "번에서 대기 중!");
});

 

.env

PORT=8002

 

app.ts

import express, { NextFunction, Request, Response } from "express";
// 시퀄라이즈 객체가 담긴 db를 import
import db from "./models";
import indexRouter from "./routes";
import dotenv from "dotenv"; // .env 파일을 읽어서 process.env로 만듦

dotenv.config();

const app = express();
app.set("port", process.env.PORT || 8001); // 포트 설정

// 시퀄라이즈 초기화
db.sequelize
  ?.sync({ force: true })
  .then(() => {
    console.log("데이터베이스 연결 성공!");
  })
  .catch((error) => {
    console.error(error);
  });

app.use("/", indexRouter);

// 포트 연결
app.listen(app.get("port"), () => {
  console.log(app.get("port"), "번에서 대기 중!");
});
$ npx tsc && yarn start

yarn run v1.22.19
$ nodemon app
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node app.js`
8001 번에서 대기 중!
Executing (default): SELECT 1+1 AS result
데이터베이스 연결 성공!

 

모델 및 관계 정의하기

  • User - Post : 1:N 관계
  • Post - Tag : M:N 관계

 

models/index.ts

"use strict";

import { Sequelize } from "sequelize";
import config from "../config/config";
import User from "./user";
import Post from "./post";
import Tag from "./tag";

interface IDb {
  sequelize: Sequelize;
}

// config 파일에서 데이터베이스 설정을 불러와 new Sequelize를 통해 MySQL 연결 객체 생성
const { database, username, password } = config["development"];
let sequelize = new Sequelize(
  database,
  username,
  password,
  config["development"]
);

const db: IDb = { sequelize };

// 모델 생성 메소드 호출
User.initiate(sequelize);
Post.initiate(sequelize);
Tag.initiate(sequelize);

// 관계 설정 메소드 호출
User.associate();
Post.associate();
Tag.associate();

export default db;

 

models/user.ts

// User 모델
import { Sequelize, Model, DataTypes } from "sequelize";
import Post from "./post";

interface UserAttributes {
  username: string;
  password: string;
  email?: string;
}

class User extends Model<UserAttributes> {
  // id can be undefined during creation when using `autoIncrement`
  public readonly id!: number;
  public username!: string;
  public password!: string;
  public email?: string;
  public readonly createdAt!: Date;
  public readonly updatedAt!: Date;

  static initiate(sequelize: Sequelize) {
    User.init(
      {
        username: {
          type: DataTypes.STRING(50),
          allowNull: false,
          unique: true,
        },
        password: {
          type: DataTypes.STRING(15),
          allowNull: false,
        },
        email: {
          type: DataTypes.STRING(50),
          allowNull: true,
        },
      },
      {
        sequelize,
        timestamps: true,
        underscored: true, // true
        modelName: "User",
        tableName: "users",
        paranoid: false,
        charset: "utf8",
        collate: "utf8_general_ci",
      }
    );
  }

  static associate() {
    User.hasMany(Post);
  }
}

export default User;

 

models/post.ts

import {
  Sequelize,
  Model,
  DataTypes,
  BelongsToManyAddAssociationMixin,
} from "sequelize";
import Tag from "./tag";
import User from "./user";

// Post 모델 정의
interface PostAttributes {
  title: string;
  content: string;
  user_id?: number;
}

class Post extends Model<PostAttributes> {
  public readonly id!: number;
  public title!: string;
  public content!: string;
  public readonly createdAt!: Date;
  public readonly updatedAt!: Date;

  public addTag!: BelongsToManyAddAssociationMixin<Tag, string>;

  static initiate(sequelize: Sequelize) {
    Post.init(
      {
        title: {
          type: DataTypes.STRING(30),
          allowNull: false,
        },
        content: {
          type: DataTypes.STRING(200),
          allowNull: false,
        },
      },
      {
        sequelize,
        timestamps: true,
        underscored: true,
        modelName: "Post",
        tableName: "posts",
        paranoid: false,
        charset: "utf8mb4",
        collate: "utf8mb4_general_ci",
      }
    );
  }

  static associate() {
    Post.belongsTo(User);
    Post.belongsToMany(Tag, { through: "PostTag" });
  }
}

export default Post;

 

models/tag.ts

import { Sequelize, Model, DataTypes } from "sequelize";
import Post from "./post";

// Tag 모델 정의
interface TagAttributes {
  title: string;
}

class Tag extends Model<TagAttributes> {
  public readonly id!: number;
  public title!: string;
  public readonly createdAt!: Date;
  public readonly updatedAt!: Date;

  static initiate(sequelize: Sequelize) {
    Tag.init(
      {
        title: {
          type: DataTypes.STRING(15),
          allowNull: false,
          unique: true,
        },
      },
      {
        sequelize,
        timestamps: true,
        underscored: true,
        modelName: "Tag",
        tableName: "tags",
        paranoid: false,
        charset: "utf8mb4",
        collate: "utf8mb4_general_ci",
      }
    );
  }

  static associate() {
    Tag.belongsToMany(Post, { through: "PostTag" });
  }
}

export default Tag;

 

라우터

routes/index.ts

import express from "express";
import index from "../controllers";

const router = express.Router();

router.get("/", index);

export default router;

 

컨트롤러

controllers/index.ts

import User from "../models/user";
import Post from "../models/post";
import Tag from "../models/tag";
import { NextFunction, Request, Response } from "express";

export default async (req: Request, res: Response, next: NextFunction) => {
  try {
    // User 생성 전 조회하기
    let user = await User.findOne({
      where: { username: "chany" },
    });
    if (!user) {
      // 존재하지 않는 경우 User 생성
      user = await User.create({
        username: "chany",
        password: "test1234",
        email: "chany@abc.com",
      });
    }

    const tag1 = await Tag.create({ title: "Tag1" });
    const tag2 = await Tag.create({ title: "Tag2" });
    const tag3 = await Tag.create({ title: "Tag3" });

    // 게시글1 추가 및 브릿지 테이블 컬럼 추가
    const post1 = await Post.create({
      title: "안녕하세요1",
      content: "노드 뿌시기!",
      user_id: user.id,
    });
    await post1.addTag(tag1);
    await post1.addTag(tag2);

    // 게시글2 추가 및 브릿지 테이블 컬럼 추가
    const post2 = await Post.create({
      title: "안녕하세요2",
      content: "내가 뿌셔짐!",
      user_id: user.id,
    });
    await post2.addTag(tag1);
    await post2.addTag(tag3);

    console.log(
      await User.findOne({
        include: [
          {
            model: Post,
            include: [{ model: Tag }],
          },
        ],
        where: { username: "chany" },
      })
    );
  } catch (error) {
    console.error(error);
    next(error);
  }
};

// const addTagToPost = (post: Post, tagList: Tag[]) => {
//   tagList.forEach((tag) => {
//     post.addTag(tag);
//   });
// };

 

  • 필요 없는 코드는 넣지 않기
  • index에서 모델을 db.User 등으로 담아서 사용하지 말고, 그냥 import!
  • ID값은 primarykey, autoIncrement 설정
  • underscored: true 설정
  • 위 코드 중 id 값들에 대해 따로 autoIncrement 설정을 하지 않았는데 어떻게 1씩 증가하며 번호가 매겨지는가?
    • id can be undefined during creation when using 'autoIncrement' → 모델로 인해 테이블이 생성될 때 id값이 undefined면 시퀄라이즈에서 autoIncrement 설정을 추가하여 생성한다고 함!
  • .env는 dotenv 패키지를 다운 받은 후 dotenv.config()를 하면 .env 파일의 내용을 process.env로 만들어줌
    따라서 중요한 정보, 비밀번호 등은 .env 파일에 만들어 불러와서 사용!