안녕하세요

[Node.js] 게시판 기능 구현하기 본문

데이터시각화-KMG/Node.js

[Node.js] 게시판 기능 구현하기

sakuraop 2023. 7. 28. 18:00

구현 목록

  • 게시판 조회
  • 게시글 작성
  • 게시글 조회
  • 게시글 권한 확인
  • 게시글 수정
  • 게시글 삭제

게시판 조회

 
// 게시판 조회
app.get("/api/posts", async (req, res) => {
  try {
    const posts = await Post.find();
    res.status(200).json({ message: `게시판 조회가 완료되었습니다.`, posts });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: "게시판 조회 작업 수행 중 문제가 발생하였습니다." });
  }
});
 
  • DB에서 게시판을 조회한 뒤 찾은 게시물을 반환합니다.

게시글 작성

 
// 게시글 작성
app.post("/api/protected/posts/create", async (req, res) => {
  try {
    const { title, content, isPrivate } = req.body;
    const { userId, nickname } = res.locals;

    // 작성될 게시글 정보
    const currentCounter = await Counter.find({ model: "Post" });
    const newPost = {
      postId: currentCounter[0].count + 1,
      userId,
      nickname,
      title,
      content,
      createdAt: convertToKrTime(new Date()), // 현재 시간을 한국 시간으로 변환하여 저장
      isPrivate,
    };

    const postResult = await Post.create(newPost);

    // count 값 증가 후, 현재 값을 가져옴
    await Counter.findOneAndUpdate(
      { model: "Post" },
      { $inc: { count: 1 } },
      { new: true, upsert: true }
    );

    // 게시글 작성 성공
    res.status(200).json({
      message: `게시글 (${title})의 작성이 완료되었습니다.`,
    });
  } catch (error) {
    res.status(500).json({ message: "게시글 작성 작업 수행 중 문제가 발생하였습니다.", result: [] });
  }
});
  • 게시글은 로그인을 한 이용자에게만 작성할 수 있도록 합니다. (protected를 포함한 api는 인증이 필요)

글 제목, 본문, 비밀글 여부를 요청받고,
userId와 nickname을 header로 전달받은 accessToken에서 가져옵니다.

// 게시글 작성
app.post("/api/protected/posts/create", async (req, res) => {
  try {
    const { title, content, isPrivate } = req.body;
    const { userId, nickname } = res.locals;

  • 게시물 작성 번호를 부여합니다. 여기서는 게시글마다 고유한 아이디를 필요로 하는데, MongoDB에서 자동으로 생성하는 아이디가 아닌, 게시물 작성 번호를 고유아이디로 하겠습니다.


필드명으로 콜렉션의 이름을 지정하고, 해당 필드에 count 값을 생성합니다.

const autoIncrementSchema = new mongoose.Schema({
  model: String, // Auto-increment를 적용할 대상 컬렉션의 이름
  field: String, // Auto-increment를 적용할 필드의 이름
  count: { type: Number, default: 0 }, // 현재까지 사용된 숫자 카운트
});
 
const Counter = mongoose.model("Counter", autoIncrementSchema);

게시글 번호를 포함하여 게시글을 DB에 저장합니다.

    // 작성될 게시글 정보
    const currentCounter = await Counter.find({ model: "Post" });

    const newPost = {
      postId: currentCounter[0].count + 1,
      userId,
      nickname,
      title,
      content,
      createdAt: convertToKrTime(new Date()), // 현재 시간을 한국 시간으로 변환하여 저장
      isPrivate,
    };

    const postResult = await Post.create(newPost);

다음 게시물에 부여될 번호를 생성하기 위해 +1 한 값을 post counter에 저장합니다.

    // count 값 증가 후, 현재 값을 가져옴
    await Counter.findOneAndUpdate(
      { model: "Post" },
      { $inc: { count: 1 } },
      { new: true, upsert: true }
    );

게시글 작성 완료를 response합니다.

    // 게시글 작성 성공
    res.status(200).json({
      message: `게시글 (${title})의 작성이 완료되었습니다.`,
    });
  }

게시글 조회

 
// 게시글 조회
app.get("/api/posts/:postId", async (req, res) => {
  try {
    const { postId } = req.params; // postId 값을 조회
    const posts = await Post.find({ postId });

    // 존재하지 않는 게시물인 경우
    if (!posts.length) {
      res.status(404).json({
        message: `게시글 조회 실패: postId - ${postId}`,
      });
    }
 
    res.status(200).json({
      message: `게시글 ${posts[0].title}(postId:${posts[0].postId})의 조회가 완료되었습니다.`,
      posts,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: "게시판 조회 작업 수행 중 문제가 발생하였습니다." });
  }
});
 

게시글 조회를 위해서 게시글의 ID를 요청받습니다.

params를 이용하여 게시물을 조회합니다.

// 게시글 조회
app.get("/api/posts/:postId", async (req, res) => {
  try {
    const { postId } = req.params; // postId 값을 조회
    const posts = await Post.find({ postId });

확인하려는 게시글 이미 삭제된 경우에는 조회가 불가능하다는 response를 보냅니다.

    // 존재하지 않는 게시물인 경우
    if (!posts.length) {
      res.status(404).json({
        message: `게시글 조회 실패: postId - ${postId}`,
      });
    }

 게시글의 조회에 문제가 없다면 완료를 response 합니다.

    res.status(200).json({
      message: `게시글 ${posts[0].title}(postId:${posts[0].postId})의 조회가 완료되었습니다.`,
      posts,
    });

게시글 수정(삭제) 권한 확인

 
// 게시글 수정 및 삭제 권한 확인
app.get("/api/protected/posts/:postId/edit/authorization", async (req, res) => {
  try {
    const { userId } = res.locals;
    const { postId } = req.params; // postId 값을 조회

    // 게시글 조회
    const requestedPost = await Post.findOne({ postId });

    // 게시글이 존재하지 않으면 오류 응답
    if (!requestedPost) {
      return res.status(404).json({ message: "수정할 게시글을 찾을 수 없습니다." });
    }

    // 사용자 인증 및 권한 검사
    if (requestedPost.userId !== userId) {
      return res.status(403).json({ message: "게시글을 수정할 권한이 없습니다." });
    }
 
    res.status(200).json({
      message: `게시글 ${requestedPost.title}(postId:${requestedPost.postId})의 수정 권한 확인이 완료되었습니다.`,
      post: requestedPost,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: "게시글 수정 작업 수행 중 문제가 발생하였습니다." });
  }
});
 

게시글의 수정에는 권한 확인이 필요합니다.

protected 경로를 포함한 요청을 보내도록 합니다.

// 게시글 수정 및 삭제 권한 확인
app.get("/api/protected/posts/:postId/edit/authorization", async (req, res) => {
  try {
    const { userId } = res.locals;
    const { postId } = req.params; // postId 값을 조회

게시글을 조회하여 존재하지 않는 게시물의 경우 response 합니다.

    // 게시글 조회
    const requestedPost = await Post.findOne({ postId });

    // 게시글이 존재하지 않으면 오류 응답
    if (!requestedPost) {
      return res.status(404).json({ message: "이미 삭제된 게시글입니다." });
    }

게시물의 작성자와 게시물의 id가 일치하는지 확인하여 일치하지 않는 경우를 response 합니다.

    // 사용자 인증 및 권한 검사
    if (requestedPost.userId !== userId) {
      return res.status(403).json({ message: "게시글을 수정할 권한이 없습니다." });
    }

권한 확인이 끝났다면 게시글 수정 권한 확인을 response 합니다.

    res.status(200).json({
      message: `게시글 ${requestedPost.title}(postId:${requestedPost.postId})의 수정 권한 확인이 완료되었습니다.`,
      post: requestedPost,
    });

 


게시글 수정

 
// 게시글 수정
app.put("/api/protected/posts/:postId/edit", async (req, res) => {
  try {
    const { title, content, isPrivate } = req.body;
    const { userId } = res.locals;
    const { postId } = req.params; // postId 값을 조회

    // 요청 데이터에 제목이 없는 경우
    if (!title) {
      return res.status(400).json({ status: "400-1", error: "제목을 입력해야 합니다." });
    }

    // 요청 데이터에 내용이 없는 경우
    if (!content) {
      return res.status(400).json({ status: "400-2", error: "본문을 입력해야 합니다." });
    }

    // 게시글 조회
    const requestedPost = await Post.findOne({ postId });

    // 게시글이 존재하지 않으면 오류 응답
    if (!requestedPost) {
      return res.status(404).json({ message: "수정할 게시글을 찾을 수 없습니다." });
    }

    // 사용자 인증 및 권한 검사
    if (requestedPost.userId !== userId) {
      return res.status(403).json({ message: "게시글을 수정할 권한이 없습니다." });
    }

    const updatedPost = await Post.findOneAndUpdate({ postId }, { title, content, isPrivate });
 
    res.status(200).json({
      message: `게시글 ${updatedPost.title}(postId:${updatedPost.postId})의 수정이 완료되었습니다.`,
      post: updatedPost,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: "게시글 수정 작업 수행 중 문제가 발생하였습니다." });
  }
});
 

protected 경로를 포함한 주소로 요청을 하여 게시글 수정 권한을 확인을 하고, 전달받은 데이터를 할당합니다.

// 게시글 수정
app.put("/api/protected/posts/:postId/edit", async (req, res) => {
  try {
    const { title, content, isPrivate } = req.body;
    const { userId } = res.locals;
    const { postId } = req.params; // postId 값을 조회

스트립트 조작으로 프론트에서 걸러내지 못하는 입력을 방지하기 위해 서버에서도 예외 처리를 해주어야 합니다.
게시물의 제목이나 본문이 없는 경우를 response 합니다.

    // 요청 데이터에 제목이 없는 경우
    if (!title) {
      return res.status(400).json({ status: "400-1", error: "제목을 입력해야 합니다." });
    }

    // 요청 데이터에 내용이 없는 경우
    if (!content) {
      return res.status(400).json({ status: "400-2", error: "본문을 입력해야 합니다." });
    }

수정할 게시글이 존재하지 않는 경우를 reponse 합니다.

    // 게시글 조회
    const requestedPost = await Post.findOne({ postId });

    // 게시글이 존재하지 않으면 오류 응답
    if (!requestedPost) {
      return res.status(404).json({ message: "수정할 게시글을 찾을 수 없습니다." });
    }

수정 권한을 검사하여 권한이 없는 경우를 response 합니다.

    // 사용자 인증 및 권한 검사
    if (requestedPost.userId !== userId) {
      return res.status(403).json({ message: "게시글을 수정할 권한이 없습니다." });
    }

게시글을 업데이트 합니다.

    const updatedPost = await Post.findOneAndUpdate({ postId }, { title, content, isPrivate });

    res.status(200).json({
      message: `게시글 ${updatedPost.title}(postId:${updatedPost.postId})의 수정이 완료되었습니다.`,
      post: updatedPost,
    });

게시글 삭제

 
// 게시글 삭제
app.delete("/api/protected/posts/:postId/delete", async (req, res) => {
  try {
    const { userId } = res.locals;
    const { postId } = req.params; // postId 값을 조회

    // 게시글 조회
    const requestedPost = await Post.findOne({ postId });

    // 게시글이 존재하지 않으면 오류 응답
    if (!requestedPost) {
      return res.status(404).json({ message: "이미 삭제된 게시글입니다." });
    }

    // 사용자 인증 및 권한 검사
    if (requestedPost.userId !== userId) {
      return res.status(403).json({ message: "게시글을 삭제할 권한이 없습니다." });
    }

    // 게시글 삭제
    const deletedPost = await Post.findOneAndDelete({ postId });
 
    res.status(200).json({
      message: `게시글 ${deletedPost.title}(postId:${deletedPost.postId})의 삭제가 완료되었습니다.`,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: "게시글 삭제 작업 수행 중 문제가 발생하였습니다." });
  }
});
 

게시글 삭제 또한 수정 과정과 비슷합니다.

단지 이번에는 삭제를 한다는 것에 차이가 있습니다.

    // 게시글 삭제
    const deletedPost = await Post.findOneAndDelete({ postId });

    res.status(200).json({
      message: `게시글 ${deletedPost.title}(postId:${deletedPost.postId})의 삭제가 완료되었습니다.`,
    });

'데이터시각화-KMG > Node.js' 카테고리의 다른 글

[Node.js] 회원 기능 구현하기  (0) 2023.07.24