[Go!Street] MERN STACK으로 혼자 구현해보는 커뮤니티 사이트 2️⃣

Beanxx·2022년 10월 29일
0

Go!Street Project 🚚 

목록 보기
2/3
post-thumbnail

💡 게시글 CRUD & 상세페이지

💫 게시글 조회

[Client]

// 게시글 조회 axios 요청
useEffect(() => {
  axios.post("/api/post/list")
    .then((response) => {
      if (response.data.success) {
        setPostList([...response.data.postList]);
      }
    })
    .catch((error) => {
      console.log(error);
    });
}, []);
// 게시글 데이터를 불러오고, 각 게시글마다 Link를 걸어서 상세페이지로 이동하게 한다.
{postList.map((post, idx) => {
  return (
    <ListItem key={idx}>
      <Link to={`/post/${post.postNum}`}>
        <p>제목: {post.title}</p>
        <p>내용: {post.content}</p>
      </Link>
    </ListItem>
  );
})}

[Server]

// server/Router/post.js

router.post("/list", (req, res) => {
  Post.find()
    .exec().then((doc) => {
      res.status(200).json({ success: true, postList: doc });
    })
    .catch((err) => {
      res.status(400).json({ success: false });
    });
});

💫 게시글 추가

[Client]

// 게시글 등록 axios 요청
const onSubmit = (e) => {
  e.preventDefault(); // 버튼 클릭시 기본 동작인 새로고침이 발생하는데 이를 막기 위한 용도
  // 새로고침이 발생해서 아래 코드들이 무시가 되어 버리므로 위 코드 추가해주기

  let body = { title, content, image };

  axios.post("/api/post/submit", body)
    .then((response) => {
      if (response.data.success) {
        // 글 등록에 성공했을 때 실행될 코드
      } else {
        // 글 등록에 실패했을 때 실행될 코드 
      }
    })
    .catch((err) => {
      console.log(err);
    });
};

[Server]

server의 Model 폴더 내에 Counter.js 파일을 추가 생성해주고, DB 스키마를 추가해준다.
Counter.js 파일은 게시글 번호를 저장해놓는 컬렉션이라고 생각하면 될듯
즉, 만약에 postNum이 10번인 글을 마지막으로 추가했다면, counter 컬랙션의 postNum은 추후 등록할 게시글의 번호인 11번이 들어가 있게 된다.

// server/Model/Counter.js

const mongoose = require("mongoose");

const counterSchema = new mongoose.Schema(
  {
    name: String,
    postNum: Number,
  },
  { collection: "counter" }
);

const Counter = mongoose.model("Counter", counterSchema);

module.exports = { Counter };
스크린샷 2022-10-29 오후 2 35 56
// server/index.js

// 게시글 CRUD 부분 코드를 Router 폴더로 따로 분리한 후, index.js 파일에서 /api/post 라우터로 불러와 쓸 수 있다.
app.use("/api/post", require("./Router/post.js"));
// server/Router/post.js

// index.js에서 루트 라우터로 /api/post를 설정해줬기 때문에 여기선 /api/post 뒤에 붙는 라우터만 작성해서 사용 가능!

// 게시글 등록
router.post("/submit", (req, res) => {
  let temp = req.body;
  // mongoDB에서 여러 document를 찾는 명령어: find()
  // 하나의 document를 찾을 땐 findOne()
  Counter.findOne({ name: "counter" })
    .exec().then((counter) => {
      temp.postNum = counter.postNum; // counter.postNum에는 등록할 게시글번호가 담겨 있기 때문에 이를 가져와 할당해준다
      const CommunityPost = new Post(temp); // temp엔 프론트에서 axios 요청으로 보낸 req.body가 담겨 있으므로 이를 Post 컬렉션에 저장해준다

      CommunityPost.save().then(() => {
        Counter.updateOne({ name: "counter" }, { $inc: { postNum: 1 } }).then( // $inc : 값 증가시켜줌
          () => {
            res.status(200).json({ success: true });
          }
        );
      });
    })
    .catch((err) => {
      res.status(400).json({ success: false });
    });
});

💫 게시글 수정

[Client]

// client/components/post/Edit.js

let params = useParams();
let navigate = useNavigate();

// 수정시 input칸에 초기 데이터 넣어주기
useEffect(() => {
  setTitle(postInfo.title);
  setContent(postInfo.content);
  setImage(postInfo.image);
}, [postInfo]);

// 게시글 수정 axios 요청
const onSubmit = (e) => {
  e.preventDefault();

  let body = { title, content, image, postNum: params.postNum };

  axios.post("/api/post/edit", body)
    .then((response) => {
      if (response.data.success) {
        // 글 수정에 성공했을 때 실행될 코드
        navigate(`/post/${params.postNum}`); // 상세페이지로 이동
      } else {
		// 글 수정에 실패했을 때 실행될 코드
      }
    })
    .catch((err) => {
      console.log(err);
    });
};

[Server]

// 게시글 수정
router.post("/edit", (req, res) => {
  let temp = { title: req.body.title, content: req.body.content, image: req.body.image };
  
  Post.updateOne({ postNum: Number(req.body.postNum) }, { $set: temp }) // $set: 새로운 정보로 완전히 교체
    .exec().then(() => {
      res.status(200).json({ success: true });
    })
    .catch((err) => {
      res.status(400).json({ success: false });
    });
});

💫 게시글 삭제

[Client]

// client/components/post/Detail.js

const [postInfo, setPostInfo] = useState({});
const [flag, setFlag] = useState(false);

// 서버에 게시글번호 보내고, 응답으로 게시글 정보 받아오기
useEffect(() => {
  let body = { postNum: params.postNum };
  axios.post("/api/post/detail", body)
    .then((response) => {
      if (response.data.success) {
        setPostInfo(response.data.post);
        setFlag(true); // 성공적으로 서버 응답을 가져오면 true로 변경
      }
    })
    .catch((err) => {
      console.log(err);
    });
}, []);

// 게시글 삭제 axios 요청
const DeleteHandler = () => {
  let body = { postNum: params.postNum };
  axios.post("/api/post/delete", body)
    .then((response) => {
      if (response.data.success) {
        // 글 삭제에 성공했을 때 실행될 코드
        navigate("/");
      }
    })
    .catch((err) => {
      // 글 삭제에 실패했을 때 실행될 코드
    });
};
// 상세페이지 return 부분에서 flag 값이 true일 땐 데이터를 보여주고, false일 경우엔 Spinner 화면 보여주기

{flag ? ( <>상세페이지 화면</> ) : ( <Spinner /> )}

[Server]

// 게시글 삭제
router.post("/delete", (req, res) => {
  Post.deleteOne({ postNum: Number(req.body.postNum) })
    .exec().then(() => {
      res.status(200).json({ success: true });
    })
    .catch((err) => {
      res.status(400).json({ success: false });
    });
});

💫 상세페이지

[Server]

// 상세페이지
router.post("/detail", (req, res) => {
  Post.findOne({ postNum: Number(req.body.postNum) })
    .exec().then((doc) => {
      console.log(doc);
      res.status(200).json({ success: true, post: doc });
    })
    .catch((err) => {
      res.status(400).json({ success: false });
    });
});


💡 Image Upload

npm i multer --save
npm i multer-s3 --save
npm i --save aws-sdk@2.348.0 

🚨 multer-s3 버전이 높아서 이미지 업로드할 때 계속 에러가 발생했었다..
이 경우에 multer-s3 버전을 낮춰줘서 해결했다.

스크린샷 2022-10-29 오전 2 28 35
# 버전 낮춰주기 (3.xx -> 2.xx)
"multer-s3": "^2.9.0" 

[Client]

import { Form } from "react-bootstrap";

const ImageUpload = (props) => {
  const FileUpload = (e) => {
    var formData = new FormData();
    formData.append("file", e.target.files[0]);
    axios.post("/api/post/image/upload", formData).then((response) => {
      props.setImage(response.data.filePath); // 응답으로 온 이미지 파일 경로 image 변수에 저장하기
    });
  };

  return (
    <div>
      <Form.Control
        type="file"
        className="shadow-none"
        accept="image/*"  
        onChange={(e) => FileUpload(e)}
      />
      {/* accept 속성: 어떤 유형의 파일을 input 태그로 관리할지 지정 가능  */}
    </div>
  );
};

[Server]

이미지 업로드 부분만 따로 분리해서 관리하기!
이미지 업로드 부분은 multer, multer-s3 모듈을 사용하여 구현한다.
이 부분은 Naver Cloud - Oject Storage 사용가이드를 참고하면 된다.

🔗 [Naver Cloud 사용가이드] Javascript용 AWS SDK - Amazon S3 API 활용 예제

// server/Util/upload.js

const AWS = require("aws-sdk");
const multer = require("multer");
const multerS3 = require("multer-s3");
const fs = require("fs");
const path = require("path");

const endpoint = new AWS.Endpoint("https://kr.object.ncloudstorage.com");
const region = "kr-standard";
const config = require("../config/key.js"); // Naver Cloud의 accessKeyId, secretAccessKey 따로 관리

const S3 = new AWS.S3({
  endpoint: endpoint,
  region: region,
  credentials: {
    accessKeyId: config.access_key,
    secretAccessKey: config.secret_key,
  },
});

function setUpload(bucket) {
  const upload = multer({
    storage: multerS3({
      s3: S3,
      bucket: bucket,
      acl: "public-read-write", // 보안규칙 설정 (누구나 읽고 쓸 수 있게 설정)
      key: function (req, file, cb) {
        let extension = path.extname(file.originalname); // 확장자명을 제외한 이름
        cb(null, Date.now().toString() + extension);
      },
    }),
  }).single("file");
  return upload;
}

module.exports = setUpload;
// server/Router/post.js

// 미들웨어를 사용하여 외부 저장소에 이미지 업로드
router.post(
  "/image/upload",
  setUpload("community-bucket/post"), // 따로 빼준 업로드 기능 함수 불러와서 사용하기
	// 함수 인자로 Naver Cloud - Object Storage - Bucket 이름/폴더명 지정해주기
  (req, res, next) => { // next 키워드로 express middleware 사용
    res.status(200).json({ success: true, filePath: res.req.file.location }); // filePath에 image file 경로 저장
  }
);

💫✨ Bucket 안에 이미지가 잘 들어간 것을 확인할 수 있다!

스크린샷 2022-10-29 오전 2 45 48
profile
FE developer

0개의 댓글