학원 수업 Day33

mongoDB를 활용해 본격적으로 커뮤니티 게시판을 생성했다.


게시글 출력

back-end

app.post('/api/read', (request, response)=>{
  Post.find()
    .exec()
    .then(doc=>{
      response.json({success: true, list: doc});
    })
    .catch(error=>{
      console.log(error);
      response.json({success: false});
    })
})

node/index.js에서 find(조건) 메서드를 사용해서 불러올 조건을 지정한다.

비워두면 전체 데이터를 불러오게 된다.

exec() 는 조건을 실행해 데이터를 받아온 후 Promise 객체를 반환한다.

성공적으로 불러오면 데이터를 반환해야 하기 때문에 then() 구문 내부에 성공시 반환값을 지정한다.

then은 파라미터로 반환된 데이터를 받아오니 해당 데이터를 임의의 키값으로 (여기서는 list) 반환시켜 주도록 한다.


front-end

List 컴포넌트를 생성해서 back-end에서 보낸 리스트 데이터를 axios로 불러와야 한다.

import axios from 'axios';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';

마운트 시 axios 로 통신하기 위해 useEffect와 axios를, 리스트를 state에 담기 위해 useState를, 클릭 시 내용 페이지로 연결하기 위해 Link 를 import 한다.


const [ List, setList ] = useState([]);

useEffect(()=>{
  axios.post('/api/read')
    .then(response=>{
      if (response.data.success) {
        console.log(response.data.communityList.length);
        setList(response.data.communityList);
      }
    })
    .catch(error=>{
      console.log(error);
    })
}, []);

node에서 설정한 경로 (/api/read) 로 연결하면 데이터를 불러오게 된다.

불러온 데이터를 state에 담는다.


<Layout name='List'>
  {List.map(post=>{
    return (
      <article key={post._id}>
        <h2>
          <Link to={`/detail/${post.communityList}`}>
            {post.title}
          </Link>
        </h2>
      </article>
    )
  })}
</Layout>

불러온 데이터를 map을 돌며 뿌려준다.

key 값은 불러온 인덱스보다 고유한 데이터인 것이 좋다.

mongoDB에서 _id 로 고유 아이디 값을 생성하므로 key 값으로 지정한다.

클릭 시 해당 글의 detail 페이지로 이동하도록 Link 에 고유값을 추가한다.

자동으로 생성되는 _id 는 랜덤한 숫자구조로 노출되기에 적절.. 예쁘지 않아서 communityList라는 이름으로 고유값을 만들어서 사용할 예정이다.






게시글 작성

back-end

리스트 출력 때 고유 아이디를 mongoDB에서 생성한 _id가 아닌 다른 보기 좋은 값으로 지정해주기로 했다.

해당 값을 DB에서 관리하기 위해 새로운 schema 를 생성해준다.

const mongoose = require('mongoose');

const counterSchema = new mongoose.Schema({
  name: String,
  communityNum: Number
}, { collection: 'Counter'});
    // 값이 저장될 컬렉션 지정

const Counter = mongoose.model('Counter', counterSchema);
module.exports = { Counter };

CounterSchema.js로 명명해서 작성한 위 코드는 마지막 export 이름인 Counter로 호출하면 실행된다.

communityNum은 숫자 형태로 저장되며 게시글 작성 시 1씩 더해서 수정할 예정이다.

현재는 값이 없어 게시글 작성 시 데이터를 불러오지 못해 에러가 나므로 MongoDB 사이트에 가서 초기값을 지정해야 한다.

name 은 communityNum 값의 분류용으로 추가했고, 임의로 counter라고 지정했다.

communityNum 은 1으로 정해주었다.

주의할 점은 DB에서 insert 시 int32로 데이터타입을 지정해서 숫자값으로 저장되게 해야한다.

초기값 저장 후 다시 index.js로 돌아와 Counter를 호출하고, 해당값을 사용한다.

app.post('/api/create', (request, response)=>{
  //Counter 에서 counter로 저장된 name을 찾아 해당 DOCUMENT 출력
  Counter.findOne({name: 'counter'})
    .exec()
    .then(doc=>{
      const PostModel = new Post({
        title: request.body.title,
        content: request.body.content,
        communityNum: doc.communityNum
        // 프론트에서 받은 데이터에 현재 Counter의 communityNum 값 추가
      })

      PostModel.save()
      .then(()=>{
        // 갱신 후 counter 의 communityNum 값 업데이트
        Counter.updateOne({name: 'counter'}, {$inc: {communityNum: 1}})
          .then(()=>{
            response.json({success: true});
          })
      })
      .catch(error=>{
        console.log(error);
        response.json({success: false})
      });
    })
})

게시글 저장 후 updateOne( 찾을조건, 변경식 ) 으로 현재 communityNum의 값을 1 더해준다.

  • $inc : 증가
  • $dec : 감소
  • $set : 새로운 값으로 변경


front-end

import axios from 'axios';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';

input 값을 받아오기 위해 useState를, 작성 버튼 클릭 시 통신하기 위해 axios를, 데이터 작성 후 페이지를 이동시키기 위해 useNavigate 를 import 한다.


const navigate = useNavigate();
const [ Title, setTitle ] = useState('');
const [ Content, setContent ] = useState('');

const handleCreate = ()=>{
  if (!Title.trim() || !Content.trim()) return alert('제목과 본문을 모두 입력하세요.');
  const item = {title: Title, content: Content};

  axios.post('/api/create', item)
  .then(response=>{
    if (response.data.success){
      alert('글 저장이 완료되었습니다.');
      navigate('/list');
    } else {
      alert('글 저장이 실패했습니다.');
    }
  })
  .catch(err=>{
    console.log(err);
  })
}

버튼 클릭 시 발생할 이벤트를 생성하고 빈 값을 저장하지 못하도록 조건문도 추가해준다.

그리고 post() 인자에 create로 경로를 지정하고 작성한 데이터를 두번째 인자로 보낸다.


<Layout name='Post'>
  <ul>
    <li>
      <label htmlFor='title'>Title</label>
      <input type='text'
        name='title' id='title'
        value={Title}
        onChange={e=>setTitle(e.target.value)}
      />
    </li>
    <li>
      <label htmlFor='content'>Content</label>
      <textarea
        name='content' id='content'
        cols='30' rows='3'
        value={Content}
        onChange={e=>setContent(e.target.value)}
      ></textarea>
    </li>
    <li>
      <button
        onClick={handleCreate}
      >SEND</button>
    </li>
  </ul>
</Layout>

input값을 state로 지정하고, onChange시 state값이 변경되도록 이벤트를 지정한다.






게시글 내용 출력

리스트에서 제목 글릭 시 게시글 내용 페이지로 이동해 글 내용을 불러온다.

back-end

app.post('/api/detail', (request, response)=>{
  Post.findOne({communityNum: request.body.num})
    .exec()
    .then(doc=>{
      response.json({success: true, detail: doc});
    })
    .catch(error=>{
      console.log(error);
      response.json({success: false});
    })
})

리스트 출력과 비슷한 양식이나 find() 가 아닌 findOne() 으로 조건에 따른 한가지 document 만 불러온다.

쿼리스트링으로 document 별 고유값으로 만들어 줬던 communityNum 을 보내줄 예정이기 때문에 그 값을 받아와서 출력 조건으로 사용한다.

그리고 성공 시 detail 이라는 키값으로 반환한다.


front-end

import axios from 'axios';
import { useParams } from 'react-router-dom';
import { useEffect, useState } from 'react';

마운트 시 데이터를 통신해야 하므로 useEffect와 axios, 받아온 데이터를 담을 useState, 그리고 리스트에서 클릭 시 넘겨준 쿼리스트링 값을 사용해야 하기 때문에 useParams 을 import 한다.

const [ Detail, setDetail ] = useState(null);
const params = useParams();

const item = {
  num: params.num
}

useState() 로 State를 생성하고, useParams() 로 게시물의 고유 id 값을 받아온 쿼리 스트링을 params으로 가져와서 변수에 담아준다.

useEffect(()=>{
  axios.post('/api/community/detail', item)
    .then(response=>{
      if (response.data.success) {
        console.log(response.data.detail);
        setDetail(response.data.detail);
      }
    })
    .catch(error=>{
      console.log(error);
    })
}, []);

마운트 시 데이터를 받아와야 하므로 useEffect() 으로 게시글 데이터를 가져오는 경로에 연결한다.

post() 의 두번째 인자값으로 앞에서 미리 정의해둔 쿼리스트링이 담긴 객체를 보내면 back-end 에서 해당 값으로 게시물을 검색해서 반환 해 줄 것이다.

반환된 데이터를 State 에 저장해서 사용하면 된다.

<Layout name='Detail'>
  {Detail &&
    <>
      <h2>{Detail.title}</h2>
      <p>{Detail.content}</p>
    </>
  }
</Layout>

State 기본값이 null 이므로 출력 조건을 걸어주고 State 값이 null 이 아닐경우 데이터를 출력하도록 작성한다.