📍Frontend
🖋️ReactQuill이란
Quill - Your powerful rich text editor
Built for Developers Granular access to the editor's content, changes and events through a simple API. Works consistently and deterministically with JSON as both input and output.
quilljs.com
React에서 사용하는 글쓰기 에디터 컴포넌트이다. 사용자는 블로그 글을 작성하듯이 텍스트를 입력하거나 이미지를 첨부할 수 있다. QuillEditor.jsx는 Quill이라는 오픈소스 리치 텍스트 에디터를 기반으로 구현된 컴포넌트이다.
import ReactQuill from 'react-quill-new'
- ReactQuill은 Quill 에디터를 React에서 사용할 수 있도록 만든 라이브러리이다.
- theme="snow" 옵션은 기본적인 하얀 배경 테마를 적용하는 설정이다.
useRef와 quillRef
const quillRef = useRef(null)
useRef는 React에서 특정 DOM 요소나 컴포넌트에 직접 접근할 때 사용하는 훅이다. 이 컴포넌트에서는 quillRef를 통해 Quill 에디터 내부 메서드에 접근하여 이미지 삽입이나 커서 위치 확인 등의 작업을 수행한다.
이미지 업로드 핸들러 (imageHandler)
const imageHandler = () => { ... }
이 함수는 사용자가 툴바에서 이미지 버튼을 클릭했을 때 실행된다. 내부적으로는 파일 선택 input을 만든 후, 사용자가 이미지를 선택하면 에디터에 미리보기용 임시 URL을 삽입한다. 이때 FileReader를 사용하여 이미지를 base64 형식으로 읽고, 에디터에 embed 방식으로 삽입한다. 주석 처리된 부분은 서버에 이미지를 업로드한 뒤, 임시 URL을 실제 서버 URL로 교체하는 기능을 담당한다. 필요에 따라 이 부분을 활용하면 클라우드 이미지 저장도 가능하다.
modules 설정 – 툴바 구성
const modules = {
toolbar: {
container: [...],
handlers: { image: imageHandler },
}
}
- modules는 Quill 에디터의 툴바 구성 옵션이다.
- 툴바에 어떤 버튼을 보여줄지, 각 버튼 클릭 시 어떤 동작을 할지 지정한다.
예를 들어 ['bold', 'italic', 'underline']은 굵게/기울임/밑줄을 의미하며, handlers.image = imageHandler는 이미지 버튼을 누를 때 사용할 커스텀 핸들러를 지정한 것이다.
ReactQuill 렌더링
<ReactQuill
ref={quillRef}
theme="snow"
value={value}
onChange={onChange}
modules={modules}
placeholder={placeholder || '내용을 입력해 주세요'}
/>
최종적으로 렌더링되는 Quill 에디터이다. 외부에서 value, onChange를 전달받아 에디터 상태를 제어하며, placeholder도 외부에서 받아온 값을 적용하거나 기본값으로 "내용을 입력해 주세요"를 사용한다.
📍Backend
1. bcryptjs – 비밀번호를 안전하게 저장하기 위한 암호화 도구 📦
왜 필요한가?
사용자가 입력한 비밀번호를 그대로 데이터베이스에 저장하면 위험하다. 누군가 DB를 털어가면 비밀번호가 고스란히 노출되기 때문이다. 그래서 비밀번호는 암호화해서 저장해야 한다.
어떻게 작동하는가?
- bcrypt는 Salt라는 값을 넣어 비밀번호를 무작위로 섞어서 암호화한다.
- 같은 비밀번호라도 매번 결과가 달라진다.
- 암호화된 비밀번호는 다시 원래대로 복호화할 수 없다.
import bcrypt from "bcryptjs";
const saltRounds = 10;
const userDoc = new userModel({
username,
password: bcrypt.hashSync(password, saltRounds), // 비밀번호 암호화
});
- saltRounds: 몇 번 섞을지 결정하는 숫자이다. 숫자가 클수록 더 강력한 암호화가 된다.
- hashSync: 입력한 비밀번호를 암호화한 값으로 바꿔준다.
2. jsonwebtoken (JWT) – 로그인 인증을 위한 토큰 🔐
왜 필요한가?
사용자가 로그인을 하면, 이 사용자가 정말 로그인한 사람인지를 서버가 기억해야 한다. 하지만 서버가 모든 사용자의 상태를 기억하기는 어렵다. 그래서 JWT(Json Web Token)이라는 인증표를 만들어서, 사용자에게 맡긴다.
JWT란?
- **로그인한 사용자 정보(아이디 등)**를 담은 작은 쪽지이다.
- 이 쪽지를 브라우저에 쿠키로 저장해두고, 다음 요청에 함께 보낸다.
- 서버는 그 쪽지를 보고, "아, 너 로그인했구나!" 라고 확인한다. 💡
import jwt from 'jsonwebtoken';
const secretKey = 'testtest';
const tokenLife = '1h'; // 토큰 유효시간
- secretKey: 쪽지를 암호화할 때 사용하는 비밀 열쇠이다. 이걸 알아야 진짜인지 확인할 수 있다.
- tokenLife: 토큰이 언제까지 유효한지를 설정하는 것이다.
로그인시 토큰 발급
const payload = { id: _id, username };
const token = jwt.sign(payload, secretKey, {
expiresIn: tokenLife,
});
- sign() 함수는 사용자의 정보를 담아서 암호화된 토큰을 만든다.
- 토큰은 res.cookie("token", token)으로 사용자 브라우저에 저장된다.
3. 쿠키 (cookie-parser) – 브라우저에 토큰을 보관 🍪
왜 필요한가?
토큰을 사용자의 브라우저에 보관하고, 다음 요청마다 자동으로 보낼 수 있게 하기 위해 쿠키를 사용한다.
import cookieParser from "cookie-parser";
app.use(cookieParser());
res.cookie("token", token, {
httpOnly: true,
});
- cookie-parser: 쿠키를 쉽게 꺼내고 사용할 수 있게 해주는 도구이다.
- httpOnly: true: 자바스크립트에서 쿠키를 못 보게 해서 XSS 공격을 막는 역할을 한다.
🛡️ xxs공격이란?
XSS(Cross-Site Scripting)는
웹사이트에 **악성 스크립트(JavaScript 등)**를 삽입해서 다른 사용자의 브라우저에서 실행되도록 만드는 공격이다.
어떤 웹사이트의 댓글창에 다음과 같은 내용을 누군가 입력했다고 가정해 보자
<script>alert('해킹됨!');</script>
해당 사이트가 입력값을 필터링하지 않고 그대로 출력한다면, 이 댓글을 본 사용자의 브라우저에서 alert()가 실제로 실행되어 팝업이 뜬다. 더 심각한 경우에는 쿠키 탈취, 세션 하이재킹, 악성 사이트 유도도 가능하다.
어떤 일이 벌어질 수 있나?
- 사용자의 세션 쿠키 탈취
- 악성 사이트로 자동 리디렉션
- 사용자 계정 무단 조작
- 입력폼 위조로 피싱 공격
어떻게 방어하나?
- 입력값 필터링 및 이스케이프 처리
- <, >, ', " 같은 HTML 태그나 속성 문자를 문자 자체로 처리해야 한다.
- httpOnly 쿠키 설정
- 자바스크립트로 쿠키에 접근하지 못하도록 막는다.
- res.cookie('token', token, { httpOnly: true })
- Content Security Policy(CSP) 설정
- 브라우저가 외부 스크립트를 제한하도록 강제할 수 있다
4. 로그인 확인 (토큰 검사)
왜 필요한가?
로그인하지 않은 사용자가 마치 로그인한 척할 수 있으니, 토큰이 진짜인지 검증해야 한다.
app.get("/profile", (req, res) => {
const { token } = req.cookies;
if (!token) {
return res.status(401).json({ error: "로그인 필요" });
}
jwt.verify(token, secretKey, (err, info) => {
if (err) {
return res.json({ error: "로그인 필요" });
}
res.json(info);
});
});
- 쿠키에서 토큰을 꺼내서 jwt.verify()로 검사한다.
- 문제가 없으면 로그인한 사용자의 정보를 info에 담아 돌려준다.
5. 로그아웃
왜 필요한가?
로그아웃을 하면 쿠키에 저장된 토큰을 삭제해야 한다.
app.post("/logout", (req, res) => {
res
.cookie("token", "", {
httpOnly: true,
expires: new Date(0),
})
.json({ message: "로그아웃 되었음" });
});
- 비밀번호는 그대로 저장하면 안 되니까 섞어서 저장하는 도구가 bcrypt이다.
- 로그인하면 로그인했다는 증명서(토큰)를 만들어서 너한테 맡긴다.
- 서버는 다음에 요청이 올 때 그 증명서를 보고 “아 너 맞구나~” 하고 인증한다.
- 이 증명서는 쿠키 안에 담겨서 자동으로 서버에 전해진다.
- 로그아웃하면 쿠키에 담긴 증명서를 지워버린다.
🛜 서버에서 하는 역할
app.post() – "보내줘! (Post)"
👉 클라이언트(브라우저)가 서버에 무언가를 보낼 때 사용한다
app.post('/register', async (req, res) => { ... });
- 이건 회원가입 요청이다.
- 사용자가 입력한 username, password 같은 데이터를 보낼 때 사용한다.
- 서버는 그걸 받아서 DB에 저장하거나 검사한다.
➕ 로그인도 POST로 한다 → 비밀번호 같은 민감한 데이터를 보내야 하기 때문!
app.get() – "가져와! (Get)"
👉 서버에서 정보를 꺼내올 때 사용한다. (데이터를 받기만 하고, 보낼 필요는 없음)
app.get('/profile', (req, res) => { ... });
- 사용자가 "내 프로필 보여줘!" 라고 요청할 때.
- 서버는 로그인한 사용자의 정보(JWT 토큰 기반)를 찾아서 보내준다.
- 데이터를 보내는 게 아니라 요청만 하는 거니까 GET
app.listen() – 서버 문 열기
👉 서버가 몇 번 포트에서 대기 중인지 알려주는 함수이다.
app.listen(port, () => {
console.log(`${port} 포트에서 돌고있음`);
});
- 3000번 문(포트)을 열고 서버가 기다리는 것이다.
- 클라이언트가 요청을 보내면 받을 준비가 됐다는 뜻!
- 보낼 땐 POST
- 받을 땐 GET
- 고칠 땐 PUT
- 삭제할 땐 DELETE
🎠 회고
비도 오고 졸립고 오늘 하루는 버티기 힘들었다. 점심시간에 바로 김치전먹고 잤다.. 일어났는데 어찌나 더 피곤한지 헤롱한채로 오후 수업 1교시를 들었다.
서버 역할을 다시 적은 이유는 까먹었다. 뭘, 언제, 왜 적는지 말이다. 기억날듯말듯해서 그냥 정리했다.

오늘 수업은 마치 고봉밥 같았다. 접시에 넘치도록 담긴 밥처럼, 한꺼번에 너무 많은 내용을 배운 탓에 머릿속이 가득 찬 느낌이었다. 모든 내용을 다 소화하기엔 벅찼지만, 그중에서도 정말 중요한 것, 그리고 내가 앞으로 꼭 기억해두고 싶은 내용들만 골라서 정리해 보았다.

성심당 망고 시루케이크 이번에는 꼭 먹어야겠다. 너무 맛있어보여..
내가 먹어본 시루케이크는 초코 시트가 있는 딸기 시루 케이크를 먹었다. 이건 하루 지나서 냉장고에서 숙성되고 먹어야 더욱 맛있게 먹을 수 있다. 망고 시루케이크 역시 그럴듯하다. 다이어트는 어디갔냐고? 일주일째 홈트를 하고 있지만 식단 조절을 안해서 그런가 1도 안빠지고 있다. 오늘부터 공복 16시간 유지하자!
'💡 URECA > 🗒️ 스터디 노트' 카테고리의 다른 글
[URECA] MogoDB + 회원가입 (0) | 2025.04.30 |
---|---|
[URECA] Day64 React API 공공데이터 (0) | 2025.04.29 |
[URECA] Day63 React Weather API (0) | 2025.04.28 |
[URECA] Day 62 React Proj WalletWatcher (2) | 2025.04.25 |
[URECA] Day 60 React Redux (0) | 2025.04.24 |