설치 명령어
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
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;
마지막으로 깃허브에 등록하고, 혼자서 작성하는 연습예정
'코딩공부 > Node.js' 카테고리의 다른 글
시퀄라이즈 개념 공부 (0) | 2023.02.17 |
---|---|
[노마드코더] 줌 클론코딩 (웹소켓,소켓I.O) (0) | 2023.01.11 |
노드js 웹소켓 강의 (0) | 2023.01.09 |
🔥노드js 개인 첫 프로젝트🔥 - 로그인,게시판,코멘트,라이크까지! (0) | 2022.12.29 |
Node.js 심화반 - 2주차 선행지식 (0) | 2022.12.21 |