728x90
반응형

설치 명령어

 

npm init -y
npm i -g sequelize-cli
npm i express mysql2 sequelize joi jsonwebtoken bcrypt

npx sequelize init (config파일생성됨) -> config에서 development 정보 수정

npx sequelize db:create (데이터베이스 생성)

npx sequelize model:generate --name User --attributes nickname:string,password:string,confirm:string(모델생성)

npx sequelize db:migrate (테이블 생성)

4개는 한세트다. 진행하다보면 1개씩 까먹어서 에러발생한 경우가 많았다!ㅠㅠ

 

app.js파일 만들기

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

const app = express();

app.listen(3000, async () => {
  console.log("3000번 서버 가동");
  await sequelize.authenticate();
  console.log("db authenticate");
});

 

회원가입

1. User 모델 만들기

   a. npx sequelize model:generate --name User --attributes nickname:string,password:string,confirm:string

   b. (모두 수정했으면) npx sequelize db:migrate

 

models 폴더에 있는 users.js 파일 수정

 

User.init(
    {
      nickname: {
        type: DataTypes.STRING,
        allowNull: false,
      },
      password: {
        type: DataTypes.STRING,
        allowNull: false,
      },
      confirm: {
        type: DataTypes.STRING,
      },
    },
    {
      sequelize,
      tableName: "users",   //이걸 꼭 추가해야한다.
      modelName: "User",
    }
  );

 

migrations 테이블이름을 Users에서 users로 모두 변경

"use strict";
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable("users", {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER,
      },
      nickname: {
        type: Sequelize.STRING,
        allowNull: false,
      },
      password: {
        type: Sequelize.STRING,
        allowNull: false,
      },
      confirm: {
        type: Sequelize.STRING,
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE,
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE,
      },
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable("users");
  },
};

 

시퀄라이즈 문법이 기억안날땐, npx sequelize 치면 명령어가 나온다

그중에서 다시 되돌릴땐 npx sequelize db:migrate:undo

 

2. users 테이블 생성하기

   a. npx sequelize db:migrate

DBeaver 생성확인

3. Route와 로직 만들기

routes 폴더 만들고 auth.js 만들어서 로직 정리

validation 폴더 만들고, index.js 만들어서 Joi사용

const Joi = require("joi");

const signupValidation = Joi.object({
  nickname: Joi.string().alphanum().not("").required(),
  password: Joi.string().min(3).not("").required(),
  confirm: Joi.equal(Joi.ref("password")).required().messages({
    "any.only": "비밀번호가 일치하지 않습니다.",
  }),
});


module.exports = {
  signupValidation,
};

4. bcrypt로 비밀번호 암호화하기

auth.js

const express = require("express");

const router = express.Router();
const { signupValidation } = require("../validations");
const { User } = require("../models");
const bcrypt = require("bcrypt");

router.get("/users", async (req, res) => {
  try {
    const users = await User.findAll({
      attributes: { exclude: ["password"] },
    });
    res.json(users);
  } catch (err) {}
});

router.post("/signup", async (req, res) => {
  try {
    const { nickname, password } = await signupValidation.validateAsync(req.body);
    const hashedPassword = await bcrypt.hash(password, 12);
    const user = await User.create({
      nickname,
      password: hashedPassword,
    });

    res.json(user);
  } catch (err) {
    if (err.isJoi) {
      return res.status(422).json({ message: err.details[0].message });
    }
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

 

게시글

모델 생성

npx sequelize model:generate --name Post --attributes title:string,content:string,userId:integer

 

models폴더에서 post.js 파일 수정

Post.init({
    title: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    content: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    userId: {
      type: DataTypes.INTEGER,
      allowNull: false,
    }
  }, {
    sequelize,
    tableName: "posts",
    modelName: 'Post',
  });

마이그레이션 post도 변경(소문자 posts 변경 및 allowNull 추가)

"use strict";
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable("posts", {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER,
      },
      title: {
        type: Sequelize.STRING,
        allowNull: false,
      },
      content: {
        type: Sequelize.STRING,
        allowNull: false,
      },
      userId: {
        type: Sequelize.INTEGER,
        allowNull: false,
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE,
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE,
      },
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable("posts");
  },
};

모두 수정후에 npx sequelize db:migrate 하기

routes 폴더에 posts.js 파일 만들고 app.js에 연결하기

 

models/post.js + user.js 연결하기

    static associate({ User }) {
      // define association here
      // UserId => userId 카멜케이스로 바꿈
      this.belongsTo(User, { foreignKey: "userId", as: "user" });
    }

    static associate({Post}) {
      // define association here
      this.hasMany(Post, {foreignKey:'userId', as: "posts"})
    }

 

validation/index.js 파일변경(게시물 생성 및 수정 검증로직 추가)

const Joi = require("joi");

const signupValidation = Joi.object({
  nickname: Joi.string().alphanum().not("").required(),
  password: Joi.string().min(3).not("").required(),
  confirm: Joi.equal(Joi.ref("password")).required().messages({
    "any.only": "비밀번호가 일치하지 않습니다.",
  }),
});

const postCreateValidation = Joi.object({
  title: Joi.string().not("").required(),
  content: Joi.string().not("").required(),
  userId: Joi.number().required(),
});

const postUpdateValidation = Joi.object({
  title: Joi.string().optional().not(""),
  content: Joi.string().optional().not(""),
  userId: Joi.forbidden(),
});

module.exports = {
  signupValidation,
  postCreateValidation,
  postUpdateValidation,
};

 

routes/posts.js

const express = require("express");
const router = express.Router();
const { Post, User } = require("../models");
const { postCreateValidation, postUpdateValidation } = require("../validations");

// 게시물 조회
router.get("/", async (req, res) => {
  try {
    const posts = await Post.findAll({
      include: [{ model: User, as: "user", attributes: ["nickname"] }],
      attributes: { exclude: ["userId"] },
    });
    res.json(posts);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// 게시물 상세 조회
router.get("/:id", async (req, res) => {
  const { id } = req.params;
  try {
    const post = await Post.findByPk(id, {
      include: [{ model: User, as: "user", attributes: ["nickname"] }],
      attributes: { exclude: ["userId"] },
    });
    res.json(post);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// 게시물 작성
router.post("/", async (req, res) => {
  try {
    const { title, content, userId } = await postCreateValidation.validateAsync(req.body);
    const post = await Post.create({
      title,
      content,
      userId,
    });
    res.json(post);
  } catch (err) {
    if (err.isJoi) {
      return res.status(422).json({ message: err.details[0].message });
    }
    res.status(500).json({ message: err.message });
  }
});

// 게시물수정
router.patch("/:id", async (req, res) => {
  const { id } = req.params;
  try {
    const fieldsToBeUpdated = await postUpdateValidation.validateAsync(req.body);
    const updatedPost = await Post.update(fieldsToBeUpdated, {
      where: { id },
    });
    res.json(updatedPost);
  } catch (err) {
    if (err.isJoi) {
      return res.status(422).json({ message: err.details[0].message });
    }
    res.status(500).json({ message: err.message });
  }
});

// 게시물삭제
router.delete("/:id", async (req, res) => {
  try {
    const { id } = req.params;
    const post = await Post.destroy({ where: { id } });
    res.json(post);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

 

 

댓글

모델 만들기

npx sequelize model:generate --name Comment --attributes content:string,userId:integer,postId:integer

 

models/comment.js에 들어가서 tableName:'comments' 추가, type: ~~~ 및 allowNull 추가(+관계형성하기)

마이그레이션에서 대문자 Comments를 소문자로 바꿔주고, allowNull 추가

완료한후 npx sequelize db:migrate 로 병합하기

라우터 폴더에서 comments.js 파일을 만들고 app.js에 적용시키기

validation에서 게시물작성 및 수정 유효검사 로직 작성후 routes/comments.js에서 로직 작성

 

routes/comments.js

const express = require("express");
const router = express.Router();
const { Comment, Post } = require("../models");
const { commentCreateValidation, commentUpdateValidation } = require("../validations/index");

// 댓글 전체 조회
router.get("/", async (req, res) => {
  try {
    const comments = await Comment.findAll();
    res.json(comments);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// 특정 댓글 조회
router.get("/:postId", async (req, res) => {
  const { postId } = req.params;

  try {
    const post = await Post.findByPk(postId);
    const postComments = await post.getComments();
    res.json(postComments);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// 댓글 작성
router.post("/:postId", async (req, res) => {
  const { postId } = req.params;
  try {
    const { content, userId } = await commentCreateValidation.validateAsync(req.body);
    const comment = await Comment.create({
      content,
      userId,
      postId,
    });
    res.json(comment);
  } catch (err) {
    if (err.isJoi) {
      return res.status(422).json({ message: err.details[0].message });
    }
    res.status(500).json({ message: err.message });
  }
});

//댓글 수정
router.patch("/:id", async (req, res) => {
  const { id } = req.params;

  try {
    const fieldsToUpdate = await commentUpdateValidation.validateAsync(req.body);
    const updatedComment = await Comment.update(fieldsToUpdate, { where: { id } });
    res.json(updatedComment);
  } catch (err) {
    if (err.isJoi) {
      return res.status(422).json({ message: err.details[0].message });
    }
    res.status(500).json({ message: err.message });
  }
});

//댓글 삭제
router.delete("/:id", async (req, res) => {
  const { id } = req.params;
  try {
    const deletedComment = await Comment.destroy({ where: { id } });
    res.json(deletedComment);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

 

로그인

auth.js (dotenv 설치, jwt사용하기)

require("dotenv").config();
const express = require("express");

const router = express.Router();
const { signupValidation } = require("../validations");
const { User } = require("../models");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");

const { JWT_SECRET_KEY } = process.env;

//회원가입
router.get("/users", async (req, res) => {
  try {
    const users = await User.findAll({
      attributes: { exclude: ["password"] },
    });
    res.json(users);
  } catch (err) {}
});

router.post("/signup", async (req, res) => {
  try {
    const { nickname, password } = await signupValidation.validateAsync(req.body);
    const hashedPassword = await bcrypt.hash(password, 12);
    const user = await User.create({
      nickname,
      password: hashedPassword,
    });

    res.json(user);
  } catch (err) {
    if (err.isJoi) {
      return res.status(422).json({ message: err.details[0].message });
    }
    res.status(500).json({ message: err.message });
  }
});

//로그인
router.post("/login", async (req, res) => {
  const { nickname, password } = req.body;
  try {
    const user = await User.findOne({ nickname });
    const isPasswordCorrect = await bcrypt.compare(password, user.password);
    if (!user || !isPasswordCorrect) {
      return res.status(400).json({ message: "이메일 또는 비밀번호가 틀렸습니다." });
    }
    res.json({ token: jwt.sign({ nickname }, JWT_SECRET_KEY) });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

 

Auth 미들웨어

middleware 폴더 생성 - auth.js 파일 생성

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

const { JWT_SECRET_KEY } = process.env;

const authMiddleware = async (req, res, next) => {
  const { authorization = "" } = req.headers;
  const [tokenType, token] = authorization.split(" "); //['Bearer', '<token>']

  const isTokenValid = token && tokenType === "Bearer";

  if (!isTokenValid) {
    return res.status(401).json({
      message: "로그인 후 이용 가능한 기능입니다.",
    });
  }

  try {
    const { nickname } = jwt.verify(token, JWT_SECRET_KEY);
    const user = await User.findOne({ where: { nickname } });
    res.locals.currentUser = user; //미들웨어를 걸쳐 전역에서 사용가능한 변수 생성
    next();
  } catch (err) {
    res.status(401).json({
      message: "로그인 후 이용 가능한 기능입니다.",
    });
  }
};

module.exports = authMiddleware;

미들웨어를 사용하고 싶다면 해당 파일에서 require를 해주고 붙여주면 된다.

나는 app.js에서 app.use로 모두 경유하게 만들면 되지 않나? 했는데

로그인 했을때만 이용가능하게 전역에서 쓴다면 문제가 발생할 수 있다.

const authMiddleware = require("../middleware/auth");

router.post("/", authMiddleware, async (req, res) =>{}

 

라이크

npx sequelize model:generate --name Like --attributes userId:integer,postId:integer

테이블네임 추가, allow추가, Likes를 likes로 변경 (동일작업)

npx sequelize db:migrate

 

이후 유저, 댓글, 게시물과의 관계를 설정

const express = require("express");
const router = express.Router();
const { Post, User, Like, Sequelize } = require("../models");
const { postCreateValidation, postUpdateValidation } = require("../validations");
const authMiddleware = require("../middleware/auth");

// 게시물 조회
router.get("/", async (req, res) => {
  try {
    const posts = await Post.findAll({
      // attributes: { exclude: ["userId"] },
      attributes: ["id", "title", "content", [Sequelize.fn("count", Sequelize.col("likes.id")), "numOfLikes"]],
      include: [
        { model: User, as: "user", attributes: ["nickname"] },
        {
          model: Like,
          as: "likes",
          attributes: [],
        },
      ],
      group: ["post.id"],
      order: [[Sequelize.literal("numOfLikes"), "DESC"]],
    });

    res.json(posts);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// 게시물 상세 조회
router.get("/:id", async (req, res) => {
  const { id } = req.params;
  try {
    const post = await Post.findByPk(id, {
      include: [{ model: User, as: "user", attributes: ["nickname"] }],
      attributes: { exclude: ["userId"] },
    });
    res.json(post);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// 게시물 작성
router.post("/", authMiddleware, async (req, res) => {
  const { currentUser } = res.locals;
  const userId = currentUser.id;
  try {
    const { title, content } = await postCreateValidation.validateAsync(req.body);
    const post = await Post.create({
      title,
      content,
      userId,
    });
    res.json(post);
  } catch (err) {
    if (err.isJoi) {
      return res.status(422).json({ message: err.details[0].message });
    }
    res.status(500).json({ message: err.message });
  }
});

// 게시물수정
router.patch("/:id", async (req, res) => {
  const { id } = req.params;
  try {
    const fieldsToBeUpdated = await postUpdateValidation.validateAsync(req.body);
    const updatedPost = await Post.update(fieldsToBeUpdated, {
      where: { id },
    });
    res.json(updatedPost);
  } catch (err) {
    if (err.isJoi) {
      return res.status(422).json({ message: err.details[0].message });
    }
    res.status(500).json({ message: err.message });
  }
});

// 게시물삭제
router.delete("/:id", async (req, res) => {
  try {
    const { id } = req.params;
    const post = await Post.destroy({ where: { id } });
    res.json(post);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// 좋아요 추가 및 취소
router.post("/:id/like", authMiddleware, async (req, res) => {
  const { id: postId } = req.params;
  const {
    currentUser: { id: userId },
  } = res.locals;
  // const userId = currentUser.id;

  try {
    const like = await Like.findOne({
      where: {
        userId,
        postId,
      },
    });

    const isLikedAlready = !!like;

    if (isLikedAlready) {
      //있다면 취소
      const deletedLike = await Like.destroy({
        where: {
          userId,
          postId,
        },
      });
      res.json(deletedLike);
    } else {
      //없으면 등록
      const postedLike = await Like.create({
        userId,
        postId,
      });
      res.json(postedLike);
    }
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

 

마지막으로 깃허브에 등록하고, 혼자서 작성하는 연습예정

 

 

 

728x90
반응형

+ Recent posts