반응형
채팅 앱 만들어보자!
Node.js
Vite + React
MongoDB
react 설치
터미널 frontend 폴더에서 설치하기
- npm create vite@latest .
- React / JavaScript 선택
패키지 설치
- 루트 폴더에서 npm init -y 실행
- npm i express dotenv cookie-parser bcryptjs mongoose socket.io jsonwebtoken
- 전부 설치 : express, dotenv, cookie-parser, bcryptjs, mongoose, socket.io, jsonwebtoken
nodemon 설치
npm install nodemon --save-dev -g
Nodemon은 Node.js 애플리케이션 개발을 보다 편리하게 해주는 도구로, 소스 코드가 변경될 때마다 자동으로 서버를 재시작해주는 역할을 합니다. 이를 통해 개발자는 서버를 수동으로 재시작할 필요 없이 코드 변경 사항을 즉시 반영할 수 있어 개발 효율성이 크게 향상됩니다.
몽고DB 연결
connectToMongoDB.js
위치 : /backend/db/---.js
import mongoose from "mongoose";
const connectToMongoDB = async () => {
try {
const uri = process.env.MONGO_URI;
if (!uri) {
throw new Error('MONGO_URI is not defined in .env file');
}
await mongoose.connect(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB connected successfully');
} catch (error) {
console.error('Error connecting to MongoDB', error);
process.exit(1); // Exit process with failure
}
};
export default connectToMongoDB;
.env
PORT = 5000
MONGO_URI=mongodb+srv://아이디:비밀번호@cluster0.-----.mongodb.net/chat-app?retryWrites=true&w=majority
기본 기능
auth.routes.js
import express from "express";
import { login, logout, signup } from "../controllers/auth.controller.js";
const router = express.Router();
router.post("/signup", signup);
router.post("/login", login);
router.post("/logout", logout);
export default router;
user.model.js
import mongoose from "mongoose";
const userSchema = new mongoose.Schema({
fullname: {
type: String,
required: true
},
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true,
minlength:6
},
confirmPassword: {
type: String,
required: true
},
gender: {
type: String,
required: true,
enum: ["male", "female"]
},
profilePic:{
type: String,
default: "",
},
});
const User = mongoose.model("User", userSchema);
export default User;
아바타 가져오기
https://avatar-placeholder.iran.liara.run/
auth.controller.js
import User from '../models/user.model.js';
import bcrypt from 'bcryptjs';
export const signup = async (req, res) => {
try {
const { fullname, username, password, confirmPassword, gender } = req.body;
if(password !== confirmPassword){
return res.status(400).json({ message: "Passwords do not match" });
}
const user = await User.findOne({ username });
if(user){
return res.status(400).json({ message: "User already exists" });
}
// hash password HERE
// https:avater-placeholder.iran.liara.run
const boyProfilePic = `https://avatar.iran.liara.run/public/boy?username=${username}`
const girlProfilePic = `htps://avatar.iran.liara.run/public/girl?username=${username}`
const newUser = new User({
fullname,
username,
password,
confirmPassword,
gender,
profilePic: gender === "male" ? boyProfilePic : girlProfilePic
});
await newUser.save();
res.status(201).json({
_id: newUser._id,
fullname: newUser.fullname,
username: newUser.username,
profilePic: newUser.profilePic
})
} catch(error){
console.log("Error in signup", error);
res.status(500).json({ message: error.message });
}
};
postman(thunder client)으로 post 보내서 확인하기 -> 유저가 1명 만들어짐.
mongoDB 확인
아까 post로 보낸 정보가 db에 저장되어있다.
비밀번호가 바로 보이니까, 이거를 암호화해야함 (bcryptjs)
bcrypt
컨트롤러에서 bcryptjs 코드 추가
import 후 salt 선언 후, password: hashedPassword로 설정.
import bcrypt from 'bcryptjs';
.
.
.
// hash password HERE
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
.
.
const newUser = new User({
fullname,
username,
password: hashedPassword,
confirmPassword,
gender,
profilePic: gender === "male" ? boyProfilePic : girlProfilePic
});
다시 post로 유저 정보 보내면 비번이 암호화가 되어서 저장된다.
VScode 에서 git bash 사용하기
좌측 3번째 메뉴에서 Windows용 GIT 다운로드 한 후, vscode 재시작하면 됨.
bash 시크릿 토큰 생성
JWT
generateToken.js
import jwt from "jsonwebtoken";
const generateTokenAndSetCookie = (userId, res) => {
const token = jwt.sign({ userId }, process.env.JWT_SECRET, {
expiresIn: "15d",
})
res.cookie("jwt", token, {
maxAge: 15 * 24 * 60 * 60 * 1000,
httpOnly: true, // prevent XSS attacks cross-site scripting attacks
sameSite:"strict", // prevent CSRF attacks
});
};
export default generateTokenAndSetCookie;
auth.controller.js
generateToken.js를 import 해주고, newUser에서 jwt 토큰 설정해줌.
import generateTokenAndSetCookie from '../utils/generateToken.js';
if(newUser){
// generate JWT token
generateTokenAndSetCookie(newUser._id, res);
await newUser.save();
}
새로운 user 생성(send)하면, cookies에 jwt value가 생성됨.
로그아웃
auth.controller.js
export const logout = (req, res) => {
try {
res.cookie("jwt","",{maxAge:0})
res.status(200).json({message:"Logged out successfully"})
} catch (error) {
console.log("Error in login", error);
res.status(500).json({ error: error.message });
}
생성, 수정 날짜 추가
// createAt, updateAt
}, {timestamps: true});
메세지 보내기
- 아까 생성한 유저로 로그인 send 보내고 cookie 값을 복사함. (쿠키값이 필요한 이유는 아래에 기입함.)
- 메세지 보낼 때, headers에 cookie를 추가해서 보내야 한다.
- 성공적으로 메세지가 전송되었다!
- 몽고DB에 저장됨
message.controller.js
- 필수 : DB에 저장되게 하려면 save() 메소드를 작성해 주어야 함.
// 순차적으로 처리됨
// await newMessage.save();
// await conversation.save();
// 동시에 처리됨.
await Promise.all([newMessage.save(), conversation.save()]);
export const getMessages = async (req, res) => {
try {
const {id:userToChatId} = req.params;
const senderId = req.user._id;
const conversation = await Conversation.findOne({
participants: {
$all: [senderId, userToChatId]
},
}).populate("messages"); // messages array에 push한다.
if(!conversation) return res.status(200).json([]);
const messages = conversation.messages;
res.status(200).json(messages);
} catch (error) {
console.log("Error in login", error);
res.status(500).json({ error: error.message });
}
}
populate는 Mongoose에서 참조된 문서들을 자동으로 조회하여 필드에 데이터를 채워주는 기능입니다. populate를 사용하면, 관련된 데이터의 ID만 저장된 필드를 실제 데이터로 치환해줍니다. 이를 통해 관련된 데이터에 쉽게 접근할 수 있습니다. 관련된 데이터를 자동으로 가져와서 개발자가 쉽게 사용할 수 있도록 해줍니다.
☠ 안 됐던 이유 ☠
현재 보호된 라우트(protectRoute 미들웨어)가 적용되어 있어 JWT 토큰이 없는 경우 401 에러를 반환합니다. 이 경우, 클라이언트 요청에서 JWT 토큰이 필요합니다.
=> send할 때 로그인 된 유저의 jwt 토큰까지 같이 보내야 했던 것.
User
user.routes.js
import express from "express";
import protectRoute from "../middleware/protectRoute.js";
import { getUsersForSidebar } from "../controllers/user.controller.js";
const router = express.Router();
router.get("/", protectRoute, getUsersForSidebar)
export default router;
user.controller.js
import User from "../models/user.model.js";
export const getUsersForSidebar = async (req, res) => {
try {
const loggedInUserId = req.user._id;
const filteredUsers = await User.find({ _id: { $ne: loggedInUserId } });
res.status(200).json(filteredUsers);
} catch (error) {
console.log("Error in getUsersForSidebar: ", error.message);
res.status(500).json({ error: "Internal Server Error" });
}
}
server.js
import userRoutes from "./routes/user.routes.js";
app.use("/api/users", userRoutes)
반응형
'프로젝트' 카테고리의 다른 글
[배포] Render.com 배포하기 (Node.js, React, mongoDB) (0) | 2024.06.07 |
---|---|
[Spring boot] 취업정보 크롤링 정제 작업 (0) | 2024.05.31 |
[Spring] 글쓰기 제한하기 관리자만 글 등록 가능하게 하기 (0) | 2024.05.27 |
[Spring boot, Vue3] 사이트 크롤링 하기 (0) | 2024.05.27 |
0525 프로젝트 트러블슈팅 게시판 정렬 및 출력 (0) | 2024.05.27 |
댓글