[Node.js-07] API, 회원, 댓글

Comely·2025년 3월 12일

Node.js

목록 보기
7/14

Node.js 서버 기본 설정

서버 생성

// server.js
const express = require('express');
const path = require('path');
const app = express();

app.listen(8080, function () {
  console.log('listening on 8080')
});

설치 및 실행

# 프로젝트 초기화
npm init -y

# Express 설치
npm install express

# 서버 실행
nodemon server.js  # 또는 node server.js

React 기본 개념

React의 역할

  • HTML을 이쁘게 만들어주는 툴
  • Single Page Application (SPA) 개발
  • 새로고침 없이 부드러운 페이지 전환
  • 모바일 앱같은 사용자 경험 제공

React vs 일반 HTML

구분일반 HTMLReact
페이지 전환새로고침 발생새로고침 없음
개발 복잡도단순복잡
사용자 경험기본고급
코드 관리어려움컴포넌트화

React 프로젝트 생성

1. 프로젝트 생성

# React 프로젝트 생성
npx create-react-app react-project

# 개발 서버 실행
npm run start

# 빌드 (배포용)
npm run build

2. 폴더 구조

project/
├── server.js           # Node.js 서버
├── react-project/      # React 프로젝트
│   ├── src/            # 소스 코드
│   ├── public/         # 정적 파일
│   └── build/          # 빌드 결과물
└── package.json

Node.js + React 통합

1. 기본 통합 설정

// server.js
const express = require('express');
const path = require('path');
const app = express();

// React 빌드 파일 서빙
app.use(express.static(path.join(__dirname, 'react-project/build')));

// 메인 페이지
app.get('/', function (요청, 응답) {
  응답.sendFile(path.join(__dirname, '/react-project/build/index.html'));
});

app.listen(8080, function () {
  console.log('listening on 8080')
});

2. React 라우팅 지원

// server.js 하단에 추가
app.get('*', function (요청, 응답) {
  응답.sendFile(path.join(__dirname, '/react-project/build/index.html'));
});

주의: 이 코드는 항상 가장 하단에 위치해야 함


API 통신 설정

1. 서버 CORS 설정

// server.js 상단에 추가
app.use(express.json());
const cors = require('cors');
app.use(cors());
# CORS 설치
npm install cors

2. React에서 API 호출

// React 컴포넌트에서
useEffect(() => {
  fetch('/api/products')
    .then(res => res.json())
    .then(data => setProducts(data))
}, [])

3. 개발 환경 Proxy 설정

// react-project/package.json
{
  "name": "react-project",
  "version": "0.1.0",
  "proxy": "http://localhost:8080"
}

데이터 렌더링 방식

Server-Side Rendering (SSR)

// 서버에서 HTML 생성
app.get('/products', async (요청, 응답) => {
  let products = await db.collection('product').find().toArray()
  응답.render('products.ejs', { products })
})

특징:

  • 서버가 완성된 HTML 생성
  • 빠른 초기 로딩
  • SEO 친화적
  • 새로고침 발생

Client-Side Rendering (CSR)

// 서버는 API만 제공
app.get('/api/products', async (요청, 응답) => {
  let products = await db.collection('product').find().toArray()
  응답.json(products)
})
// React에서 데이터 가져오기
const [products, setProducts] = useState([])

useEffect(() => {
  fetch('/api/products')
    .then(res => res.json())
    .then(data => setProducts(data))
}, [])

특징:

  • React가 브라우저에서 HTML 생성
  • 부드러운 사용자 경험
  • 초기 로딩 느림
  • AJAX 통신 위주

실전 구현 예시

1. 제품 목록 API

// server.js
app.get('/api/products', async (요청, 응답) => {
  try {
    let products = await db.collection('product').find().toArray()
    응답.json(products)
  } catch (error) {
    응답.status(500).json({ error: '서버 에러' })
  }
})

2. React 제품 목록 컴포넌트

// ProductList.js
import { useState, useEffect } from 'react'

function ProductList() {
  const [products, setProducts] = useState([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetch('/api/products')
      .then(res => res.json())
      .then(data => {
        setProducts(data)
        setLoading(false)
      })
      .catch(err => {
        console.error(err)
        setLoading(false)
      })
  }, [])

  if (loading) return <div>로딩중...</div>

  return (
    <div>
      <h2>제품 목록</h2>
      {products.map(product => (
        <div key={product._id}>
          <h3>{product.name}</h3>
          <p>{product.price}</p>
        </div>
      ))}
    </div>
  )
}

export default ProductList

서브디렉토리 배포

서버 설정

// 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",
  ...
}

회원 기능 통합

1. 글 발행시 작성자 정보 저장

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,  // 유저명
    img: 요청.file?.location
  })
  응답.redirect('/list')
})

2. 본인 글만 삭제 가능

app.delete('/delete', async (요청, 응답) => {
  await db.collection('post').deleteOne({
    _id: new ObjectId(요청.query.docid),
    user: 요청.user._id  // 본인 글만 삭제 가능
  })
  응답.send('삭제완료')
})

정규화 vs 비정규화

정규화 (관계형 DB 방식)

// 글 테이블
{ _id: 1, title: '제목', content: '내용', userId: 123 }

// 유저 테이블  
{ _id: 123, username: 'john', email: 'john@email.com' }

장점: 데이터 정확성
단점: 조회시 JOIN 필요 (속도 저하)

비정규화 (MongoDB 권장 방식)

// 글 document
{ 
  _id: 1, 
  title: '제목', 
  content: '내용',
  user: 123,
  username: 'john'  // 중복 저장
}

장점: 빠른 조회 속도
단점: 데이터 불일치 가능성


댓글 기능 구현

1. 데이터 구조 설계

방법 1: 글 안에 배열로 저장

{
  _id: 1,
  title: '제목',
  content: '내용',
  comments: [
    { content: '댓글1', writer: 'user1' },
    { content: '댓글2', writer: 'user2' }
  ]
}

문제점:

  • 댓글 개별 수정/삭제 어려움
  • 16MB 크기 제한
  • 일부 댓글만 가져오기 불가능

방법 2: 별도 컬렉션 (권장)

// comment collection
{
  _id: ObjectId,
  content: '댓글 내용',
  writerId: ObjectId,
  writer: 'username',
  parentId: ObjectId,  // 부모 글 ID
  createdAt: new Date()
}

2. 댓글 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>

3. 댓글 저장 API

app.post('/comment', async (요청, 응답) => {
  await db.collection('comment').insertOne({
    content: 요청.body.content,
    writerId: new ObjectId(요청.user._id),
    writer: 요청.user.username,
    parentId: new ObjectId(요청.body.parentId),
    createdAt: new Date()
  })
  
  // 이전 페이지로 돌아가기
  응답.redirect('back')  // Express 4.x
  // 응답.redirect(요청.get('Referrer'))  // Express 5.x
})

4. 상세페이지에서 댓글 조회

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 
  })
})

개발 워크플로우

개발 환경

  1. React 개발 서버: npm run start (포트 3000)
  2. Node.js 서버: nodemon server.js (포트 8080)
  3. Proxy 설정으로 API 통신

배포 환경

  1. React 빌드: npm run build
  2. 통합 서버: Express에서 React + API 모두 서빙
  3. 단일 포트로 서비스

성능 최적화 팁

1. React 최적화

  • React.memo() 사용
  • useMemo(), useCallback() 활용
  • 불필요한 리렌더링 방지

2. API 최적화

  • 데이터 페이지네이션
  • 캐싱 전략 적용
  • 필요한 필드만 조회

3. 번들 최적화

  • Code Splitting
  • Lazy Loading
  • 이미지 최적화

실전 체크리스트

개발 환경 설정

  • Node.js 서버 구축
  • React 프로젝트 생성
  • CORS 설정
  • Proxy 설정

통합 작업

  • 정적 파일 서빙 설정
  • React 라우팅 지원
  • API 엔드포인트 구현
  • 에러 처리

회원 기능

  • 로그인 상태 확인
  • 권한 기반 기능 제한
  • 사용자 정보 저장

댓글 시스템

  • 데이터 구조 설계
  • CRUD 기능 구현
  • UI/UX 개선
profile
App, Web Developer

0개의 댓글