728x90
반응형

Create

이 메서드는 저장되지 않은 인스턴스를 빌드 하고 인스턴스를 저장합니다.

const jane = await User.create({ firstName: "Jane", lastName: "Doe" });
console.log("Jane's auto-generated ID:", jane.id);

 

findByPk

이 메서드는 제공된 기본 키를 사용하여 테이블에서 단일 항목만 가져옵니다.

 

findOne

이 메서드는 찾은 첫 번째 항목(제공된 경우 선택적 쿼리 옵션을 충족함)을 가져옵니다.

 

findAll

데이터베이스에서 전체 테이블을 읽을 수 있습니다

// Find all users
const users = await User.findAll();

SELECT * FROM ...

attribues 일부만 선택하는 방법

Model.findAll({
  attributes: ['foo', 'bar']
});

SELECT foo, bar FROM ...

where 필터링 하는 방법 (여러개도 가능)

Post.findAll({
  where: {
    authorId: 12,
    status: 'active'
  }
});
// SELECT * FROM post WHERE authorId = 12 AND status = 'active';

OP + OP.or

const { Op } = require("sequelize");
Post.findAll({
  where: {
    [Op.and]: [
      { authorId: 12 },
      { status: 'active' }
    ]
  }
});
// SELECT * FROM post WHERE authorId = 12 AND status = 'active';

 

 

 

 

 

 

 

출처 :  https://sequelize.org/docs/v6/core-concepts/model-querying-basics/

 

Model Querying - Basics | Sequelize

Sequelize provides various methods to assist querying your database for data.

sequelize.org

 

728x90
반응형
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
반응형
728x90
반응형

준비단계

초기 폴더구조

초기설정

위의 폴더대로 생성

npm init -y

npm i express

npm i nodemon -D

npm i @babel/core @babel/cli @babel/node @babel/preset-env -D 

npm i pug

babel.config.json 파일 생성

{
  "presets": ["@babel/preset-env"]
}

nodomon.json 파일생성

{
  "exec": "babel-node src/server.js"
}

src폴더 생성(server.js 파일 포함)

package.json 스크립트 추가

노데몬 실행명령어 npm run dev

server.js

import express from "express";
const app = express();
app.listen(3000);

 

 

 

 

 

728x90
반응형
728x90
반응형

https://teamsparta.notion.site/230109-Websocket-81b16827afce4b9fb7cb4ec71175e823

 

 

웹소켓 커넥션을 만들려면 new WebSocket을 호출하면 된다.

let socket = new WebSocket("ws://localhost");

소켓이 정상적으로 생성되면 네 개의 이벤트를 사용할 수 있습니다.

open - 커넥션이 이루어 졌을 때 이벤트 발생

message - 데이터를 수신했을 때 이벤트 발생

error - 에러가 생겼을 때 이벤트 발생

close - 커넥션이 종료되었을 때 발생

 

클라이언트 데모 소스 살펴보기

let socket = new WebSocket(url);

//send를 호출할때 body에는 문자열이나 이진 데이터만 들어갈 수 있다.
socket.end(body)

//code 커넥션을 닫을때 사용하는 특수코드, reason 커넥션을 닫는 이유
socket.close([code], [reason]);

//닫기를 요청한 주체
socket.close(1000, "완료");

//다른 주체
socket.onclose = event => {
	// envet.code === 1000
    // envet.reason === "작업완료"
 };

 

메시지 전송 소스

송수신 메소드는 다음과 같다

 

// 접속된 모든 클라이언트에게 메시지를 전송한다

io.emit('event_name', msg)'

 

// 메시지를 전송한 클라이언트에게만 메시지를 전송한다

socket.emit('event_name',msg);

 

// 메시지를 전송한 클라이언트를 제외한 모든 클라이언트에게 메시지를 전송한다

socket.broadcast.emit('event_name', msg);

 

// 특정 클라이언트에게만 메시지를 전송한다

io.to(id).emit('event_name', data);

728x90
반응형
728x90
반응형

들어가며

강의와 책, 유튜브 등으로만 이론 공부를 했는데

직접 코딩할 생각을 하니 막막해서 잠시 이론 공부는 멈추고

이전에 과제로 내주었던 것을 혼자 실행해보기로 했다.

 

[1] DB설계 (ERD 작성)

우선 만들고자 하는 서비스를 ERD를 작성 한다.

https://drawsql.app/ 서비스를 이용하여 직관적으로 볼 수 있게 정리한다.

 

[2] 목표 설정

Node.js 숙련주차 개인과제 (참고 : https://teamsparta.notion.site/Node-js-b177edcc731147b38ba49594849627e9)

  1. 회원 관련 기능을 만들 수 있어요.
  2. 게시글에 댓글을 작성하도록 만들 수 있어요.
  3. swagger를 이용하여 API 스펙을 관리할 수 있어요.
  4. ERD를 이용하여 현재 Database의 관계현황을 파악할 수 있어요

 

[3] 프로젝트 만들기

비주얼 스튜디오로 진행하였습니다.

app.js 파일을 만들어 준후 진행시 필요한 라이브러리를 설치한다.

더보기

1. express - 서버 프레임 워크
2. cookie-parser - 쿠키를 쉽게 세팅
3. jsonwebtoken - jwt 이용하여 인증
4. nodemon - 서버를 자동으로 켜줌(개발용)
5. sequelize - ORM db, table 을 서버에서 쉽게 생성 및 여러 쿼리 함수사용가능
6. sequelize-cli - 시퀄라이즈를 cli를 통해서 실행(개발용)
7. mysql2 - node용 mysql client
8. dotenv - 환경변수 .env파일을 사용하게 해줌

 

nodemon을 사용하기 위해서는 

pacakage.json 파일 작성 - scripts → start(nodemon) → 실행

 

"scripts": { "start": "nodemon app.js" }   <-- 패키지.json에서 변경해야 할 부분

npm init -y
npm i express cookie-parser jsonwebtoken sequelize mysql2 dotenv
npm i nodemon sequelize-cli -D

기본 app.js는 https://expressjs.com/ko/ 에서 [시작하기] - [Hello world]에서 가져온다.

const express = require('express')
const app = express()
const port = 8080

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

 

[4] 폴더 구성하기

위와 같은 폴더 구성을 만들기 위해 우선 시퀄라이즈를 설치한다.

설치하기 전에 config파일을 통해 데이터베이스를 접근해야 하기에

이름과 비밀번호, 데이터베이스 이름, 주소를 설정해준다.

// 시퀄라이즈 사용준비
npx sequelize init

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

//User모델 생성 명령어
npx sequelize model:generate --name User --attributes nickname:string,password:string
npx sequelize model:generate --name Post --attributes user_id:integer,title:string,content:string,like_cnt:integer
npx sequelize model:generate --name Comment --attributes user_id:integer,post_id:integer,content:string
npx sequelize model:generate --name Likes --attributes user_id:integer,post_id:integer

//테이블 생성하기
npx sequelize db:migrate

//잘못 합병했다면 이전으로 돌리자!
npx sequelize db:migrate:undo

 

 

routers 폴더는 따로 만들어서 설정해주어야 한다.

 

 

[5] 회원가입 만들기

회원가입을 담당할 routers/register.js 을 생성한다.

 

routers/register.js

const express = require('express');
const router = express.Router();
const {Op} = require('sequelize');
const {User} = require('../models');

router.post('/register', async (req, res) => {
    const {nickname, password, confirmpw} = req.body;

    const nameReg = /^[a-zA-Z0-9]{3,}$/

    try {
        if (!nameReg.test(nickname)) {
            return res.status(412).send({"errorMessage": "ID의 형식이 일치하지 않습니다."})
        }

        if (password.length < 4) {
            return res.status(412).send({"errorMessage": "패스워드의 형식이 일치하지 않습니다."})
        }

        if (password === nickname) {
            return res.status(412).send({"errorMessage": "패스워드와 닉네임이 일치합니다."})
        }

        if (password !== confirmpw) {
            return res.status(412).send({"errorMessage": "패스워드가 일치하지 않습니다."})
        }


        const existUser = await User.findAll({
            where: {nickname: nickname}
        })

        if (existUser.length){
            return res.status(412).send({"errorMessage": "중복된 닉네임입니다."})
        }

        await User.create({
            nickname, password
        })


        res.status(201).send({message: "회원가입 성공!"})

    } catch(err) {
        res.status(400).send({"errorMessage": "요청한 데이터 형식이 올바르지 않습니다."})
    }


});

module.exports = router;

app.js

const express = require('express')
const app = express()
const port = 8080

const registerRouter = require("./routers/register");

app.use(express.json());

app.use("/api", registerRouter);

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

 

app.js 에서는 register.js 에서 정보를 가져와 app.use로 실행하여 서버를 가동시킨다.

서버를 실행시킨후 썬더클라이언트로 회원가입을 한다.

 

json 형식이 큰 따움표(" ")로 감싼다는 사실을 까먹고 키값을 그냥 입력했다가 에러나서 동료의 도움을 받았다.

 

[6] 미들웨어 만들기

 

jwt를 사용하고, 모델에 user 폴더에 있는 클래스 User를 가져옵니다.

토큰 값을 받아서 토큰이 유효한지, 가지고 있는지에 따라 결과값을 반환하는 코드를 작성합니다.

다른 코드에서 사용시, 라우터쪽에 authMiddleWare 를 입력하여 사용합니다.

 

middlewares/auth-middleware.js

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

module.exports = (req, res, next) => {
  const token = req.headers.cookie.split("=")[1];
  console.log(req.headers);

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

  try {
    const { userId } = jwt.verify(token, SECRET_KEY);
    User.findByPk(userId).then((user) => {
      res.locals.user = user;
      return next();
    });
  } catch (err) {
    res.status(401).send({
      errorMessage: "로그인 후 이용 가능한 기능입니다.",
    });
  }
};

[7] 로그인 기능 만들기

routers/login.js

const express = require("express");
const cookieParser = require("cookie-parser");
const router = express.Router();
const { User } = require("../models");
const jwt = require("jsonwebtoken");
const SECRET_KEY = "1234";

const app = express();
app.use(cookieParser());

router.post("/login", async (req, res) => {
  const { nickname, password } = req.body;

  try {
    const user = await User.findOne({
      where: { nickname },
    });

    if (!user || user.password !== password) {
      return res
        .status(412)
        .send({ errorMessage: "닉네임 또는 패스워드를 확인해주세요." });
    }

    const token = jwt.sign(
      { nickname: nickname, userId: user.userId },
      SECRET_KEY,
      {
        expiresIn: "1h",
      }
    );

    res.cookie("token", token);

    return res.json({ token: token });
  } catch (err) {
    return res.status(400).send({ errorMessage: "로그인에 실패하였습니다." });
  }
});

module.exports = router;

 

app.js에서는 아래 코드를 추가해줘야 작동합니다.

const registerRouter = require("./routers/register");
const loginRouter = require("./routers/login");

app.use(express.json());

app.use("/api", [registerRouter, loginRouter]);

로그인 성공하면 토큰값을 반환하도록 설정된 모습

 

[8] 게시판 만들기

 

routers/posts.js

const express = require("express");
const router = express.Router();
const cookieParser = require("cookie-parser");

const { Post, Comment, Like } = require("../models");
const { Op } = require("sequelize");
const authMiddleWare = require("../middlewares/auth-middleware");

const app = express();
app.use(cookieParser());

// 전체 게시글 조회
router.get("/posts", async (req, res) => {
  try {
    const posts = await Post.findAll({ order: [["createdAt", "desc"]] });
    // 오류 예제
    // try catch 있을때/없을때
    // const posts = await NonexistentCollection.find({});

    res.send(posts);
  } catch (error) {
    console.error(error);
    res.status(500).send({ message: error.message });
  }
});

// 특정 게시글 조회
router.get("/posts/:postId", async (req, res) => {
  try {
    const { postId } = req.params;
    // 오류테스트
    // const postId = "63a11f34dee1fb38182cdb93234234";
    const post = await Post.findByPk(postId);

    console.log(post);
    res.send(post);
  } catch (error) {
    console.error(error);

    res.status(500).send({ message: error.message });
  }
});

// 게시글 작성
router.post("/posts", authMiddleWare, async (req, res) => {
  const { title, content } = req.body;
  const user_id = res.locals.user.userId;
  try {
    const posts = await Post.create({
      title,
      content,
      user_id,
    });

    // res.json({posts});
    // res.json(posts);
    res.send(posts);
  } catch (error) {
    console.error(error);
    res.status(500).send({ message: error.message });
  }
});

// 특정 게시글 수정
// 비밀번호 비교 후 비밀번호 일치할 때만 수정
router.put("/posts/:postId", authMiddleWare, async (req, res) => {
  // postId 값 다르게 주고 try catch 빼고 실행
  try {
    const { postId } = req.params;
    const { title, content } = req.body;

    // 조회 실패
    const post = await Post.findByPk(postId);
    if (post === null) {
      return res.status(400).send({ message: "🛑 게시글이 없습니다." });
    }

    const result = await Post.update(
      { title: title, content: content },
      { where: { postId } }
    );

    console.log("result", result);

    res.send({ message: "success" });
  } catch (error) {
    console.error(error);

    res.status(500).send({ message: error.message });
  }
});

// 특정 게시글 삭제
router.delete("/posts/:postId", authMiddleWare, async (req, res) => {
  try {
    const { postId } = req.params;

    // 조기 리턴
    const _post = await Post.findByPk(postId);
    if (_post === null) {
      return res.status(400).send({ message: "🛑 게시글이 없습니다." });
    }

    // 게시글 삭제
    await Post.destroy({
      where: { postId },
    });
    // 게시글에 속한 댓글들 삭제
    await Comment.destroy({
      where: { post_id: postId },
    });

    // console.log(comments);

    res.send("삭제완료!");
  } catch (error) {
    console.error(error);

    res.status(500).send({ message: error.message });
  }
});

module.exports = router;

 

[9] 댓글 만들기

routers/comments.js

const express = require("express");
const router = express.Router();

const { Post, Comment, Like } = require("../models");
const { Op } = require("sequelize");
const authMiddleWare = require("../middlewares/auth-middleware");

// 특정 게시글에 속한 댓글 전체 조회
router.get("/posts/:postId/comments", async (req, res) => {
  try {
    const { postId } = req.params;

    // 조기 리턴
    const post = await Post.findByPk(postId);
    if (post === null) {
      return res.status(400).send({ message: "🛑 게시글이 없습니다." });
    }
    const comment = await Comment.findAll({ order: [["createdAt", "desc"]] });

    res.send(comment);
  } catch (error) {
    console.error(error);

    res.status(500).send({ message: error.message });
  }
});

// 특정 게시글에 속한 댓글 작성
router.post("/posts/:postId/comments", authMiddleWare, async (req, res) => {
  try {
    const post_id = req.params.postId;
    const { content } = req.body;
    const user_id = res.locals.user.userId;

    // 조기 리턴
    const post = await Post.findByPk(post_id);
    if (post === null) {
      return res.status(400).send({ message: "🛑 게시글이 없습니다." });
    }

    if (content === "") {
      return res.status(400).send("🛑 댓글 내용을 입력해주세요");
    }

    const comment = await Comment.create({
      content,
      post_id,
      user_id,
    });

    res.send(comment);
  } catch (error) {
    console.error(error);
    res.status(500).send(error.message);
  }
});

// 특정 게시글에 속한 특정 댓글 수정
router.put(
  "/posts/:postId/comments/:commentId",
  authMiddleWare,
  async (req, res) => {
    try {
      const { postId, commentId } = req.params;
      const { content } = req.body;

      // 조기 리턴
      const post = await Post.findByPk(postId);
      if (post === null) {
        return res.status(400).send({ message: "🛑 게시글이 없습니다." });
      }

      const _comment = await Comment.findByPk(commentId);
      if (_comment === null) {
        return res.status(400).send({ message: "🛑 댓글이 없습니다." });
      }

      if (content === "") {
        return res.status(400).send("🛑 댓글 내용을 입력해주세요");
      }

      await Comment.update(
        {
          content: content,
        },
        { where: { commentId } }
      );

      res.send({ message: "success" });
    } catch (error) {
      console.error(error);

      res.status(500).send(error.message);
    }
  }
);

// 특정 게시글에 속한 특정 댓글 삭제
router.delete(
  "/posts/:postId/comments/:commentId",
  authMiddleWare,
  async (req, res) => {
    try {
      const { postId, commentId } = req.params;

      // 조기 리턴
      const post = await Post.findByPk(postId);
      if (post === null) {
        return res.status(400).send({ message: "🛑 게시글이 없습니다." });
      }

      const _comment = await Comment.findByPk(commentId);
      if (_comment === null) {
        return res.status(400).send({ message: "🛑 댓글이 없습니다." });
      }

      await Comment.destroy({
        where: { commentId },
      });

      res.send("삭제완료!");
    } catch (error) {
      console.error(error);

      res.status(500).send(error.message);
    }
  }
);

module.exports = router;

 

 

 

[10] 좋아요 만들기 & 마무리 app.js

 

routers/likes.js

const express = require("express");
const router = express.Router();
const { Post, likes, User } = require("../models");
const authMiddleWare = require("../middlewares/auth-middleware");

router.get("/likes/posts", authMiddleWare, async (req, res) => {
  const user_id = res.locals.user.userId;
  console.log(res.locals.user);

  const data = await likes.findAll({
    where: { user_id: user_id },
    raw: true,
    attributes: ["Post.user_id", "Post.title", "Post.content", "Post.like_cnt"],
    include: [
      {
        model: Post,
        attributes: [],
      },
    ],
    order: [[Post, "like_cnt", "desc"]],
  });

  console.log("********", data);

  res.status(200).json({ data });
});

router.put("/posts/:postId/like", authMiddleWare, async (req, res) => {
  const user_id = res.locals.user.userId;
  const { postId } = req.params;

  const existlike = await likes.findOne({
    where: { user_id, post_id: postId },
  });

  try {
    if (!existlike) {
      await likes.create({
        user_id: user_id,
        post_id: postId,
      });

      await Post.increment({ like_cnt: 1 }, { where: { postId } });
      return res.status(200).send("좋아요^^");
    } else {
      likes.destroy({
        where: { post_id: postId },
      });

      await Post.decrement({ like_cnt: 1 }, { where: { postId } });
      return res.status(200).send("안 좋아요ㅠㅠ");
    }
  } catch (error) {
    res.status(400).send({ errorMessage: "게시글 좋아요에 실패하였습니다." });
  }
});

module.exports = router;

app.js

const express = require("express");
const app = express();

const loginRouter = require("./routers/login");
const registerRouter = require("./routers/register");
const postRouter = require("./routers/posts");
const commentRouter = require("./routers/comments");
const likesRouter = require("./routers/likes");

app.use(express.json());

app.use("/api", [
  registerRouter,
  loginRouter,
  postRouter,
  commentRouter,
  likesRouter,
]);

app.get("/", (req, res) => {
  res.send("Welcome to my page");
});

app.listen(8080, () => {
  console.log("서버 접속");
});

 

[참고자료]

노드js 숙련학습자료 Sequelize

https://teamsparta.notion.site/2-4-MySQL-Sequelize-167995db19ec4f5eb1d041e95f461281#8f4304ba9ec74c878dcc0832421f8b4b

 

노드js 후발대 자료

https://teamsparta.notion.site/_1-12-26-30-1cd4866ab2e34ea194a59f09da2e166e

 

728x90
반응형
728x90
반응형

1) 쿠키 & 세션

 

쿠키 : 브라우저가 서버로부터 응답으로 Set-Cookie 헤더를 받은 경우

해당 데이터를 저장한 뒤 모든 요청에 포함하여 보냅니다.

 

세션 : 쿠키를 기반으로 구성된 기술입니다. 단, 클라이언트가 마음대로 데이터를 확인 할 수 있던 쿠키와는 다르게 세션은 데이터를 서버에만 저장하기 때문에 보안이 좋으나, 반대로 사용자가 많은 경우 서버에 저장해야 할 데이터가 많아져서 서버 컴퓨터가 감당하지 못하는 문제가 생기기 쉽습니다.

 

2) express - 미들웨어의 개념

express에서의 미들웨어는 어떠한 요청에 대해서 공통적으로 처리하는 로직을 모아둔 코드 덩어리입니다.

  • express.static(path): path에 입력한 경로에 있는 파일을 그대로 서빙해주는 기능을 수행하는 미들웨어입니다.
                                     router의 기능을 일부 가지고 있는것이죠!
  • express.json: HTTP Request에서 Body에 담긴 JSON 형식의 데이터를
                           express 서버에서 사용할 수 있게 해주는 미들웨어예요!
  • express.urlencoded: 이것도 HTTP Request에서 Body에 담긴 Form(URL Encoded) 형식의 데이터를
                                      express 서버에서 사용할 수 있게 해주는 미들웨어입니다

3) ES6 - 구조 분해 할당(Destructuring assignment) 문법

const { email, password } = req.body;

 

4) mongoose 사용법

https://mongoosejs.com/docs/guide.html 공식문서 참조

 

5) REST API

REST 아키텍쳐를 따라 구현된 API를 REST API라고 부릅니다.

간단히 말하면 원래 있던 방법보다 더 쉽고 사람이 읽기 편한 방식으로 원칙을 세워놨고,

개발자들의 생산성과 상호작용을 증진시키는것에 목적이 있습니다.

 

 

 

 

728x90
반응형
728x90
반응형

Validation이란 무엇인가?

Validation은 말 그대로 어떤것을 검증한다고 보면 됩니다.

 

검증을 위한 라이브러리 추천 joi (https://joi.dev/api/)

//예제코드
const Joi = require('joi');

const schema = Joi.object({
    username: Joi.string()
        .alphanum()
        .min(3)
        .max(30)
        .required(),

    password: Joi.string()
        .pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')),

    repeat_password: Joi.ref('password'),

    access_token: [
        Joi.string(),
        Joi.number()
    ],

    birth_year: Joi.number()
        .integer()
        .min(1900)
        .max(2013),

    email: Joi.string()
        .email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } })
})
    .with('username', 'birth_year')
    .xor('password', 'access_token')
    .with('password', 'repeat_password');


schema.validate({ username: 'abc', birth_year: 1994 });
// -> { value: { username: 'abc', birth_year: 1994 } }

schema.validate({});
// -> { value: {}, error: '"username" is required' }

// Also -

try {
    const value = await schema.validateAsync({ username: 'abc', birth_year: 1994 });
}
catch (err) { }
728x90
반응형
728x90
반응형
const express = require('express');
const app = express();
const port = 3000p
const goodsRouter = require('./routes/goods.js');

app.use(express.json()); //바디파서를 전역미들웨어로 사용할꺼다!

app.post("/", (req,res) => {
	console.log(req.body); // {"key":"value"}
    const obj = {
    	"이름": "표정훈",
        "나이": "33"
    }
	res.json(obj);
});

app.get("/", (req,res) => {
	console.log(req.query); //key와 value 값을 받겠다 [ ?Key=Value ]
	res.json();
});

app.get("/:id", (req,res) => {
	console.log(req.params);  // 기본주소 / 뒤에 붙은걸 받겠다!
    res.send(":id URI 정상반환");
})
728x90
반응형

+ Recent posts