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

allPosts의 새 게시물 버튼을 누르면 게시물을 추가 할 수 있도록 한다. 이 부분에 링크를 만들어 주자.
<div class = "admin-title">
<h2><%= locals.title %></h2>
<a href="/add" class = "button">+ 새 게시물</a>
</div>
이제 새로 만든 add 경로를 처리하는 라우트 코드를 작성하자.
우선 새 게시물 작성을 위한 ejs 파일이 필요하다. admin 폴더 안에 add.ejs 파일을 만들자.
<a href="/allPosts">← 뒤로</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 - 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
const methodoverride = require("method-override");
모듈을 가져오고
app.use(methodoverride("_method"));
미들웨어로 등록하자.
이제 [편집] 버튼을 눌렀을 때 해당 게시물 내용을 가지고 와서 브라우저 창에 보여주는 ejs 파일을 만들 것이다. admin 폴더안에 edit.ejs 파일을 만들자.
<a href="/allPosts">← 뒤로</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 - 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에서 링크를 수정하자.
<a href="/edit/<%= post._id %>" class="btn">편집</a>
게시물 옆의 편집을 클릭하면?

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

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

이렇게 잘 수정된 것이 보인다.
이제 삭제를 한번 만들어 보자. 이 삭제 버튼은

여기에도 있고

여기에도 있다. 이 두 군데에서 모두 동작하도록 하겠다.
form에서는 delete 요청을 처리하지 못하므로 method-override 모듈을 사용해야 한다.
<form action="/delete/<%= post._id %>?_method=DELETE" method="POST">
<input type="submit" value="삭제" class="btn-delete btn">
</form>
<form action="/delete/<%= data._id %>?_method=DELETE" method="POST">
<input type="submit" value="삭제" class="btn btn-delete">
</form>
이렇게 하면 삭제 버튼을 위한 링크는 모두 수정되었다.
이제 delete 요청 방식을 처리하는 라우트 코드만 작성해주면 된다.
router.delete("/delete/:id", checkLogin, asyncHandler(async(req,res)=>{
await Post.deleteOne({_id: req.params.id});
res.redirect("/allPosts");
}))
테스트용 게시물을 삭제해보자.

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

삭제를 누르면?

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