저장을 습관화

MySQL sequelize 연습 기록 2 본문

공부/node.js

MySQL sequelize 연습 기록 2

ctrs 2023. 7. 8. 02:06

메모

작업 환경 Windows 10, VSC, sequelize, MySQL, AWS

 

mysql-sequelize 초기 환경 구성

0. npm 시작

$ npm init -y



1. 필요한 라이브러리 설치
1-1. express, sequelize, mysql2 라이브러리 설치

$ npm install express sequelize mysql2 cookie-parser jsonwebtoken

 


1-2. sequelize-cli, nodemon 라이브러리를 DevDependency로 설치
sequelize-cli는 sequelize 명령어를 터미널에서 직접 실행할 수 있게 해주는 도구이다

$ npm install -D sequelize-cli nodemon

 


1-3. 설치한 sequelize를 초기화 하여, sequelize를 사용할 수 있는 구조를 생성합니다.

$ npx sequelize init

config, migrations, models, seeders 폴더가 생성됨을 확인할 수 있다



2. package.json 내용 추가

...
"scripts": {
"start":"nodemon app.js"
},
...



3. config/config.json 파일 설정
"username" : AWS RDS DB 생성할때 입력한 마스터 사용자
"password" : AWS RDS DB 생성할때 입력한 마스터 사용자
"database" : 사용할 DB의 이름
"host" : AWS RDS DB 생성 후 발급받은 엔드포인트
"dialect" : "mysql" 다른 DB(postgres 등) 사용할거면 그거

 

 

4. DB 생성

$ npx sequelize db:create

MySQL에서 DB 생성 확인

이번 실습에서의 DB명은 sequelize_relation

 

 

5. models, migrations 생성

# Users 모델
npx sequelize model:generate --name Users --attributes email:string,password:string

# UserInfos 모델
npx sequelize model:generate --name UserInfos --attributes UserId:integer,name:string,age:integer,gender:string,profileImage:string

# Posts 모델
npx sequelize model:generate --name Posts --attributes UserId:integer,title:string,content:string

# Comments 모델
npx sequelize model:generate --name Comments --attributes UserId:integer,PostId:integer,comment:string

사용할 테이블도 많고, 각 테이블에 등록될 속성도 많다

작성하면서 DB 내리고 올리고 반복하려면 번거로우니

개발 진행 전에 계획을 잘 세워 ERD를 그리고 시작하도록 하자

 

파일 확인

 

 

6. migration 파일 수정

- 20230707140753-create-users.js

"use strict";
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable("Users", {
      userId: {
        allowNull: false, // NOT NULL
        autoIncrement: true, // AUTO_INCREMENT
        primaryKey: true, // Primary Key (기본키)
        type: Sequelize.INTEGER,
      },
      email: {
        allowNull: false, // NOT NULL
        type: Sequelize.STRING,
        unique: true,
      },
      password: {
        allowNull: false, // NOT NULL
        type: Sequelize.STRING,
      },
      createdAt: {
        allowNull: false, // NOT NULL
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn("now"),
      },
      updatedAt: {
        allowNull: false, // NOT NULL
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn("now"),
      },
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable("Users");
  },
};

 

- 20230707140833-create-posts.js

"use strict";
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable("Posts", {
      postId: {
        allowNull: false, // NOT NULL
        autoIncrement: true, // AUTO_INCREMENT
        primaryKey: true, // Primary Key (기본키)
        type: Sequelize.INTEGER,
      },
      UserId: {
        allowNull: false, // NOT NULL
        type: Sequelize.INTEGER,
        references: {
          model: "Users", // Users 모델을 참조합니다.
          key: "userId", // Users 모델의 userId를 참조합니다.
        },
        onDelete: "CASCADE", 
        // 만약 Users 모델의 userId가 삭제되면(회원 탈퇴 등), 
        // Posts 모델의 데이터가 삭제됩니다(사용자가 작성했던 모든 게시글이 삭제된다.).
      },
      title: {
        allowNull: false, // NOT NULL
        type: Sequelize.STRING,
      },
      content: {
        allowNull: false, // NOT NULL
        type: Sequelize.STRING,
      },
      createdAt: {
        allowNull: false, // NOT NULL
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn("now"),
      },
      updatedAt: {
        allowNull: false, // NOT NULL
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn("now"),
      },
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable("Posts");
  },
};

 

게시글(Posts) 모델은 사용자(Users) 모델과 N:1 관계를 가지고 있다. (한 명의 사용자가 여러 게시글을 작성할 수 있음)

 

- onDelete: "CASCADE"

만약 게시글을 작성한 사용자가 회원 탈퇴(onDelete)할 경우 사용자가 작성한 모든 게시글을 삭제한다.

 

사용자 정보 변경에 따라 참조하는 모델도 수정하고 싶다면 onUpdate를 사용하면 된다.

 

 

- 20230707140805-create-user-infos.js

"use strict";
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable("UserInfos", {
      userInfoId: {
        allowNull: false, // NOT NULL
        autoIncrement: true, // AUTO_INCREMENT
        primaryKey: true, // Primary Key (기본키)
        type: Sequelize.INTEGER,
      },
      UserId: {
        allowNull: false, // NOT NULL
        type: Sequelize.INTEGER,
        unique: true, // 1명의 유저는 1개의 사용자 정보만 가질 수 있다.
        references: {
          model: "Users", // Users 모델을 참조합니다.
          key: "userId", // Users 모델의 userId를 참조합니다.
        },
        onDelete: "CASCADE", // 만약 Users 모델의 userId가 삭제되면, UserInfos 모델의 데이터가 삭제됩니다.
      },
      name: {
        allowNull: false, // NOT NULL
        type: Sequelize.STRING,
      },
      age: {
        allowNull: false, // NOT NULL
        type: Sequelize.INTEGER,
      },
      gender: {
        allowNull: false, // NOT NULL
        type: Sequelize.STRING,
      },
      profileImage: {
        type: Sequelize.STRING,
      },
      createdAt: {
        allowNull: false, // NOT NULL
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn("now"),
      },
      updatedAt: {
        allowNull: false, // NOT NULL
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn("now"),
      },
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable("UserInfos");
  },
};

 

users와 userinfos는 서로 1:1 관계를 가지고 있다.

 

 

- 20230707140839-create-comments.js

"use strict";
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable("Comments", {
      commentId: {
        allowNull: false, // NOT NULL
        autoIncrement: true, // AUTO_INCREMENT
        primaryKey: true, // Primary Key (기본키)
        type: Sequelize.INTEGER,
      },
      UserId: {
        allowNull: false, // NOT NULL
        type: Sequelize.INTEGER,
        references: {
          model: "Users", // Users 모델을 참조합니다.
          key: "userId", // Users 모델의 userId를 참조합니다.
        },
        onDelete: "CASCADE", // 만약 Users 모델의 userId가 삭제되면, Comments 모델의 데이터가 삭제됩니다.
      },
      PostId: {
        allowNull: false, // NOT NULL
        type: Sequelize.INTEGER,
        references: {
          model: "Posts", // Posts 모델을 참조합니다.
          key: "postId", // Posts 모델의 postId를 참조합니다.
        },
        onDelete: "CASCADE", // 만약 Posts 모델의 postId가 삭제되면, Comments 모델의 데이터가 삭제됩니다.
      },
      comment: {
        allowNull: false, // NOT NULL
        type: Sequelize.STRING,
      },
      createdAt: {
        allowNull: false, // NOT NULL
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn("now"),
      },
      updatedAt: {
        allowNull: false, // NOT NULL
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn("now"),
      },
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable("Comments");
  },
};

 

7. DB 테이블 생성

$ npx sequelize db:migrate

 

8. models 파일 수정

- /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) {
      // define association here

      // 1. Users 모델에서
      this.hasOne(models.UserInfos, {
        // 2. UserInfos 모델에게 1:1 관계 설정을 합니다.
        sourceKey: "userId", // 3. Users 모델의 userId 컬럼을
        foreignKey: "UserId", // 4. UserInfos 모델의 UserId 컬럼과 연결합니다.
      });

      // 1. Users 모델에서
      this.hasMany(models.Posts, {
        // 2. Posts 모델에게 1:N 관계 설정을 합니다.
        sourceKey: "userId", // 3. Users 모델의 userId 컬럼을
        foreignKey: "UserId", // 4. Posts 모델의 UserId 컬럼과 연결합니다.
      });
    }
  }

  Users.init(
    {
      userId: {
        allowNull: false, // NOT NULL
        autoIncrement: true, // AUTO_INCREMENT
        primaryKey: true, // Primary Key (기본키)
        type: DataTypes.INTEGER,
      },
      email: {
        allowNull: false, // NOT NULL
        type: DataTypes.STRING,
        unique: true,
      },
      password: {
        allowNull: false, // NOT NULL
        type: DataTypes.STRING,
      },
      createdAt: {
        allowNull: false, // NOT NULL
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
      },
      updatedAt: {
        allowNull: false, // NOT NULL
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
      },
    },
    {
      sequelize,
      modelName: "Users",
    }
  );
  return Users;
};

 

 

- /models/userinfos.js

"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
  class UserInfos 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) {
      // define association here

      // 1. UserInfos 모델에서
      this.belongsTo(models.Users, {
        // 2. Users 모델에게 1:1 관계 설정을 합니다.
        targetKey: "userId", // 3. Users 모델의 userId 컬럼을
        foreignKey: "UserId", // 4. UserInfos 모델의 UserId 컬럼과 연결합니다.
      });
    }
  }

  UserInfos.init(
    {
      userInfoId: {
        allowNull: false, // NOT NULL
        autoIncrement: true, // AUTO_INCREMENT
        primaryKey: true, // Primary Key (기본키)
        type: DataTypes.INTEGER,
      },
      UserId: {
        allowNull: false, // NOT NULL
        type: DataTypes.INTEGER,
        unique: true, // UNIQUE
      },
      name: {
        allowNull: false, // NOT NULL
        type: DataTypes.STRING,
      },
      age: {
        allowNull: false, // NOT NULL
        type: DataTypes.INTEGER,
      },
      gender: {
        allowNull: false, // NOT NULL
        type: DataTypes.STRING,
      },
      profileImage: {
        type: DataTypes.STRING,
      },
      createdAt: {
        allowNull: false, // NOT NULL
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
      },
      updatedAt: {
        allowNull: false, // NOT NULL
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
      },
    },
    {
      sequelize,
      modelName: "UserInfos",
    }
  );
  return UserInfos;
};

 

users와 userinfos는 1:1 관계를 맺고 있다.

// users.js
...
      // 1. Users 모델에서
      this.hasOne(models.UserInfos, {
        // 2. UserInfos 모델에게 1:1 관계 설정을 합니다.
        sourceKey: "userId", // 3. Users 모델의 userId 컬럼을
        foreignKey: "UserId", // 4. UserInfos 모델의 UserId 컬럼과 연결합니다.
      });
...


// userinfos.js
...
      // 1. UserInfos 모델에서
      this.belongsTo(models.Users, {
        // 2. Users 모델에게 1:1 관계 설정을 합니다.
        targetKey: "userId", // 3. Users 모델의 userId 컬럼을
        foreignKey: "UserId", // 4. UserInfos 모델의 UserId 컬럼과 연결합니다.
      });
...

sourceKey는 내가 상대에게 줄 컬럼

targetKey는 내가 상대에게서 받아올 컬럼

 

 

- /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) {
      // define association here

      // 1. Posts 모델에서
      this.belongsTo(models.Users, {
        // 2. Users 모델에게 N:1 관계 설정을 합니다.
        targetKey: "userId", // 3. Users 모델의 userId 컬럼을
        foreignKey: "UserId", // 4. Posts 모델의 UserId 컬럼과 연결합니다.
      });
    }
  }

  Posts.init(
    {
      postId: {
        allowNull: false, // NOT NULL
        autoIncrement: true, // AUTO_INCREMENT
        primaryKey: true, // Primary Key (기본키)
        type: DataTypes.INTEGER,
      },
      UserId: {
        allowNull: false, // NOT NULL
        type: DataTypes.INTEGER,
      },
      title: {
        allowNull: false, // NOT NULL
        type: DataTypes.STRING,
      },
      content: {
        allowNull: false, // NOT NULL
        type: DataTypes.STRING,
      },
      createdAt: {
        allowNull: false, // NOT NULL
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
      },
      updatedAt: {
        allowNull: false, // NOT NULL
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
      },
    },
    {
      sequelize,
      modelName: "Posts",
    }
  );
  return Posts;
};

 

users와 posts는 1:N 관계를 맺고 있다.

// users.js
...
      // 1. Users 모델에서
      this.hasMany(models.Posts, {
        // 2. Posts 모델에게 1:N 관계 설정을 합니다.
        sourceKey: "userId", // 3. Users 모델의 userId 컬럼을
        foreignKey: "UserId", // 4. Posts 모델의 UserId 컬럼과 연결합니다.
      });
...

// posts.js
...
      // 1. Posts 모델에서
      this.belongsTo(models.Users, {
        // 2. Users 모델에게 N:1 관계 설정을 합니다.
        targetKey: "userId", // 3. Users 모델의 userId 컬럼을
        foreignKey: "UserId", // 4. Posts 모델의 UserId 컬럼과 연결합니다.
      });
...

sourceKey는 내가 상대에게 줄 컬럼

targetKey는 내가 상대에게서 받아올 컬럼

 

 

 

여기서 잠깐, 이전 작업에서는 npx sequelize db:migrate:undo, npx sequelize db:migrate로

migration 파일들과 MySQL 테이블을 맵핑시켰지만

현재 model 파일을 바탕으로 데이터베이스에서 테이블을 삭제하고 재생성하도록 한다면?

더보기

 - app.js

const { sequelize } = require("./models/index.js");

async function main() {
  // model을 이용해 데이터베이스의 테이블을 삭제 후 생성합니다.
  await sequelize.sync({ force: true });
}

main();

 

- 터미널

$ npx sequelize db:drop

$ npx sequelize db:create

$ node app.js
Executing (default): DROP TABLE IF EXISTS `UserInfos`;
Executing (default): DROP TABLE IF EXISTS `Posts`;
Executing (default): DROP TABLE IF EXISTS `Users`;
Executing (default): DROP TABLE IF EXISTS `Comments`;
Executing (default): SELECT CONSTRAINT_NAME as constraint_name,CONSTRAINT_NAME as constraintName,CONSTRAINT_SCHEMA as constraintSchema,CONSTRAINT_SCHEMA as constraintCatalog,TABLE_NAME as tableName,TABLE_SCHEMA as tableSchema,TABLE_SCHEMA as tableCatalog,COLUMN_NAME as columnName,REFERENCED_TABLE_SCHEMA as referencedTableSchema,REFERENCED_TABLE_SCHEMA as referencedTableCatalog,REFERENCED_TABLE_NAME as referencedTableName,REFERENCED_COLUMN_NAME as referencedColumnName FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = 'Comments' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='sequelize_relation' AND REFERENCED_TABLE_NAME IS NOT NULL;
Executing (default): SELECT CONSTRAINT_NAME as constraint_name,CONSTRAINT_NAME as constraintName,CONSTRAINT_SCHEMA as constraintSchema,CONSTRAINT_SCHEMA as constraintCatalog,TABLE_NAME as tableName,TABLE_SCHEMA as tableSchema,TABLE_SCHEMA as tableCatalog,COLUMN_NAME as columnName,REFERENCED_TABLE_SCHEMA as referencedTableSchema,REFERENCED_TABLE_SCHEMA as referencedTableCatalog,REFERENCED_TABLE_NAME as referencedTableName,REFERENCED_COLUMN_NAME as referencedColumnName FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = 'Posts' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='sequelize_relation' AND REFERENCED_TABLE_NAME IS NOT NULL;
Executing (default): SELECT CONSTRAINT_NAME as constraint_name,CONSTRAINT_NAME as constraintName,CONSTRAINT_SCHEMA as constraintSchema,CONSTRAINT_SCHEMA as constraintCatalog,TABLE_NAME as tableName,TABLE_SCHEMA as tableSchema,TABLE_SCHEMA as tableCatalog,COLUMN_NAME as columnName,REFERENCED_TABLE_SCHEMA as referencedTableSchema,REFERENCED_TABLE_SCHEMA as referencedTableCatalog,REFERENCED_TABLE_NAME as referencedTableName,REFERENCED_COLUMN_NAME as referencedColumnName FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = 'UserInfos' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='sequelize_relation' AND REFERENCED_TABLE_NAME IS NOT NULL;
Executing (default): SELECT CONSTRAINT_NAME as constraint_name,CONSTRAINT_NAME as constraintName,CONSTRAINT_SCHEMA as constraintSchema,CONSTRAINT_SCHEMA as constraintCatalog,TABLE_NAME as tableName,TABLE_SCHEMA as tableSchema,TABLE_SCHEMA as tableCatalog,COLUMN_NAME as columnName,REFERENCED_TABLE_SCHEMA as referencedTableSchema,REFERENCED_TABLE_SCHEMA as referencedTableCatalog,REFERENCED_TABLE_NAME as referencedTableName,REFERENCED_COLUMN_NAME as referencedColumnName FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = 'Users' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='sequelize_relation' AND REFERENCED_TABLE_NAME IS NOT NULL;
Executing (default): DROP TABLE IF EXISTS `Comments`;
Executing (default): DROP TABLE IF EXISTS `Posts`;
Executing (default): DROP TABLE IF EXISTS `UserInfos`;
Executing (default): DROP TABLE IF EXISTS `Users`;
Executing (default): DROP TABLE IF EXISTS `Comments`;
Executing (default): CREATE TABLE IF NOT EXISTS `Comments` (`id` INTEGER NOT NULL auto_increment , `UserId` INTEGER, `PostId` INTEGER, `comment` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `Comments` FROM `sequelize_relation`
Executing (default): DROP TABLE IF EXISTS `Users`;
Executing (default): CREATE TABLE IF NOT EXISTS `Users` (`userId` INTEGER NOT NULL auto_increment , 
`email` VARCHAR(255) NOT NULL UNIQUE, `password` VARCHAR(255) NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`userId`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `Users` FROM `sequelize_relation`
Executing (default): DROP TABLE IF EXISTS `Posts`;
Executing (default): CREATE TABLE IF NOT EXISTS `Posts` (`postId` INTEGER NOT NULL auto_increment , 
`UserId` INTEGER NOT NULL, `title` VARCHAR(255) NOT NULL, `content` VARCHAR(255) NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`postId`), FOREIGN KEY (`UserId`) 
REFERENCES `Users` (`userId`) ON DELETE NO ACTION ON UPDATE CASCADE) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `Posts` FROM `sequelize_relation`
Executing (default): DROP TABLE IF EXISTS `UserInfos`;
Executing (default): CREATE TABLE IF NOT EXISTS `UserInfos` (`userInfoId` INTEGER NOT NULL auto_increment , `UserId` INTEGER NOT NULL UNIQUE, `name` VARCHAR(255) NOT NULL, `age` INTEGER NOT NULL, `gender` VARCHAR(255) NOT NULL, `profileImage` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` 
DATETIME NOT NULL, PRIMARY KEY (`userInfoId`), FOREIGN KEY (`UserId`) REFERENCES `Users` (`userId`) 
ON DELETE NO ACTION ON UPDATE CASCADE) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `UserInfos` FROM `sequelize_relation`

 

이렇게 해서도 DB까 삭제 후 재생성 되는것은 확인할 수 있다.

하지만 추천하지 않는 방법이다

 

sequelize의 model 만으로 table 설계가 가능한데도 migration을 써야하는 이유는

1) sequelize의 model의 설계가 변경될 경우 현재 서비스 중인 테이블이 삭제된 후 재생성된다.

-> DB에 저장되어있는 데이터가 전부 사라진다.

 

2) MySQL의 테이블이 Node.JS application에 의존적이 된다.

서버 인프라를 구현해야할때 sequelize.sync 명령어가 수행되어야만 MySQL의 테이블을 구현할 수 있게 된다면

현재 DB의 상황을 인지하기 힘들어지고, MySQL에 직접 등록한 부가기능을 이해하기 힘들어진다.

 

특히 model에서 작업하기 힘든 MySQL의 기능 View, Index, Scheduler, Trigger와 같은 기능들은 실제 MySQL에 접근하여 작업하는 것이 수월하며,

 

MySQL에서 별도의 쿼리로 수정하더라도, sequelize.sync를 포함하는 프로젝트를 실행할 때 마다 테이블의 속성이 초기화 될 위험이 있다.

 

이렇게 다양한 문제점으로 인해 sync 명령어를 이용하여 테이블을 생성하는 것은 좋지 못한 방법이다.

서버의 안정성과 개발 편의성을 위해서라도 migration을 이용하는 것이 model보다 관리가 수월하고,

cli로서 테이블을 구현할 수 있어 더 좋은 방법이라고 할 수 있다.

 

9. API 작성

app.js 생성

const express = require("express");
const cookieParser = require("cookie-parser");
// 얘가 있어야지 auth-middleware.js가 req.cookies로 쿠키 정보를 받을 수 있음
const usersRouter = require("./routes/users.route");
const postsRouter = require("./routes/posts.route");
const app = express();
const PORT = 3018;

app.use(express.json());
app.use(cookieParser());
// 얘가 있어야지 auth-middleware.js가 req.cookies로 쿠키 정보를 받을 수 있음
app.use("/api", [usersRouter, postsRouter]);

app.listen(PORT, () => {
  console.log(PORT, "포트 번호로 서버가 실행되었습니다.");
});

 

/routes/users.route.js 생성

const express = require("express");
const router = express.Router();
const authMiddleware = require("../middlewares/auth-middleware");
const { Posts } = require("../models");

// 게시글 생성 API
router.post("/posts", authMiddleware, async (req, res) => {
  // 게시글을 생성하는 사용자 정보를 가지고 온다.
  // auth-middleware에서 만들었던 복호화된 사용자 정보
  const { userId } = res.locals.user;
  const { title, content } = req.body;

  const post = await Posts.create({
    UserId: userId,
    title,
    content,
  });

  return res.status(201).json({ data: post });
});

// 게시글 목록 조회 API
router.get("/posts", async (req, res) => {
  const posts = await Posts.findAll({
    attributes: ["postId", "title", "createdAt", "updatedAt"],
    order: [["createdAt", "DESC"]], // 생성일을 기준으로 내립차순으로 정렬
  });

  return res.status(200).json({ data: posts });
});

// 게시글 상세 조회 API
router.get("/posts/:postId", async (req, res) => {
  const { postId } = req.params;
  const post = await Posts.findOne({
    attributes: ["postId", "title", "content", "createdAt", "updatedAt"],
    where: { postId: postId },
  });
  return res.status(200).json({ data: post });
});

module.exports = router;

 

/middlewares/auth-middleware.js 생성

const jwt = require("jsonwebtoken");
const { Users } = require("../models");

module.exports = async (req, res, next) => {
  const { authorization } = req.cookies;
  const [tokenType, token] = authorization.split(" "); // [배열] 형태임! 중요!
  if (tokenType !== "Bearer" || !token) {
    console.log(tokenType);
    console.log(token);
    return res.status(401).json({
      errorMessage: "토큰 타입이 일치하지 않거나, 토큰이 존재하지 않습니다.",
    });
  }

  try {
    // 토큰 검증 및 복호화
    const decodedToken = jwt.verify(token, "customized_secret_key");
    const userId = decodedToken.userId;

    const user = await Users.findOne({ where: { userId: userId } });
    if (!user) {
      return res
        .status(401)
        .json({ errorMessage: "토큰에 해당하는 사용자가 존재하지 않습니다." });
    }

    res.locals.user = user; // 전달받은 사용자의 정보를 전부 저장
    next(); // middleware 종료
  } catch (error) {
    return res.status(401).json({ errorMessage: "비정상적인 접근입니다." });
  }
};

 

 

app.js 실행

$ npm start

 

Thunder Client 사용

 

회원 가입 API 확인

 

 

로그인 API 확인

쿠키 발급

 

사용자 정보 조회 API 확인

 

 

게시글 생성 API

 

 

게시글 목록 조회 API

 

 

게시글 상세 조회 API