728x90
📆 2025년 5월 23일 코딩 작업 기록
Frontend
QuillEditor.jsx
import { useRef } from 'react'
import ReactQuill from 'react-quill-new'
import 'react-quill-new/dist/quill.snow.css'
// import './QuillEditor.css'
const QuillEditor = ({ value, onChange, placeholder }) => {
const quillRef = useRef(null)
// Quill 에디터 모듈 설정
const modules = {
toolbar: {
container: [
[{ header: [1, 2, 3, false] }],
['bold', 'italic', 'underline', 'strike'],
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'image'],
['clean'],
],
},
}
return (
<div className="quill-editor-container">
<ReactQuill
ref={quillRef}
theme="snow"
value={value}
onChange={onChange}
modules={modules}
placeholder={placeholder || '내용을 입력해 주세요'}
/>
</div>
)
}
export default QuillEditor
useRef 사용 이유
변하지 않는 값을 저장하거나 DOM에 직접 접근 할때 사용 여기서는 자동 텍스트 삽입 같은 기능으로 인해 useRef 사용
Backend
index.js
// dotenv와 환경 변수 (.env 파일)
import dotenv from 'dotenv';More actions
dotenv.config();
import express from 'express';
const app = express();
const port = process.env.PORT || 4000;
import cors from 'cors';
// app.use(express.json()) -> 요청 본문의 JSON데이터를 파싱해 req-body로 접근 가능하게 해준다.
app.use(
cors({
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
credentials: true, // true여야 쿠키 전송
methods: ['GET', 'POST', 'PUT', 'DELETE'], // 허용할 HTTP 메서드
allowedHeaders: ['Content-Type', 'Authorization'], // 허용할 헤더
})
);
app.use(express.json()); // JSON 파싱 미들웨어
import cookieParser from 'cookie-parser';
app.use(cookieParser()); // 쿠키 파싱 미들웨어
import mongoose from 'mongoose';
import { userModel } from './model/user.js'; // userModel import
mongoose
.connect(process.env.MONGODB_URI, {
dbName: process.env.MONGODB_DB_NAME, // 환경변수 적용
})
.then(() => {
console.log('MongoDB 연결됨');
})
.catch((err) => {
console.log('MongoDB 연결 안됨', err);
});
import bcrypt from 'bcryptjs';
const saltRounds = parseInt(process.env.BCRYPT_SALT_ROUNDS);
import jwt from 'jsonwebtoken';
const secretKey = process.env.JWT_SECRET;
const tokenLife = process.env.JWT_EXPIRATION; // 토큰 유효시간
const cookieOptions = {
httpOnly: true,
maxAge: 1000 * 60 * 60, // 쿠키 만료 시간 (1시간)
secure: process.env.NODE_ENV === 'production', // HTTPS 환경에서만 쿠키 전송
sameSite: 'Strict', // CSRF 공격 방지
path: '/', // 쿠키의 경로
};
app.post('/register', async (req, res) => {
try {
console.log('----', req.body);
const { name, email, password } = req.body;
// 이메일 중복 확인
const existingUser = await userModel.findOne({ email });
if (existingUser) {
return res.status(409).json({ error: '이미 존재하는 이메일입니다.' });
}
// 비밀번호 유효성 검사
const hashedPassword = bcrypt.hashSync(password, saltRounds);
// 사용자 저장
const userDoc = new userModel({
username: name,
email,
password: hashedPassword,
});
const savedUser = await userDoc.save();
res.status(201).json({
success: true,
msg: '회원가입 성공',
user: {
name: savedUser.username,
email: savedUser.email,
},
});
} catch (err) {
console.error('에러', err);
res.status(500).json({ error: '서버 에러' });
}
});
app.listen(port, () => {
console.log(`서버가 http://localhost:${port} 에서 실행 중`);
});
// 로그인
app.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
const userDoc = await userModel.findOne({ email });
if (!userDoc) {
return res.status(401).json({ error: '없는 사용자 입니다.' });
}
const passOk = bcrypt.compareSync(password, userDoc.password);
if (!passOk) {
return res.status(401).json({ error: '비밀번호가 틀렸습니다.' });
} else {
const { _id, email, username } = userDoc;
const payload = { id: _id, email };
const token = jwt.sign(payload, secretKey, {
expiresIn: tokenLife,
});
// 쿠키에 토큰 저장
res.cookie('token', token, cookieOptions).json({
id: userDoc._id,
email,
username,
});
}
} catch (error) {
console.error('로그인 오류:', error);
res.status(500).json({ error: '로그인 실패' });
}
});
//회원정보 조회
app.get('/profile', (req, res) => {
const { token } = req.cookies;
// console.log("쿠키", token);
if (!token) {
return res.json({ error: '로그인 필요' }); // 오류를 JSON 형식으로 응답
}
jwt.verify(token, secretKey, (err, info) => {
if (err) {
return res.json({ error: '로그인 필요' });
}
res.json(info);
});
});
// 로그아웃
app.post('/logout', (req, res) => {
// 쿠키 옵션을 로그인과 일관되게 유지하되, maxAge만 0으로 설정
const logoutCookieOptions = {
...cookieOptions,
maxAge: 0,
};
res
.cookie('token', '', logoutCookieOptions)
.json({ message: '로그아웃 되었음' });
});
왜 app.use를 사용해야할까?
app.use()는 Express에서 미들웨어(middleware)를 등록하는 함수이다.
미들웨어란?
- 클라이언트 → 서버 요청 사이에 중간에서 실행되는 함수
- 모든 요청 또는 특정 경로의 요청을 가로채서 가공하거나 처리할 수 있음
app.use(express.json());
들어오는 요청의 본문(body)을 JSON으로 자동 파싱해줌
→ 그래서 req.body로 쉽게 접근 가능
app.use(cors({...}));
모든 요청 전에 CORS 정책을 적용
app.use(cookieParser());
클라이언트에서 보내는 쿠키를 파싱해서 req.cookies에 넣어준다.
왜 use()라고 할까?
- app.use()는 모든 경로(/)에 대해 동작하는 기본 설정을 할 때 주로 사용한다.
- 예를 들어 /api, /auth, /posts 등 여러 요청이 오더라도, 먼저 use()로 등록된 미들웨어들이 공통으로 작동한다.
cors(cross-origin resource sharing) 설정
cors란?
프론트엔드와 백엔드가 서로 다른 주소에서 실행될 때,
브라우저는 보안상 요청을 차단하려고 한다. 그걸 허용해 주는 설정이 cors이다.
app.use(
cors({
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
})
);
✅ origin
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
- 어떤 주소에서 들어오는 요청을 허용할 것인지를 지정하는 설정한다.
- 예: 프론트가 http://localhost:5173에서 실행 중이면 이 주소만 허용한다.
- 배포 환경에서는 .env에 FRONTEND_URL=https://labbitlog.vercel.app 같이 넣어서 관리한다.
✅ credentials: true
credentials: true
- 클라이언트에서 쿠키, 인증 정보, 세션 등을 포함해서 요청할 수 있도록 허용하는 옵션.
- 이걸 true로 설정하면:
- 백엔드에서 res.cookie()로 만든 쿠키를 브라우저가 받을 수 있다.
- 프론트에서 axios.defaults.withCredentials = true도 함께 설정해야 한다.
✅ methods
methods: ['GET', 'POST', 'PUT', 'DELETE']
- 서버가 어떤 HTTP 메서드의 요청을 허용할지를 명시
- 여기서는 CRUD 작업에 필요한 네 가지 메서드를 허용 중
CRUD란?
더보기
CRUD는 데이터를 다루는 네 가지 기본 작업을 의미
약자 | 의미 | HTTP | 메서드 대응 예시 |
C | Create | POST | 회원가입, 게시글 작성 |
R | Read | GET | 글 목록 보기, 유저 정보 가져오기 |
U | Update | PUT / PATCH | 글 수정, 프로필 수정 |
D | Delete | DELETE | 글 삭제, 계정 삭제 |
✅ allowedHeaders
allowedHeaders: ['Content-Type', 'Authorization']
- 클라이언트가 요청할 때 보낼 수 있는 헤더 종류를 제한한다.
- Content-Type: 주로 JSON 요청에 사용된다.
- Authorization: JWT 토큰이나 인증 관련 헤더를 사용할 때 필요하다.
728x90
'💡 URECA > 🗒️ 스터디 노트' 카테고리의 다른 글
[URECA] Day 80 LabbitLog 블로그 만들기 #4 (0) | 2025.05.27 |
---|---|
[URECA] Day79 LabbitLog 블로그 만들기 #3 (0) | 2025.05.27 |
[URECA] Day 78 MERN stack이란 무엇을 의미할까? 그리고 LabbitLog (0) | 2025.05.22 |
[URECA] 2번째 미니 프로젝트 끝! Day 77 (4) | 2025.05.21 |
[URECA] 학습 블로깅은 데일리 프로젝트 회고로 대체 (Day 68~Day 76) (0) | 2025.05.07 |