Node.js 서버 생성
const express = require('express');
const path = require('path');
const app = express();
app.listen(8080, function () {
console.log('listening on 8080')
});
설치 과정
1. Node.js 설치
2. 작업폴더 생성 후 에디터로 오픈
3. server.js 파일 생성
4. 터미널에서 npm init -y 입력
5. npm install express 입력
6. nodemon server.js 또는 node server.js로 서버 실행
React가 필요한 이유
React 프로젝트 생성
npx create-react-app 프로젝트명
cd 프로젝트명
npm run start # 개발 서버 실행
npm run build # 배포용 파일 생성
폴더 구조 예시
프로젝트 폴더/
├── server.js
└── react-project/
└── build/
├── index.html
├── static/
└── ...
서버에서 React 파일 제공
// server.js
app.use(express.static(path.join(__dirname, 'react-project/build')));
app.get('/', function (요청, 응답) {
응답.sendFile(path.join(__dirname, '/react-project/build/index.html'));
});
정적 파일 제공 설정
express.static(): CSS, JS, 이미지 등 정적 파일들을 자동으로 제공React 라우터 사용 시 주의사항
React 라우팅 전권 위임
// 모든 경로를 React가 처리하도록 설정 (가장 하단에 배치)
app.get('*', function (요청, 응답) {
응답.sendFile(path.join(__dirname, '/react-project/build/index.html'));
});
Server-side Rendering vs Client-side Rendering
Server-side Rendering
1. 서버에서 DB 데이터 조회
2. HTML에 데이터 삽입
3. 완성된 HTML을 클라이언트에 전송
Client-side Rendering (React 방식)
1. React에서 서버에 AJAX 요청으로 데이터 요청
2. 서버가 JSON 형태로 데이터만 전송
3. React가 받은 데이터로 동적으로 HTML 생성
React-서버 통신 설정
// server.js 상단에 추가
app.use(express.json());
var cors = require('cors');
app.use(cors());
설치 필요
npm install cors
express.json(): 클라이언트가 보낸 JSON 데이터 파싱cors: 다른 도메인 간의 AJAX 요청 허용개발 시 권장 방식
npm run start로 별도 서버 실행nodemon server.js로 별도 서버 실행React에서 서버 통신
// 전체 URL 명시
fetch('http://localhost:8080/api/data')
// 또는 package.json에 proxy 설정
"proxy": "http://localhost:8080"
배포 시
npm run build로 최종 빌드 파일 생성여러 앱을 다른 경로에서 제공
// server.js
app.use('/', express.static(path.join(__dirname, 'public')))
app.use('/react', express.static(path.join(__dirname, 'react-project/build')))
app.get('/', function(요청, 응답){
응답.sendFile(path.join(__dirname, 'public/main.html'))
})
app.get('/react', function(요청, 응답){
응답.sendFile(path.join(__dirname, 'react-project/build/index.html'))
})
React 프로젝트 설정
// react-project/package.json
{
"homepage": "/react",
"version": "0.1.0"
}
글 발행 시 작성자 정보 저장
app.post('/add', upload.single('img1'), async (요청, 응답) => {
await db.collection('post').insertOne({
title: 요청.body.title,
content: 요청.body.content,
user: 요청.user._id, // 현재 로그인 유저 ID
username: 요청.user.username // 현재 로그인 유저명
})
})
비정규화 vs 정규화
정규화 방식 (관계형 DB)
비정규화 방식 (MongoDB 권장)
본인 글만 삭제 가능
app.delete('/delete', async (요청, 응답) => {
await db.collection('post').deleteOne({
_id: new ObjectId(요청.query.docid),
user: 요청.user._id // 현재 로그인 유저와 작성자가 일치하는 경우만
})
응답.send('삭제완료')
})
추가 구현 아이디어
1. 본인 글만 수정 가능: 삭제와 동일한 방식으로 조건 추가
2. 본인 글에만 삭제 버튼 표시: EJS에서 조건문으로 버튼 노출 제어
3. 삭제 성공 시에만 UI 업데이트: AJAX 응답 처리로 성공 여부 확인
데이터 저장 방식 비교
방식 1: 게시글 내 배열로 저장
{
title: '게시글 제목',
content: '게시글 내용',
comments: ['댓글1', '댓글2', '댓글3']
}
방식 2: 별도 컬렉션으로 관리
// comment 컬렉션
{
content: '댓글 내용',
writerId: ObjectId('작성자ID'),
writer: '작성자명',
parentId: ObjectId('부모게시글ID')
}
1. 댓글 UI 생성
<!-- detail.ejs -->
<div class="detail-bg">
<h4><%= result.title %></h4>
<p><%= result.content %></p>
<hr style="margin-top: 60px">
<!-- 댓글 목록 -->
<% for (let i = 0; i < result2.length; i++) { %>
<p><strong><%= result2[i].writer %></strong> <%= result2[i].content %></p>
<% } %>
<!-- 댓글 작성 폼 -->
<form action="/comment" method="POST">
<input name="content" placeholder="댓글을 입력하세요">
<input name="parentId" value="<%= result._id %>" style="display: none">
<button type="submit">댓글작성</button>
</form>
</div>
2. 댓글 저장 API
app.post('/comment', async (요청, 응답) => {
let result = await db.collection('comment').insertOne({
content: 요청.body.content,
writerId: new ObjectId(요청.user._id),
writer: 요청.user.username,
parentId: new ObjectId(요청.body.parentId)
})
응답.redirect('back') // 이전 페이지로 이동
})
3. 댓글과 함께 상세페이지 표시
app.get('/detail/:id', async (요청, 응답) => {
let result = await db.collection('post').findOne({
_id: new ObjectId(요청.params.id)
})
let result2 = await db.collection('comment').find({
parentId: new ObjectId(요청.params.id)
}).toArray()
응답.render('detail.ejs', {result: result, result2: result2})
})
고급 기능 추가