들어가며
강의와 책, 유튜브 등으로만 이론 공부를 했는데
직접 코딩할 생각을 하니 막막해서 잠시 이론 공부는 멈추고
이전에 과제로 내주었던 것을 혼자 실행해보기로 했다.
[1] DB설계 (ERD 작성)
우선 만들고자 하는 서비스를 ERD를 작성 한다.
https://drawsql.app/ 서비스를 이용하여 직관적으로 볼 수 있게 정리한다.

[2] 목표 설정
Node.js 숙련주차 개인과제 (참고 : https://teamsparta.notion.site/Node-js-b177edcc731147b38ba49594849627e9)
- 회원 관련 기능을 만들 수 있어요.
- 게시글에 댓글을 작성하도록 만들 수 있어요.
- swagger를 이용하여 API 스펙을 관리할 수 있어요.
- 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
노드js 후발대 자료
https://teamsparta.notion.site/_1-12-26-30-1cd4866ab2e34ea194a59f09da2e166e
'코딩공부 > Node.js' 카테고리의 다른 글
[노마드코더] 줌 클론코딩 (웹소켓,소켓I.O) (0) | 2023.01.11 |
---|---|
노드js 웹소켓 강의 (0) | 2023.01.09 |
Node.js 심화반 - 2주차 선행지식 (0) | 2022.12.21 |
노드js 입문 1주차 (0) | 2022.12.20 |
req.query , req.params, req.body, res.json (0) | 2022.12.20 |