EJS 파일을 이용해서 연락처를 추가해보자.
처리할 내용
- 전체 연락처 화면에서 [연락처 추가] 버튼 클릭 -> 추가 화면 보여주기
- 추가 화면에서 정보 입력하고 [저장하기] 버튼 클릭 -> 데이터베이스에 연락처 추가
연락처 추가, 수정 시 사용자가 입력하기 쉽도록 form을 이용 할 것이고, 그 폼이 ejs 파일에 들어있다.
연락처 추가를 누르면 contacts경로에 add 요청을 보내겠다.
그리고 연락처 추가하는 화면 add.ejs 파일을 화면에 보여준다.
그 후 화면에서 [저장하기]를 누르면 바로 POST 요청이 가게 될 것이다.
이제 컨트롤러를 한번 수정해보자.
새로운 연락처를 추가하기 위해 add.ejs 파일, 추가하는 form을 화면에 보여 줄 것이다. 이때 사용하는 방식은 GET이 된고, 요청할 경로는 contacts 라는 경로 밑에서 add라고 요청을 했을 때이다.
// View add Contact form
// Get /contacts/add
const addContactForm = (req, res) => {
res.render("add"); // 수정한 부분
}
이렇게 함수를 완성이 되었고, 이 파일을 모듈로 내보내야 다른 곳에서 쓸 수 있을 것이다.
module.exports = {
getAllContacts,
createContact,
getContact,
updateContact,
deleteContact,
addContactForm // 추가!
};
이렇게 해서 컨트롤러가 수정되었다. 이젠 라우트도 추가 해 줄 차례다.
그 경로로 들어왔을 때 addContactForm 을 실행하라고 알려주겠다.
const express = require("express");
const router = express.Router();
const {
getAllContacts,
createContact,
getContact,
updateContact,
deleteContact,
addContactForm, // 함수 추가
} = require("../controllers/contactController");
router.route("/").get(getAllContacts).post(createContact);
router.route("/:id").get(getContact).put(updateContact).delete(deleteContact);
router.route("/add").get(addContactForm); // 경로 추가
module.exports = router;
이제 제대로 동작하는지 확인해보자.

다음과 같은 화면이 나온다. (강의 영상에서 나온 예시와는 다르게 나온다. 왜일까?)
이 화면에서 [연락처 목록] 버튼을 클릭하면 전체 목록을 /contacts 경로 요청 화면으로 이동해야 하고, 전체 목록 화면에서 [연락처 추가] 버튼을 클릭하면 다시 뒤에 /add가 붙어 요청할 수 있도록 링크를 바꿔보자.
<a href="/contacts/add" class="btn btn-light"><i class="fa-solid fa-user-plus"></i>연락처 추가</a>
[연락처 추가] 버튼을 눌렀을 때 추가 화면으로 이동하도록 # 대신 /contacts/add 요청 경로를 변경했다.
<a href="/contacts" class="btn btn-light"><i class="fa-solid fa-list"></i>연락처 목록</a>
마찬가지로 [연락처 목록]을 눌렀을 때 전체 목록 화면으로 이동하도록 #이라는 링크를 없애고 /contacts라고 요청 경로를 변경했다.
이제 웹 브라우저에서 확인해보면 해당 버튼을 누를 때 화면이 잘 변경되는 것을 확인할 수 있을 것이다.
이제 연락처 추가 화면에서 정보를 입력한 다음 저장하기를 누르면 post 요청이 발생하며 db에 해당 내용이 추가되어야 한다.
이때 주의할 것은, 요청 경로가 contacts 경로 밑에 있는 add 경로를 사용한다는 것이다.
우리가 만든 컨트롤러, 라우트 코드를 보면 post가 contacts 경로로 연결되게 되어 있는데 이를 add 경로로 요청했을 때 처리되도록 변경해야 한다.
router.route("/").get(getAllContacts);
router.route("/add").get(addContactForm).post(createContact); // createContact를 add 경로로 변경
router.route("/:id").get(getContact).put(updateContact).delete(deleteContact);
<form method="POST" id="add-user">
add.ejs를 보면 form이 있는데, form에서 안에 있는 내용을 서버로 보낼 때 method라는 속성을 이용해서 post인지 아닌지를 지정하게 된다. 여기는 form을 post 방식으로 보내달라고 작성되어 있다.
아무튼 post로 보내는데, 얘를 어디로 보내야 될까? form에다 처리해야 될 함수 프로그램을 연결할 때는 action이라는 속성을 사용한다. 이 action 다음에 처리해야 될 프로그램을 연결 해주는데, 우리는 컨트롤러 함수를 연결해 줄 것이다.
<form action="/contacts/add" method="POST" id="add-user">
form에 있는 내용을 post 방식으로 보낼 것이다. 이 post 요청 방식을 처리하는 경로는 /contacts/add 이다 라는 뜻이 된다.

이제 브라우저를 실행 후 이렇게 내용을 입력한 다음 [저장하기]를 눌러보자.

이렇게 Create Contacts 라고 나온다. 컨트롤러 함수에 지정한대로 나온것을 보니 제대로 동작하는 것 같다.
제대로 동작했다면 db에 저장이 되어야 한다. db를 한번 확인해보자.

이렇게 몽고 db를 확인해보면 db에 새로 입력한 정보가 추가되었다는 것을 확인할 수 있다.
템플릿 파일을 이용해 폼에서 사용자 정보를 받고, [저장하기] 버튼을 눌렀을 때 사용자가 입력한 정보를 db에 추가하는 것까지 해 보았다.
이번에는 db에 있는 연락처를 수정하는 방법에 대해 알아보자.

연필 아이콘을 클릭하면 그 아이콘이 있는 해당 정보를 가지고 와서 연락처 수정 화면을 보여줘야 한다. 연락처 수정 화면은 GET 방식을 이용해서 가져와야 할 것이다.
이때 경로는 contacts 뒤에 id 값이 들어가 줘야 한다. id 값은 아이콘이 있는 줄에서 contacts에 해당하는 id 값을 가져오면 된다.
우선 컨트롤러 함수를 수정하겠다. 수정할 함수는 getContact 이다.
// Get contact
// GET /contacts/:id
const getContact = asyncHandeler(async(req,res)=>{
const contact = await Contact.findById(req.params.id);
res.render("update", { contact: contact }); // 수정한 부분
});
render 함수를 이용해서 렌더링 해주자. update.ejs 파일을 보여주고, db에서 가져온 contact를 넘겨준다.
이제 update.ejs 파일을 수정해보자.
텍스트 필드의 value 속성을 보면 임시 값이 들어가 있을 텐데, 그 임시 값 대신 넘겨받은 contact라는 정보의 값을 표시해야 한다. update.ejs에서 해당 부분을 다음 코드들로 수정하자.
<input type="text" class="form-control" name="name" id="name" value="<%= contact.name %>">
<input type="text" class="form-control" name="email" id="email" value="<%= contact.email %>">
<input type="text" class="form-control" name="phone" id="phone" value="<%= contact.phone %>">
이 update.ejs 파일은 연락처 관리 페이지에서 연필 아이콘을 클릭했을 때 나타나야 한다. 그렇기 때문에 연필 아이콘을 눌렀을 때 update.ejs 파일로 링크할 수 있도록 index.ejs에서 링크를 수정해 줘야 한다.
<tbody>
<% contacts.forEach (contact => { %>
<tr>
<td><%= contact.name %></td>
<td><%= contact.email %></td>
<td><%= contact.phone %></td>
<td>
<a href="/contacts/<%= contact._id %>" class="btn update" title="수정"> // 수정된 부분
<i class="fas fa-pencil-alt"></i>
</a>
<a href="#" class="btn delete" title="삭제">
<i class="fas fa-times"></i>
</a>
</td>
</tr>
<% }) %>
</tbody>
contacts라는 경로 뒤에 id 값이 들어가는데, 해당 정보의 id는 넘겨받은 contact에 있는 id이다. 그 정보의 id 값을 이용해서 db에서 값을 가져와달라는 뜻이다.

이제 연필 아이콘을 누르면 연락처 수정 페이지로 이동하고 그에 해당하는 정보가 나온다.(화면은 여전히 이상하게 출력된다. 대체 왜일까)
이제 연락처 수정 화면 즉, update.ejs에서 [연락처 목록]을 클릭했을 때 앞으로 돌아가는 기능을 추가하자.
<a href="/contacts" class="btn btn-light"><i class="fa-solid fa-list"></i>연락처 목록</a> // 경로 수정
이제 우리는 연락처 수정 페이지에서 연락처를 수정하고 [수정하기]를 클릭했을 때 POST 요청이 아닌 PUT 요청을 보내는 방법에 대해 알아보자.
update.ejs의 form 에서 method가 POST로 지정되어 있다. form이라는 요소에서는 사용할 수 있는 요청 방식이 GET과 POST 밖에 없다. 즉, PUT과 DELETE는 form에서 처리 해 주지 못한다.
이 PUT 과 DELETE를 처리하기 위해서 Ajax라는 기법을 사용할 수도 있고, method-override라고 하는 모듈을 설치해서 사용할 수도 있다.
Ajax : 서버로 요청을 보낼 때 정보 안에다 요청 방식을 담아서 보낼 수 있다.
method-override 모듈 : method를 덮어 쓸 수 있다.
다만, method-override가 좀 더 수월하기 때문에 우리는 이 방식을 써볼 것이다.
POST라는 요청을 작성해놨지만, 이를 서버로 보낼 때는 이를 PUT이라는 방식으로 바꿔서 보내달라고 지정을 해보겠다.
이를 위해 모듈을 하나 설치 해 줘야 한다.
모듈 설치
npm i method-override
패키지를 설치 했으니 method-override를 사용한다고 애플리케이션에게 알려줘야 한다.
애플리케이션 전체에게 알려 줄 때는 app.js에다가 미들웨어 형태로 등록해야 한다.
const methodOverride = require("method-override");
app.use(methodOverride("_method"))
app.js에다가 두 코드를 추가하자.
이 때 methodOverride를 어떤 식으로 사용할 지 지정하면 된다.
이제 update.ejs로 가서 원래 POST로 되어 있던 부분을 수정할 것이다.
<form method="POST" id="add-user">
이 부분이다.
이 form을 어딘가로 넘겨서 처리하도록 해야 하는데 어디로 넘길 것인지 지정하는 속성이 action 속성이다. 수정 정보를 서버로 넘겨 주기 위해서는 contact와 id 값을 사용한다. 이 값들을 경로로 넣어 주면 된다.
또한 현재 있는 POST를 덮어 써 주기 위해서 입력한 경로 뒤에 ?를 붙인다.
어떤 방식으로 사용할 것인지는 _method, 그리고 우리가 바꾸려고 하는 방식은 PUT이다.
<form action="/contacts/<%= contact._id %>?_method=PUT" method="POST" id="add-user">
이렇게 수정했다면 잘 작동되는지 확인해보자.

이렇게 이름을 바꾸고 수정하기를 누르면

이름이 수정된 것을 볼 수 있다.
이 부분이 컨트롤러에서 PUT에 해당하는 부분이다.이것을 브라우저 창에 보여달라고 했기 때문에 브라우저 창에 json형태로 나타났는데 이 형태가 아니라 수정이 끝나면 다시 전체 연락처 목록을 보여 주라고 해 보자.
res.json(contact);
이렇게 되어 있던 부분을
res.redirect("/contacts");
이렇게 변경하자.
이제 다시 실행해보자.

실행 해보면 이렇게 정보가 제대로 수정된 것을 확인할 수 있다.
이번에는 수정하자마자 contacts 목록이 나타나는지 확인해보자.

이렇게 바꾸고 수정하기를 누르면?

수정한 내용이 바로 나타난다. redirect를 이용한 덕분이다.
이번에는 목록에서 특정 항목을 삭제 해보자.
X 버튼을 눌렀을 때 DELETE 방식으로 서버로 요청을 보내게 된다. DELETE 역시 특정한 정보만 삭제해야 하기 때문에 요청 경로는 contacts 경로 뒤에 id를 붙여 줘야 한다.
컨트롤러 함수로 가서 우리가 만들어둔 delete를 보도록 하자.
const deleteContact = asyncHandeler(async(req,res)=>{
const id = req.params.id;
const contact = await Contact.findById(id);
if (!contact){
throw new Error("Contact not found.");
}
await Contact.deleteOne();
res.send("Deleted");
})
이 코드를 db에 있는 정보를 삭제하도록 수정해보자.
findByIdAndDelete를 쓰면 이 함수는 그 id에 해당하는 정보를 찾아서 삭제하는 것까지 한꺼번에 처리해준다. 그리고 삭제 후 바로 연락처 목록을 보여줄 수 있도록 하겠다.
const deleteContact = asyncHandeler(async(req,res)=>{
const id = req.params.id;
await Contact.findByIdAndDelete(id);
res.redirect("/contacts");
})
이제 index.ejs로 가서 삭제 버튼을 눌렀을 때 그 id에 해당하는 정보가 삭제되도록 해야 하는데 <a> 태그에서는 요청을 보낼 수가 없다. 웹 문서에서 서버로 어떤 요청을 보내야 할 때는 form을 이용해야 한다.
<a href="#" class="btn delete" title="삭제">
<i class="fas fa-times"></i>
</a>
이 부분을 form 태그로 바꾸고, form에서 method를 DELETE로 바꾸도록 코드를 수정하자.
<form action="/contacts/<%= contact._id %>?_method=DELETE" method="POST" style="display:inline">
<input type="submit" class="btn delete" title="삭제" value="X">
</form>
form을 이용해서 버튼을 하나 넣었고, submit 이라는 것은 form 안에 있는 정보를 서버로 보내라는 뜻이다. action은 뭐가 되냐면 id를 이용해 서버로 넘겨 줄 것이다. 여기서 하려는 것은 method 값이 POST도 GET도 아닌 DELETE여야 한다. form의 method는 그냥 기본 값인 post로 넣어 놓겠다.
이렇게 하면 form 안의 x값을 클릭 했을 때 그 정보를 서버로 넘겨 주게 된다.
이제 테스트해보자. 브라우저 창에서 첫 번째 사용자에서 X를 눌러보면?

바로 사라진다. 누르는 순간 서버로 가서 해당 정보를 삭제하고 다시 전체 정보를 가져와 index.ejs에 표시해 준 것이다.
몽고 db에서도 확인해보자.

Documents가 2개 남은 것을 볼 수 있다.
이렇게 연락처 관리 앱을 만들면서 필요한 create, read, update, delete 4가지 방식을 어떻게 작성하는지에 대해 알아보았다.
분명 강의와 같은 연락처 추가 화면을 나타내는 add.ejs 코드를 사용 했음에도 강의에서 출력된 화면과 내 화면이 다르다.




헤더도 똑같이 적용된 것 같은데 어째서일까.
add.ejs 뿐만 아니라, update.ejs와 메인 페이지인 localhost:3000/contacts에 /를 붙여도 이상하게 출력되고 멀쩡하게 나오는건 index.ejs 밖에 없다.
문제 해결
<link rel="stylesheet" href="css/style.css">이 코드를 반드시
<link rel="stylesheet" href="/css/style.css">이렇게 바꾸자.