[NodeJS] Sequelize + Typescript

seonjeongยท2023๋…„ 6์›” 16์ผ
0

NodeJS

๋ชฉ๋ก ๋ณด๊ธฐ
10/19
post-thumbnail

๐Ÿ’– Sequelize

  • Node.js์˜ ORM ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๊ฐ์ฒด์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋ฆด๋ ˆ์ด์…˜์„ ๋งคํ•‘ํ•ด์ค€๋‹ค
  • MySQL, PostgreSQL, MariaDB ๋“ฑ ๋งŽ์€ RDBMS๋ฅผ ์ง€์›ํ•œ๋‹ค

ORM (Object Relational Mapping)
: ๊ฐ์ฒด์™€ ๊ด€๊ณ„๋ฅผ ๋งคํ•‘ํ•ด์ฃผ๋Š” ๊ฒƒ


๐Ÿ’– TS์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ

sequelize๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์ง€๋งŒ ์—ฌ๊ธฐ์„œ๋Š” sequelize-typescript๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค

We recommend using sequelize-typescript to bridge the gap until our improvements are ready to be released.

-- sequelize ๊ณต์‹๋ฌธ์„œ ๋ฐœ์ทŒ


1. ์„ค์น˜

npm install --save-dev @types/node @types/validator
npm install sequelize reflect-metadata sequelize-typescript

2. ์„ค์ •

๐Ÿ“ tsconfig

"target": "es6",
"experimentalDecorators": true,
"emitDecoratorMetadata": true

๐Ÿ“ connection

// db_config.ts
import dotenv from "dotenv";

dotenv.config(); // .env ํŒŒ์ผ์˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋กœ๋“œ

const { DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE } = process.env;

const db_config = {
  dialect: "mysql" as const, // TypeScript์—๊ฒŒ ํ•ด๋‹น ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Œ์„ ์•Œ๋ ค์คŒ
  host: DB_HOST,
  username: DB_USER,
  password: DB_PASSWORD,
  database: DB_DATABASE,
  timezone: "+09:00",	// ํ•œ๊ตญ์‹œ๊ฐ„
};

export default db_config;


// connection.ts
import { Sequelize } from "sequelize-typescript";
import db_config from "../config/db_config";

const sequelize = new Sequelize({
  ...db_config,
  models: [__dirname + "/*Model.ts"],
  modelMatch: (filename, member) => {
    return filename.substring(0, filename.indexOf("Model")) === member;
  },
});

export default sequelize;


// index.ts
app.listen(app.get("port"), async () => {
  console.log("Express server listening on port " + app.get("port"));

  // ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ
  try {
    await sequelize.authenticate();
    console.log("Connection has been established successfully.");
  } catch (error) {
    console.error("Unable to connect to the database:", error);
  }
});

3. Model

  • sequelize์˜ ํ•ต์‹ฌ
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ํ…Œ์ด๋ธ”์„ ์ถ”์ƒํ™”ํ•œ ๊ฒƒ
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”๊ณผ์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ๋‹ด๋‹นํ•œ๋‹ค
  • entity๊ฐ€ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฒƒ๋“ค(๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ” ์ด๋ฆ„, ๋ฐ์ด๋ธ” ์ปฌ๋Ÿผ ๋“ฑ)์„ ์•Œ๋ ค์ค€๋‹ค
  • model์˜ ์ด๋ฆ„์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ” ์ด๋ฆ„๊ณผ ์ผ์น˜ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค
  • sequelize-typescript์—์„œ๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ชจ๋ธ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค

// userModel.ts
import { Table, Column, Model, DataType, HasMany } from "sequelize-typescript";

@Table({ tableName: "user" })
export class User extends Model {
  @Column({
    type: DataType.INTEGER,
    autoIncrement: true,
    primaryKey: true,
  })
  id!: number;

  @Column({
    type: DataType.STRING,
    allowNull: false,
  })
  email!: string;

  @Column({
    type: DataType.STRING,
    allowNull: false,
  })
  name!: string;

  @Column({
    type: DataType.STRING,
    allowNull: false,
  })
  password!: string;

  @Column({
    type: DataType.DATE,
    allowNull: false,
  })
  createdAt!: Date;

  @Column({
    type: DataType.DATE,
    allowNull: false,
  })
  updatedAt!: Date;

  @Column({
    type: DataType.BOOLEAN,
    allowNull: false,
  })
  del!: boolean;

 @HasMany(() => Friend)
  friends!: Friend[]; // User, Friend -> 1:N ๊ด€๊ณ„ ์„ค์ •
}

// friendModel.ts
import {
  Table,
  Column,
  Model,
  DataType,
  ForeignKey,
} from "sequelize-typescript";
import { User } from "../models/userModel";

@Table({ tableName: "friends" })
export class Friend extends Model {
  @ForeignKey(() => User)
  @Column({
    type: DataType.INTEGER,
    allowNull: false,
  })
  user_id!: number;

  @Column({ type: DataType.INTEGER })
  friend_id!: number;

  @Column({
    type: DataType.BOOLEAN,
    allowNull: false,
    defaultValue: 0,
  })
  status!: boolean;
}

4. Migration, Seeder

sequelize init์œผ๋กœ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ sequelize-cli๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๊ฒฝ๋กœ๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์—†๋‹ค -> .sequelizerc ์™€ ๊ฐ™์€ ์„ค์ • ํŒŒ์ผ์„ ํ†ตํ•ด ๊ฒฝ๋กœ๋ฅผ ๋งค์นญ์‹œ์ผœ์ฃผ์–ด์•ผ ํ•œ๋‹ค

๐Ÿ“ Migration

  • ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” Git๊ณผ ๊ฐ™์€ ๋ฒ„์ „ ์ œ์–ด ์‹œ์Šคํ…œ
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋‹ค
  • ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋‹ค๋ฅธ ์ƒํƒœ๋กœ ๋˜๋Š” ๊ทธ ๋ฐ˜๋Œ€๋กœ ์ „์†กํ•  ์ˆ˜ ์žˆ๋‹ค

๐Ÿ“ Seeder

  • ์„œ๋ฒ„๊ฐ€ ์‹œ์ž‘๋  ๋•Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•  ์ •์ ์ธ ๋ฐ์ดํ„ฐ๋“ค์„ DB์— ์ถ”๊ฐ€ํ•ด์ค€๋‹ค

Sequelize-cli๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์ค€๋น„

์„ค์น˜

npm i @babel/core @babel/register @babel/preset-env @babel/preset-typescript @babel/runtime @babel/plugin-transform-runtime typescript

babel ์„ค์ •

.babelrc ํ˜น์€ babel.config.json ํŒŒ์ผ์— ์„ค์ • ์ถ”๊ฐ€

{
  "presets": ["@babel/preset-env", "@babel/preset-typescript"],
  "plugins": ["@babel/plugin-transform-runtime"]
}

sequelizerc

// .sequelizerc
// Sequelize CLI๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ TypeScript ํŒŒ์ผ์„ ์ธ์‹ํ•˜๊ณ  ์ปดํŒŒ์ผํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•จ
require("@babel/register")({
  configFile: "./.babelrc",
  extensions: [".js", ".ts"],
});

const path = require('path');

module.exports = {
  'config': path.resolve('config', 'config.js'),	// ํ™•์žฅ์ž๋Š” json ํ˜น์€ js
  'models-path': path.resolve('db', 'models'),
  'seeders-path': path.resolve('db', 'seeders'),
  'migrations-path': path.resolve('db', 'migrations')
};

์ฐธ๊ณ ํ•  ๊ฒƒ
https://www.npmjs.com/package/sequelize-cli
https://hoontae24.github.io/17


++ ์ถ”๊ฐ€์‚ฌํ•ญ

modelํŒŒ์ผ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜ ๋ฐœ์ƒ

  @Column({
                                       ^
TypeError: Cannot convert undefined or null to object

tsconfig.json์— ์„ค์ •์„ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋œ๋‹ค

 {
  "compilerOptions": {
    "experimentalDecorators": true, // ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๋ฌธ๋ฒ• ์‚ฌ์šฉ ํ—ˆ์šฉ
    "emitDecoratorMetadata": true // ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ƒ์„ฑ
  }
}




Reference

npm sequelize-typescript
sequelize ๊ณต์‹ ๋ฌธ์„œ
https://stackoverflow.com/questions/76105765/typeerror-in-sequelize-typescript-model

profile
๐Ÿฆ‹๊ฐœ๋ฐœ ๊ณต๋ถ€ ๊ธฐ๋ก๐Ÿฆ‹

0๊ฐœ์˜ ๋Œ“๊ธ€