31강. 블로그 앱 만들기 - CRUD (完)

한시현·2024년 4월 13일

UDR 백엔드 야생형

목록 보기
15/15

Section 4.

31강. 블로그 앱 만들기 - CRUD

CRUD

관리자 권한으로 로그인했을 때 게시물을 추가, 수정, 삭제하는 방법에 대해 알아보자.
백엔드 프로그래밍에서 중요한 CRUD API를 만드는 것이다.

필요한 기능

게시물 추가하기

allPosts의 새 게시물 버튼을 누르면 게시물을 추가 할 수 있도록 한다. 이 부분에 링크를 만들어 주자.

allPost 수정

<div class = "admin-title">
    <h2><%= locals.title %></h2>
    <a href="/add" class = "button">+ 새 게시물</a>
</div>

이제 새로 만든 add 경로를 처리하는 라우트 코드를 작성하자.

우선 새 게시물 작성을 위한 ejs 파일이 필요하다. admin 폴더 안에 add.ejs 파일을 만들자.

add.ejs

<a href="/allPosts">&larr; 뒤로</a>
<div class="admin-title" %>
    <h2><%= locals.title %></h2>
</div>

<form action="/add" method="POST">
    <label for="title"><b>제목</b></label>
    <input type="text" placeholder="게시물 제목" name="title" id="title">

    <label for="body"><b>내용</b></label>
    <textarea name="body" id="body" cols="50" rows="10" placeholder="게시물 내용"></textarea>

    <input type="submit" value="등록" class="btn">
</form>

뒤로를 눌렀을때 전체 게시물 목록으로 넘어가게 링크를 만들었다.

이제 요청을 받아서 ejs 파일로 보여주거나 db에 추가하는 라우트 코드를 작성할 차례이다.

admin.js

// Admin - Add Post
// GET /add
router.get("/add", asyncHandler(async(req,res)=>{
    const locals = {
        title: "게시물 작성"
    }
    res.render("admin/add", {locals, layout: adminLayout})
}))

이렇게 작성하면 add라는 경로로 get 요청이 들어왔을 때 add.ejs 파일을 렌더링 해준다.

// Admin - Add Post
// POST /add
router.post("/add", asyncHandler(async(req,res)=>{
    const {title,body} = req.body;
    const newPost = new Post({
        title: title,
        body: body
    });

    await Post.create(newPost);
    res.redirect("/allPosts");
}))

이건 post 요청 방식에 대한 라우트 코드이다.

실행 결과

새 게시물을 클릭하면 다음과 같은 페이지로 이동한다. 뒤로를 누르면 전체 게시물 페이지로 이동한다.

여기에 위와 같은 내용을 입력하고 등록을 눌러보자.

맨 끝에 우리가 등록한 내용이 추가된 것을 볼 수 있다.

여기서 우리가 하나 짚고 넘어가야 하는 것이 있다.
경로를 보면 뒤에 /add를 붙여서 게시물 작성 페이지로 갈 수 있는데, 이는 원래 관리자 로그인을 한 사람만 사용할 수 있는 기능이다. 그러나 로그인 하지 않은 상태로도 저 경로를 사용하면 게시물 작성을 할 수 있게 되어 버린다.

그래서 중간에 이 경로로 요청을 하게 되면 관리자로 로그인 한 상태인지 아닌지 체크해주는 기능이 필요하다. 관리자가 아니라면 이 경로를 알고 있다 하더라도 해당 기능을 사용할 수 없게 만들어야 한다.

그래서 필요한 것이 로그인을 체크해주는 미들웨어이다.

로그인 확인하기

미들웨어를 별도의 함수로 만들어서 app.js에다가 이를 미들웨어로 사용하겠다고 등록해도 되고, 체크하는 코드가 짧기 때문에 라우트 코드 안에 직접 정의해 줘도 된다.

// Chect Login
const checkLogin=(req,res,next)=>{
    const token = req.cookies.token;
    if (!token){
        res.redirect("/admin");
    }
    else {
        try{
            const decoded = jwt.verify(token, jwtSecret)
            req.userId = decoded.userId;
            next(); // 관리자라면 next를 처리
        } catch(error){
            res.redirect("/admin");
        }
    }
}

해당 함수에서 할 일은 토큰이 있는지 없는지를 먼저 체크하고, 토큰이 없으면 로그인 할 수 있는 창으로 보내고, 토큰이 있다면 내가 발행한 토큰인지 확인(verify 함수 이용)하는 것이다.
이렇게 만든 함수는 관리자 기능과 관련된 라우트 코드를 작성할 때 마다 포함해서 체크 해 줘야 한다.

먼저 allPosts 라우트 코드로 이동해보자.

router.get("/allPosts", checkLogin, asyncHandler(async(req,res)=>{

allPosts는 db에서 자료를 가지고 와서 보여주는 관리자만이 접근할 수 있는 기능으로, 첫 줄에 checkLogin을 삽입해서 이를 먼저 실행하도록 하자.
checkLogin에서 통과되면 try문 안의 next()가 바로 여기서 실행된다.

이 checkLogin은 add에도 필요하다.

router.get("/add", checkLogin, asyncHandler(async(req,res)=>{

post도 역시 필요하다.

router.post("/add", checkLogin, asyncHandler(async(req,res)=>{

이렇게 하면 관리자가 아닌 사람이 이 경로를 알고 있다 하더라도 중간에 들어와서 해당 기능을 사용할 수 없다. 한번 확인해보자.

실행 결과

로그인을 하지 않은 상태로 /add를 입력하면 로그인 창으로 이동하는 모습이다.

앞으로 나오는 편집, 삭제 기능에도 checkLogin을 이용해 보겠다.

편집

다음으로는 [편집]버튼을 눌렀을 때 해당 내용을 가져와 브라우저에 보여주고, 수정 내용을 입력해 [수정하기]를 누르면 db의 내용을 수정하도록 하겠다. 즉, put 요청을 처리를 해야 한다.
다만, form 에서 put 요청은 처리할 수 없다. 그렇기 때문에 method-override 모듈을 사용해서 put 요청으로 바꿔서 전송하도록 하겠다.

모듈 설치: method-override

npm i method-override

app.js

const methodoverride = require("method-override");

모듈을 가져오고

app.use(methodoverride("_method"));

미들웨어로 등록하자.

이제 [편집] 버튼을 눌렀을 때 해당 게시물 내용을 가지고 와서 브라우저 창에 보여주는 ejs 파일을 만들 것이다. admin 폴더안에 edit.ejs 파일을 만들자.

edit.ejs

<a href="/allPosts">&larr; 뒤로</a>
<div class="admin-title" %>
    <h2><%= locals.title %></h2>
    <form>
        <input type="submit" value="삭제" class="btn btn-delete">
    </form>
</div>

<form action="/edit/<%= data._id %>?_method=PUT" method="POST">
    <label for="title"><b>제목</b></label>
    <input type="text" name="title" id="title" value="<%= data.title %>">
    <label for="body"><b>내용</b></label>
    <textarea name="body" id="body" cols="50" rows="10"">
        <%= data.body %>
    </textarea>

    <input type="submit" value="수정" class="btn">
</form>

여기서 기억해 둘 것은 라우트 코드에서 서버에서 가져온 게시물 내용을 data 변수로 받는다는 것이다.

admin.js

// Admin - Edit Post
// GET /edit/:id
router.get("/edit/:id", checkLogin, asyncHandler(async(req,res)=>{
    const locals = {title: "게시물 편집"};
    const data = await Post.findOne({_id: req.params.id});
    res.render("admin/edit", {locals, data, layout: adminLayout});
}))

edit 경로로 id를 함께 넘겨 주었을 경우에 게시물을 수정하는 라우트 코드이다.
put도 작성해보자.

// Admin - Edit Post
// PUT /edit/:id
router.put("/edit/:id", checkLogin, asyncHandler(async(req,res)=>{
    await Post.findByIdAndUpdate(req.params.id,{
        title: req.body.title,
        body: req.body.body,
        createdAt: Date.now()
    })
    res.redirect("/allPosts");
}))

findByIdAndUpdate 함수는 id값을 이용해서 데이터를 찾고 그 정보를 수정해주는 것을 한꺼번에 해준다. req.params안에 들어있는 id를 사용해서 게시물을 찾는다. 또 현재 시간, 날짜를 createdAt에 넣으라고 지정했다. 수정이 끝나면 전체 게시물 화면으로 이동한다.

이렇게 작성했다면 마지막으로 allPost.ejs에서 링크를 수정하자.

allPost.ejs

<a href="/edit/<%= post._id %>" class="btn">편집</a>

실행 결과

게시물 옆의 편집을 클릭하면?

이렇게 나타난다. 한번 수정해보자.

이렇게 입력하고 수정을 누르면

이렇게 잘 수정된 것이 보인다.

삭제

이제 삭제를 한번 만들어 보자. 이 삭제 버튼은

여기에도 있고

여기에도 있다. 이 두 군데에서 모두 동작하도록 하겠다.
form에서는 delete 요청을 처리하지 못하므로 method-override 모듈을 사용해야 한다.

allPost.ejs 수정

<form action="/delete/<%= post._id %>?_method=DELETE" method="POST">
    <input type="submit" value="삭제" class="btn-delete btn">
</form>

edit.ejs 수정

<form action="/delete/<%= data._id %>?_method=DELETE" method="POST">
    <input type="submit" value="삭제" class="btn btn-delete">
</form>

이렇게 하면 삭제 버튼을 위한 링크는 모두 수정되었다.
이제 delete 요청 방식을 처리하는 라우트 코드만 작성해주면 된다.

admin.js

router.delete("/delete/:id", checkLogin, asyncHandler(async(req,res)=>{
    await Post.deleteOne({_id: req.params.id});
    res.redirect("/allPosts");
}))

실행 결과

테스트용 게시물을 삭제해보자.

게시물이 잘 삭제된 것을 확인할 수 있다.
이번에는 편집을 누르고 삭제해보자.

삭제를 누르면?

역시 게시물이 잘 삭제된다.

이렇게 CRUD, 블로그 애플리케이션을 만들 때 필요한 4가지 기능에 대해서 라우트 코드를 작성해 보았다.

0개의 댓글