글 작성자: 망고좋아
반응형

🎯 게시판 CRUD 만들기

📝 모델 선언하기

🛠 /models/schemas/post.js

const mongoose = require("mongoose");
const { Schema } = require("mongoose");
const shortId = require("./types/short-id");

module.exports = new Schema(
  {
    shortId,
    title: String,
    content: String,
    author: String,
  },
  {
    timestamps: true,
  }
);

 

🛠 /models/index.js

exports.Post = mongoose.model("Post", PostSchema);
  • MongoDB의 ObjectID는 URL에 사용하기 좋은 값이 아니기 때문에 대체할 수 있는 아이디를 shortId로 생성해준다.
  • 제목, 내용, 작성자를 String 타입으로 스키마에 선언 (회원가입 로그인 후 작성자 연동으로 변경 예정)
  • timestamps 옵션으로 작성 시간, 수정 시간을 자동으로 기록해 준다.

 

📝 shortId

const { nanoid } = require("nanoid");
const shortId = {
  type: String,
  default: () => { // shortId의 값이 전달되지 않았을 때 자동으로 함수를 실행해서 nanoid를 생성해서 shortId를 default으로 지정
    return nanoid();
  },
  require: true,
  index: true,
};
module.exports = shortId;
  • ObjectId를 대체할 shortId 타입을 Mongoose Custom Type으로 선언해준다.
  • 중복 없는 문자열을 생성해 주는 nanoid 패키지 활용
  • default를 이용해 모델 생성 시 자동으로 ObjectId를 대체할 아이디 생성해준다.

 

📝 게시글 작성

📕 게시글 작성 흐름

  1. /posts?write=true 로 작성 페이지 접근
  2. <form action="/posts" method="post"> 이용해 post 요청 전송
  3. router.post 이용하여 post 요청 처리
  4. res.redirect 이용하여 post 완료 처리

 

📕 작성 페이지 만들기

🛠 /routes/posts.js

const { Router } = require("express");

const router = Router();

router.get("/", (req, res, next) => {
  if (req.query.write) {
    res.render("posts/edit"); //req.query.write값이 있다면 posts/edit 템플릿으로 이동
    return;
  }
  ....
});

....
module.exports = router;

 

🛠 /views/posts/edit.pug

form(action="/posts", method="post")
    table
        tbody
            tr
                th 제목
                td: input(type="text" name="title")
            tr
                th 제목
                td: textarea(name="content")
            td
                td(colspan="2")
                    input(type="submit" value="등록")

 

📕 POST 요청 처리하기

const { Post } = require("./models");
...

router.post("/", async (req, res, next) => {
  const { title, content } = req.body;
  try {
        // create 생성
    await Post.create({
      title,
      content,
      author: "DUMMY", // set dummy author before add users
    });
    res.redirect("/"); // 게시글 작성이 완료되면 root path로 이동, posts/post:shortid로 전달하면 작성한 게시글로 이동
  } catch (err) {
    next(err);
  }
});

...

 

📝 게시글 목록 및 상세

📕 게시글 목록 및 상세 흐름

  1. /posts 로 목록 페이지 접근
  2. <a href="/posts/:shortId"> 이용하여 상세 URL Link
  3. router.get('/:shortId') path parameter 이용하여 요청 처리

 

📕 게시글 목록 구현하기

🛠 /routes/posts.js

router.get("/", async (req, res, next) => { // posts  경로로 접근했을 때
  const posts = await Post.find({}); // 작성된 모든 글 가져오기
  res.render("posts/list", { posts }); //posts/list에 posts 값 전달
});

 

🛠 /view/posts/list.pug

...
table
    tbody
        each post in posts
            tr
                td
                    a(href=`/posts/${post.shortId}`) = post.title // 경로설정
                td= post.author
                td= formatDate(post.createdAt) // formatDate는 따로 추가한 함수이다.
    tfoot
        tr
            td(colspan="3")
                a(href="/posts?write=true") 등록하기

 

📕 formatDate 함수 추가하기

🛠 app.js

const dayjs = require("dayjs");

app.locals.formatDate = (date) => {
  return dayjs(date).format("YYYY-MM-DD HH:mm:ss"); // 특정 포맷으로 변환
};

 

📕 게시글 상세 구현하기

🛠 /routes/posts.js

router.get("/:shortId", async (req, res, next) => {
  const { shortId } = req.params;
  const post = await Post.findOne({ shortId }); // 게시글 검색
  if (!post) {
    next(new Error('Post NotFound');
    return;
  }
  ...
  res.render("posts/view", { post });
});

 

🛠 /views/posts/view.pug

...
table
    tbody
        tr
            td(colspan="2")= post.title
        tr
            td= post.author
            td= formatDate(post.createdAt)
        tr
            td(colspan="2"): pre= post.content
        tr
            td: a(href=`/posts/${post.shortId}?edit=true`) 수정
            td button(onclick=`deletePost("${post.shortId")`) 삭제

 

📝 게시글 수정

📕 게시글 수정 흐름

  1. /posts/{shortId}?edit=true로 수정 페이지 접근
  2. 작성 페이지를 수정 페이지로도 동작하도록 작성
  3. <form action="/posts/:shortId" method="post">로 post 요청 전송
    • html form은 PUT method를 지원하지 않기 때문에 post 사용

 

📕 수정 페이지 만들기

🛠 /routes/posts.js

router.get("/:shortId", async (req, res, next) => {
  ...
  if (req.query.edit) { // query가 edit으로 들어왔을 때
    res.render("posts/edit", { post }); // 아까 상세 페이지에서 찾았던 post값 넘겨주기
  }
  ...
});

 

🛠 /view/posts/edit.pug

...
- var action = post ? `/posts/${post.shortId}` : "/posts"
    form(action=action, method="post")
        table
            tbody
                tr
                    th 제목
                    td: input(type="text" name="title" value=post&&post.title)
                tr
                    th 제목
                    td: textarea(name="content")= post&&post.content
                td
                    td(colspan="2")
                        - var value = post ? "수정" : "등록"
                        input(type="submit" value=value)

 

📕 수정 요청 처리하기

...
router.post('/:shortId', async (req, res, next) => {
  const { shortId } = req.params;
    const { title, content } = req.body;
  const post = await Post.findOneAndUpdate({ shortId }, {
        title, content,
    }); // shortId 검색해서 내용을 title, content로 변경하겠다는 의미 
  if (!post) {
      next(new Error('Post NotFound');
      return;
  }
    res.redirect(`/posts/${shortId}`);
});

 

📝 게시글 삭제

📕 게시글 삭제 흐름

  1. 게시글 상세 페이지에 삭제 버튼 추가
  2. html form은 DELETE 메서드를 지원하지 않음
  3. JavaScript를 이용해 fetch 함수로 HTTP Delete 요청 전송
  4. router.delete의 응답을 fetch에서 처리

 

📕 HTTP Delete 요청 전송 및 응답 처리

🛠 /posts/view.pug

td
    button.delete(
    onclick=`deletePost("${post.shortId}")`) 삭제 
...
...
script(type="text/javascript").
    function deletePost(shortId) {
        fetch('/posts/' + shortId, { method: 'delete' })
            .then((res) => {
                if (res.ok) {
                    alert('삭제되었습니다.');
                    window.location.href = '/posts';
                } else {
                    alert('오류가 발생했습니다.');
                    console.log(res.statusText);
                }
            })
            .catch((err) => {
                console.log(err);
                alert('오류가 발생했습니다.');
        })
}

 

📕 DELETE 요청 처리하기

🛠 /routes/posts.js

const { Post } = require("./models");
...
router.delete("/:shortId", async (req, res, next) => {
  const { shortId } = req.params;
  try {
    await Post.delete({ shortId });
    res.send("OK");
  } catch (err) {
    next(err);
  }
});
...
반응형