Sequelize로 CRUD 수행하기 (Request Method)
✒️ 2025-05-26 15:07 내용 수정
사전 설정
- Sequelize로 CRUD 수행하기 (Rest Client)에서 진행한 내용에서 server.js에 있는 메소드를 수정하여 진행한다.
- Rest Client와 test.http 파일 없이 웹 페이지를 통해 데이터를 주고 받는다.
- server.js의 설정 상태는 Sequelize로 CRUD 수행하기 (Rest Client)#사전 설정 내용과 동일하다.
// 1. require
const express = require('express');
const app = express();
//db
const db = require('./models/index');
//const employee = require('./models/employee'); // 이 부분은 주석해도 무관
const { Employee } = db;
// 2. use, set
app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/public/js'));
app.set('view engine', 'ejs');
app.use(express.json());
app.use(express.urlencoded({'extended':'true'}));
// 3. listen
// rest client를 사용했을 때 8080 포트로 연결이 안되서 변경함
app.listen('3000', () => {
console.log('접속 http://localhost:3000');
});
// 4. routing
app.get('/', (request, response) => {
response.render('main.ejs');
});
- 공용 css
*{margin: 0; padding:0; box-sizing:border-box;}
ul, ol, li{list-style: none;}
a{text-decoration: none;}
/* section영역 */
.sec{width: 100%;padding: 100px 0;}
.sec .title{margin: 0 0 40px; font: bold 24px 'inherit'; text-align: center;}
.employee table{width: 100%; border-spacing: 0; border-collapse: collapse; border-bottom: 1px solid #ddd;}
.employee th,
.employee td{border-top: 1px solid #ddd; padding: 5px 0;}
.employee th{background-color: #fdd;}
.employee table td.email{text-align: left;}
.employee tr:nth-of-type(2n-1){background-color: #f9f9f9;}
@media screen and (max-width: 768px) {
.employee th{display: none;}
.employee td{display:block;}
.employee td:first-of-type{
display: table-cell;
background-color: #f9f9f9;
}
.employee td{text-align: left;}
.employee td.id{text-align: center; background-color: #eee;}
.employee td.name{font-weight: bold; text-align: center; background-color: #eee;}
.employee td::before{ display: inline-block; padding: 0 20px; width: 100px;}
.employee td.team::before{content: 'team';}
.employee td.position::before{content: 'position'; }
.employee td.email::before{content: 'email';}
.employee td.tel::before{content: 'phone';}
.employee td.birthday::before{content: 'birthday';}
}
1. 데이터 조회
- server.js 파일에서 이전에 작성했던 내용과 동일하지만 코드를 좀 더 깔끔하게 정리하고, 이번엔 try-catch문을 사용해서 에러 처리도 진행한다.
- 요청 메소드를 작성하고, callback 함수를 작성하는데, 이번엔 callback 함수를 함수 표현식이 아닌 함수 선언으로 만들어 사용한다.
- Resources/Java/예외 처리/예외 처리 참고.
// 1. 데이터 조회
app.get("/employee", employeeSearchQuery);
app.get("/employee/:id", employeeSearchParam);
// 데이터 조회 query string
async function employeeSearchQuery(request, response) {
const {id, team, position} = request.query; // query string의 key에 저장된 value들을 가져옴
let employee = null; // employee 변수를 null로 설정
try { // 각 query string의 key의 value 상태에 따라 MySQL에 조회 조건을 다르게 설정
if (id) {
employee = await Employee.findAll({where:{id}});
} else if (team) {
employee = await Employee.findAll({where:{team}});
} else if (position) {
employee = await Employee.findAll({where:{position}});
} else if (team && position) {
employee = await Employee.findAll({where:{team, position}});
} else {
employee = await Employee.findAll();;
}
// 조회한 결과를 페이지에 전송
response.render('employee.ejs', {employee});
} catch (error) { // 에러가 발생하면 콘솔에 출력하고, status 적용과 메시지를 전송
console.log(error);
response.status(500).send('서버 에러 발생');
}
}
// 데이터 조회 param
// parameter를 받는 경우의 메소드
async function employeeSearchParam(request, response) {
const { id } = request.params;
try { // 전달 받은 id값으로 해당 직원을 조회
const employee = await Employee.findOne({where:{id}});
console.log({id});
if (employee) { // 직원이 존재하면 해당 직원 상세보기 페이지에 데이터를 전송
response.render('employee-detail.ejs', {employee});
} else { // 직원이 조회가 안된다면 존재하지 않다는 메시지 전송
response.send('존재하지 않음');
}
} catch (error) { // 에러 처리
console.log(error);
response.status(500).send('서버 에러 발생');
}
}
- 이전과 동일하게 전체 직원을 조회할 ejs 파일과 버튼 액션을 처리할 js 파일을 만든다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<!-- 파일 연결 시 /는 밴다 -->
<link href="main.css" type="text/css" rel="stylesheet">
</head>
<body>
<section class="sec employee">
<div class="container">
<h2 class="title">Employee List</h2>
<div class="row">
<div class="col">
<div class="search">
<div class="option-box">
<label for="group">choose team or position : </label>
<select id="group">
<option value="total">total</option>
<optgroup label="team">
<option value="engineering" class="team-option">engineering</option>
<option value="marketing" class="team-option">marketing</option>
<option value="sales" class="team-option">sales</option>
</optgroup>
<optgroup label="position">
<option value="Server Developer" class="position-option">Server Developer</option>
<option value="Android Developer" class="position-option">Android Developer</option>
<option value="Web Frontend Developer" class="position-option">Web Frontend Developer</option>
<option value="Marketing Manager" class="position-option">Marketing Manager</option>
<option value="Marketing Staff" class="position-option">Marketing Staff</option>
<option value="Sales Manager" class="position-option">Sales Manager</option>
<option value="Sales Staff" class="position-option">Sales Staff</option>
</optgroup>
</select>
</div>
<div class="search-box">
<input type="text" id="search" placeholder="input employee id">
<button id="send-btn">send</button>
</div>
</div>
<a href="/add-page" >회원 추가</a>
<div class="info-box">
<table>
<tr>
<th>id</th>
<th>이름</th>
<th>부서</th>
<th>직종</th>
<th>이메일</th>
<th>전화번호</th>
<th>생일</th>
</tr>
<tr>
<% for(let i = 0; i < employee.length; i++) {
%>
<tr>
<td class="id"> <%= employee[i].id %> </td>
<td class="name">
<a href="/employee/<%= employee[i].id %>"><%= employee[i].name %></a>
</td>
<td class="team"> <%= employee[i].team %> </td>
<td class="position"> <%= employee[i].position %> </td>
<td class="email"> <%= employee[i].emailAddress %> </td>
<td class="tel"> <%= employee[i].phoneNumber %> </td>
<!-- 날짜 처리를 toLocaleString() 메소드로 더 간단하게 처리 -->
<td class="birthday"> <%= employee[i].birthday.toLocaleString("ko-kr",{dateStyle:'long'}) %> </td>
</tr>
<% } %>
</tr>
</table>
</div>
</div>
</div>
</div>
</section>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script type="text/javascript" src="action.js" ></script> <!-- src로 주소 설정 시 /는 뺀다 -->
</body>
</html>
- employee.ejs 파일에 사용하는 javascript : 주로 button 액션과 url 연결을 담당한다.
// 직원 정보 페이지의 기본 url
let employee_url = "http://localhost:3000/employee";
// 드롭다운 메뉴의 요소들
let group = document.getElementById("group");
let teamList = document.getElementsByClassName("team-option");
let positionList = document.getElementsByClassName("position-option");
let default_option = document.querySelector(".search-info");
// 검색창의 요소들
let search_box = document.querySelector("#search");
let send_btn = document.querySelector("#send-btn");
// 부서 이름과 직종 이름을 저장할 배열
let teamName = [];
let positionName = [];
// 현재 query string의 value에서 공백 특수 문자를 공백으로 변환
let currentQuery = window.location.search.split("=")[1];
if (currentQuery) {
if (currentQuery.includes("%20")) {
currentQuery = currentQuery.replace("%20", " ");
}
}
// option 태그들의 페이지별 selected Attribute 설정
// 기본 option 태그를 선택 상태로 만들어 전체 검색을 위한 동작도 가능하게 수정
if (window.location.href === employee_url) {
default_option.setAttribute("selected", true);
} else {
default_option.setAttribute("selected", false);
}
// option 태그의 value를 가져와 부서 이름 배열에 이름을 저장
for (let i = 0; i < teamList.length; i++) {
teamName[i] = teamList[i].value;
if (currentQuery == teamList[i].value) {
teamList[i].setAttribute("selected", true); // 현재의 query string의 value와 일치하는 option 태그를 selected 상태로 설정
}
}
// option 태그의 value를 가져와 직종 이름 배열에 이름을 저장
for (let i = 0; i < positionList.length; i++) {
positionName[i] = positionList[i].value;
if (currentQuery == positionList[i].value) {
positionList[i].setAttribute("selected", true); // 현재의 query string의 value와 일치하는 option 태그를 selected 상태로 설정
}
}
// event listener
group.addEventListener("change", function () { // select 태그는 change일 때 설정이 잘 적용되었음
let flag_team = false; // 부서 이름인지 확인하는 flag
let flag_position = false; // 직종 이름인지 확인하는 flag
// 부서 이름인지 확인
teamName.forEach((el) => {
if (group.value == el) {
flag_team = true;
}
});
// 직종 이름인지 확인
positionName.forEach((el) => {
if (group.value == el) {
flag_position = true;
}
});
// select 태그에서 선택한 내용이 부서인지, 직종인지, 전체인지 확인
if (flag_team) {
location.href = employee_url + "?team=" + group.value; // query string에 부서 정보를 전달
flag_team = false; // flag를 다시 false로 변환
} else if (flag_position) {
location.href = employee_url + "?position=" + group.value; // query string에 직종 정보를 전달
flag_position = false;
} else if (group.value == "total") { // query string을 제거하고 전체 조회를 수행
location.href = employee_url;
}
});
// 직원 번호 입력창 설정
send_btn.addEventListener("click", function () {
location.href = employee_url + "?id=" + search_box.value; // 버튼 클릭 시 직원 id를 query string으로 전달
});
- 특정 직원의 정보를 상세히 보거나 직원의 정보를 수정, 삭제할 수 있는 페이지를 만든다.
- button 태그에
data-customName="value"을 추가해 원하는 value를 dataset.customName attribute로 설정한다. - 버튼을 눌렀을 때 이 값이 url을 통해 같이 전달될 수 있도록 설정한다.
new Date('날짜').toLocaleString("ko-kr",{dateStyle:'long'})를 사용하면xxxx년 x월 x일로 date가 표기된다.
- button 태그에
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link href="/main.css" type="text/css" rel="stylesheet">
</head>
<body>
<section class="sec employee">
<div class="container">
<h2 class="title">Employee View</h2>
<div class="row">
<div class="col">
<table>
<tr>
<th class="col">id</th>
<td class="id"><%= employee.id %></td>
</tr>
<tr>
<th class="col">이름</th>
<td class="name"><%= employee.name %></td>
</tr>
<tr>
<th class="col">부서</th>
<td class="team"><%= employee.team %></td>
</tr>
<tr>
<th class="col">직종</th>
<td class="position"><%= employee.position %></td>
</tr>
<tr>
<th class="col">이메일</th>
<td class="email"><%= employee.emailAddress %></td>
</tr>
<tr>
<th class="col">전화번호</th>
<td class="tel"><%= employee.phoneNumber %></td>
</tr>
<tr>
<th class="col">생일</th>
<td class="birthday"><%= employee.birthday.toLocaleString("ko-kr",{dateStyle:'long'}) %></td>
</tr>
</table>
<div class="btn-wrap">
<!-- custom data attribute : data-customName="value" -->
<button class="modify" type="button" data-id="<%= employee.id %>">수정</button>
<button class="del" type="button" data-id="<%= employee.id %>">삭제</button>
<button class="cancel" type="button">취소</button>
</div>
</div>
</div>
</div>
</section>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script type="text/javascript" src="/main.js"></script>
</body>
</html>
- 버튼 액션을 위한 Javascript 파일을 추가한다.
- main.js 파일은 다른 ejs 파일에서도 사용할 예정이므로, 다른 파일들에 없는 버튼들이 javacript에서 element를 찾지 못하는 오류를 발생시키기 때문에 if문으로 해당 element가 파일에 존재할 때 버튼 액션을 추가하도록 설정했다.
- 삭제 버튼은 Sequelize로 CRUD 수행하기 (Request Method)#4. 데이터 삭제의 요청 메소드와 callback 함수를, 수정 페이지 이동 버튼은 Sequelize로 CRUD 수행하기 (Request Method)#3. 데이터 수정의 요청 메소드와 callback 함수를 호출한다.
// 삭제 버튼 - 4. 데이터 삭제 참고
let del = document.querySelector(".del");
if (del) { // 문서에 del이 존재할때만
del.addEventListener('click', function (e) {
// 버튼을 눌렀을 때 버튼에 설정한 사용자 지정 data attribute를 가져온다.
// data-customName="value"
let id = e.target.dataset.id;
fetch('/delete/'+id, {method : 'delete'})
.then((response) => {response.text()})
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});
});
}
// 수정 페이지 이동 버튼 - 3. 데이터 수정 참고
let modify = document.querySelector(".modify");
if (modify) { // 문서에 modify가 존재할 때만
modify.addEventListener('click', function (e) {
// 버튼을 눌렀을 때 버튼에 설정한 사용자 지정 data attribute를 가져온다.
// data-customName="value"
let id = e.target.dataset.id;
window.location.href = "/edit-page/"+id;
});
}
// 취소 버튼
let cancel = document.querySelector(".cancel");
if (cancel) {
cancel.addEventListener('click', function () {
window.location.href = "/employee"; // 취소 버튼을 누르면 직원 조회 페이지로 이동
})
}
- 직원 상세 보기 페이지에서 직원의 정보와 각 버튼으로 원하는 동작을 수행할 수 있다.
2. 데이터 추가
- server.js 파일에 데이터 추가를 위한 요청 메소드와 callback 함수를 작성한다.
- 이번엔 데이터 추가를 위한 페이지가 필요하므로 페이지 이동용 get 메소드도 추가한다.
// 2. 데이터 추가
app.get("/add-page", employeeAddPage); // 추가 페이지 이동
app.post("/add", employeeAdd);
// 데이터 추가 페이지
function employeeAddPage(request, response) { // 페이지 이동 처리만 해준다.
try {
response.render('employee-add.ejs');
} catch (error) {}
}
// 데이터 추가
async function employeeAdd(request, response) {
const newEmpolyee = request.body; // html의 form 태그에서 데이터를 전송하면 request.body에 저장됨
console.log(newEmpolyee);
try {
const employee = await Employee.create(newEmpolyee); // build+save를 하거나 create를 사용해서 추가
response.redirect('/employee'); // 직원 추가가 완료되면 직원 조회 mapping을 통해 이동
} catch (error) { // 에러처리
console.log(error);
response.status(500).send('서버 에러 발생');
}
}
- 데이터 추가를 위한 ejs 파일을 만든다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<!-- url에 파라미터를 전달 받을 때 에러가 나던 employee.ejs와 다르게 여기선 주소에 /를 써도 css가 적용된다 -->
<link href="/main.css" type="text/css" rel="stylesheet">
</head>
<body>
<section class="sec employee">
<div class="container">
<h2 class="title">Add Employee</h2>
<div class="row">
<div class="col">
<!-- form 태그에 action과 method를 지정한다 -->
<!-- action="/add"로 app.post("/add", callback)이 호출됨 -->
<form action="/add" method="post">
<table>
<tr>
<th class="col"><label for="addName">이름</label></th>
<td><input type="text" id="addName" name="name"></td>
</tr>
<tr>
<th class="col"><label for="addTeam">부서</label></th>
<td><input type="text" id="addTeam" name="team"></td>
</tr>
<tr>
<th class="col"><label for="addPosition"></label>직종</label></th>
<td><input type="text" id="addPosition" name="position"></td>
</tr>
<tr>
<th class="col"><label for="addEmail">이메일</label></th>
<td><input type="text" id="addEmail" name="emailAddress"></td>
</tr>
<tr>
<th class="col"><label for="addPhone">전화번호</label></th>
<td><input type="text" id="addPhone" name="phoneNumber"></td>
</tr>
<tr>
<th class="col"><label for="addBirthday"></label>생일</label></th>
<td><input type="date" id="addBirthday" name="birthday"></td>
</tr>
</table>
</form>
<div class="btn-wrap">
<!-- submit을 사용하면 form에 지정된 action으로 바로 동작함 -->
<button class="add-btn" type="sumbit">확인</button>
<button class="cancel" type="button">취소</button>
</div>
</div>
</div>
</div>
</section>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<!-- url에 파라미터를 안 받는 이 페이지에선 주소에 /를 써도 js가 적용된다 -->
<script type="text/javascript" src="/main.js"></script>
</body>
</html>
- 버튼 액션을 지정한 javascript를 작성한다.
- form 태그의 내용을 전송하거나(submit()) 페이지 이동을 담당하는 버튼 액션을 추가한다.
- 20240215 수정 : 왜 form 태그에 action을 지정하고 button 태그의 타입을 submit으로 했는데, 이벤트 리스너에서 또 fetch로 데이터를 전송하는 걸까
- form에 action을 이미 지정했다면 submit 타입인 버튼(<button>)의 이벤트 리스너의 callback에서 fetch를 안해도 데이터가 잘 들어간다.
- <button>의 타입이 button이라면 이벤트 액션에서 callback 내에 fetch를 설정해야 한다.
- (수업 시간에 진행한 실습에서 내용을 정정해주셨다)
- form 태그의 내용을 전송하거나(submit()) 페이지 이동을 담당하는 버튼 액션을 추가한다.
// 추가 버튼
let add_btn = document.querySelector(".add-btn");
if (add_btn) {
add_btn.addEventListener('click', function (e) {
// 만약 add_btn이 submit이 아닌 button 타입이라면 이 부분이 필요하다.
// submit 타입이라면 form 태그에 지정한 action이 수행되므로 fetch가 없어도 바로 데이터가 전송된다.
fetch('/add', {method : 'post'}) // fetch로 데이터 전송
.then((response) => {response.text()})
.then((result) => { // 데이터가 잘 전송되면 콘솔에 결과를 작성
console.log(result);
})
.catch((error) => {
console.log(error);
});
});
}
// 취소 버튼
let cancel = document.querySelector(".cancel");
if (cancel) {
cancel.addEventListener('click', function () {
window.location.href = "/employee"; // 취소 버튼을 누르면 직원 조회 페이지로 이동
})
}
- 직원 추가 페이지에서 DB에 직원을 등록하기 위해 필요한 정보를 작성하고 확인 버튼을 누르면 form 태그 내의 내용들을 DB로 전송한다.
3. 데이터 수정
- server.js 파일에서 데이터 수정을 위한 요청 메소드와 callback 함수를 작성한다.
- 수정은 두 가지 방법 중 하나를 선택해서 함수를 작성한다.
// 3. 데이터 수정
// 이번엔 parameter(path variable)을 둘 다 받는다
app.get("/edit-page/:id", empolyeeModifyPage); // 수정 페이지 이동
app.post("/edit/:id", empolyeeModify);
// 데이터 수정 페이지
async function empolyeeModifyPage(request, response) {
const {id} = request.params; // url에 포함된 parameter를 저장
const employee = await Employee.findOne({where:{id}}); // id로 직원을 조회
if(employee) { // 직원이 존재하는지 확인
try {
response.render('employee-edit.ejs', {employee});
} catch (error) { // 조회 과정에 오류가 생기면 에러 처리
response.status(404).send({message: '회원 조회 실패'});
}
} // 직원이 없다면 수정 과정이 없음
}
// 데이터 수정
// 직원을 조회하고 객체의 property를 새로 지정한 후 save하는 방법
async function empolyeeModify(request, response) {
const {id} = request.params; // 여기에도 id가 파라미터로 같이 넘어왔기에 저장한다
console.log(id)
const newInfo = request.body; // 수정 페이지의 form 태그에서 데이터를 전송하면 request.body에 저장됨
const employee = await Employee.findOne({where:{id}}); // 수정을 위해 id로 직원을 조회함
if (employee) { // 직원이 있다면
try {
Object.keys(newInfo).forEach((prop) => {
// 조회한 직원 객체의 property를 가져와서 수정할 property로 저장
employee[prop] = newInfo[prop];
});
await employee.save(); // 수정한 내용을 저장
response.redirect('/employee'); // 수정이 완료되면 직원 조회 페이지로 이동
} catch (error) { // 에러 처리
console.log(error);
response.status(500).send('서버 에러 발생');
}
}
}
// 데이터 수정
// 직원을 조회하고 객체의 property를 새로 지정한 후 save하는 방법
async function empolyeeModify(request, response) {
const {id} = request.params; // 여기에도 id가 파라미터로 같이 넘어왔기에 저장한다
console.log(id);
const newInfo = request.body; // 수정 페이지의 form 태그에서 데이터를 전송하면 request.body에 저장됨
try {
let result = await Employee.update(newInfo, {where:{id}}); // 특정 id의 데이터를 조회해서 새 정보로 업데이트
response.redirect('/employee'); // 수정이 완료되면 직원 조회 페이지로 이동
} catch (error) { // 에러 처리
console.log(error);
response.status(500).send('서버 에러 발생');
}
}
- 데이터 수정을 위한 ejs 파일을 만든다.
- put을 작성할 때 주의사항 : HTML의 form 태그의 메소드에는 get과 post 밖에 없기 때문에 put을 쓰면 query string에 내용이 전부 포함된 GET 메소드로 request가 전송되고, 제대로 수정되지 않는다.
- 수정 테스트만 한다면
<form method="put">이 아닌<form method="post">로 작성하고,app.put()와fetch("", {method : "put"})으로 작성해야 한다. - 관련 내용을 더 찾아봤을 때는 RestFul API 구현을 위해 GET, POST, PUT, DELETE 메소드를 따로 구분해서 작성하며, 개발하려는 상황에 맞춰 필요한 메소드를 구현한다고 한다.
- PUT과 POST를 비교한 설명 참고 자료 : stackoverflow What is the difference between POST and PUT inHTTP
- 이와 관련된 멱등성에 관한 참고 자료 : KAMIYU's 프로그래밍 용어 멱등성(idempotent)란
- POST로 PUT을 구현할 수 있음에도 다른 라이브러리를 이용해 메소드 오버라이딩을 통해 PUT과 DELETE를 구현하는 방법이 있다.
- MySQL의 Date를 Javascript로 가져올 때 형식 변환 :
let newDate = new Date(targetDate).toISOString().split('T')[0];- Javascript의 datepicker에서 인식할 수 있는 string 형태를 얻을 수 있다.
- 참고 자료 : stackoverflow Convert JS date time to MySQL datetime
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link href="/main.css" type="text/css" rel="stylesheet">
</head>
<body>
<section class="sec employee">
<div class="container">
<h2 class="title">Edit Employee</h2>
<div class="row">
<div class="col">
<!-- html에는 get과 post만 있기 때문에 둘 중 하나만 쓰고 fetch와 app에서 put을 지정해야 함-->
<!-- form의 action에서도 id를 url의 파라미터로 넘겨줘야 하므로 표현식을 써서 넘김 -->
<form action="/edit/<%= employee.id %>" method="post">
<table>
<!-- 수정페이지에선 해당 직원의 원래 정보가 뜨도록 표시 -->
<tr>
<th class="col"><label for="addName">이름</label></th>
<td><input type="text" id="addName" name="name" value="<%= employee.name %>"></td>
</tr>
<tr>
<th class="col"><label for="addTeam">부서</label></th>
<td><input type="text" id="addTeam" name="team" value="<%= employee.team %>"></td>
</tr>
<tr>
<th class="col"><label for="addPosition"></label>직종</label></th>
<td><input type="text" id="addPosition" name="position" value="<%= employee.position %>"></td>
</tr>
<tr>
<th class="col"><label for="addEmail">이메일</label></th>
<td><input type="text" id="addEmail" name="emailAddress" value="<%= employee.emailAddress %>"></td>
</tr>
<tr>
<th class="col"><label for="addPhone">전화번호</label></th>
<td><input type="text" id="addPhone" name="phoneNumber" value="<%= employee.phoneNumber %>"></td>
</tr>
<tr>
<th class="col"><label for="addBirthday"></label>생일</label></th>
<!-- MySQL의 Date 형식은 Javascript의 datepicker 형식과 맞지 않기에 수정 필요 -->
<% let birthday = new Date(employee.birthday).toISOString().split('T')[0]; %>
<td><input type="date" id="addBirthday" name="birthday" value="<%= birthday %>"></td>
</tr>
</table>
</form>
<div class="btn-wrap">
<button class="put-btn" type="submit" data-id="<%= employee.id %>" >확인</button>
<button class="cancel" type="button">취소</button>
</div>
</div>
</div>
</div>
</section>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script type="text/javascript" src="/main.js"></script>
</body>
</html>
- 버튼 액션을 지정한 javascript를 작성한다.
- form 태그의 내용을 전송하거나(submit()) 페이지 이동을 담당하는 버튼 액션을 추가한다.
- 20240215 수정 : 왜 form 태그에 action을 지정하고 button 태그의 타입을 submit으로 했는데, 이벤트 리스너에서 또 fetch로 데이터를 전송하는 걸까
- form에 action을 이미 지정했다면 submit 타입인 버튼(<button>)의 이벤트 리스너의 callback에서 fetch를 안해도 데이터가 잘 들어간다.
- <button>의 타입이 button이라면 이벤트 액션에서 callback 내에 fetch를 설정해야 한다.
- (수업 시간에 진행한 실습에서 내용을 정정해주셨다)
- form 태그의 내용을 전송하거나(submit()) 페이지 이동을 담당하는 버튼 액션을 추가한다.
// 수정 버튼
let put_btn = document.querySelector(".put-btn");
if (put_btn) {
put_btn.addEventListener('click', function(e) {
// 이미 form 태그에 action을 지정한 상태로 button 태그의 타입이 submit이라면 fetch를 생략해도 된다.
// fetch로 put 메소드 방식으로 app.put("/edit/:id", callback)을 호출한다.
let id = e.target.dataset.id;
//console.log(id);
fetch("/edit/"+id, {method : "put"})
.then((response) => {
response.text()
})
.then((result) => {
console.log(result)
})
.catch((error) => {
console.log(error)
})
});
}
// 취소 버튼
let cancel = document.querySelector(".cancel");
if (cancel) {
cancel.addEventListener('click', function () {
window.location.href = "/employee"; // 취소 버튼을 누르면 직원 조회 페이지로 이동
})
}
- form 태그 내의 값을 가져와서 json 형태로 만들고 요청을 보낼 수도 있다.
- MIME 타입의 application/json 참고.
let put_btn = document.querySelector(".put-btn");
// form 태그 내의 정보들을 가져온다.
let addName = document.getElementById('addName');
let addTeam = document.getElementById('addTeam');
let addPosition = document.getElementById('addPosition');
let addEmail = document.getElementById('addEmail');
let addPhone = document.getElementById('addPhone');
let addBirthday = document.getElementById('addBirthday');
put_btn.addEventListener('click', function(e){
// 이미 form 태그에 action을 지정한 상태로 button 태그의 타입이 submit이라면 fetch를 생략해도 된다.
// fetch로 put 메소드 방식으로 app.put("/edit/:id", callback)을 호출한다.
// 버튼을 눌렀을 때 버튼에 설정한 사용자 지정 data attribute를 가져온다.
// data-customName="value"
let id = e.currentTarget.dataset.id;
// newInfo 객체에 가져온 정보들을 저장하기
let newInfo ={
id, // db에서 auto_increment가 적용되었기에 값을 넣지 않음
name : addName.value,
team : addTeam.value,
position : addPosition.value,
emailAddress : addEmail.value,
phoneNumber : addPhone.value,
birthday : addBirthday.value
}
fetch('/edit/'+id, {
method : 'PUT', // 수정을 위해 put으로 요청
headers: { 'Content-Type': 'application/json'}, // JSON 형식으로 데이터를 전송
body: JSON.stringify(newInfo) // request.body의 내용을 json(string) 형식으로 변환
})
.then((response) => {
response.text()
})
.then((result) => {
console.log(result);
window.location.href="/employee"; // 요청 성공 시 직원 조회 페이지로 이동
})
.catch((error) => {
console.log(error);
})
});
- 수정 페이지에서 직원의 원래 정보를 확인할 수 있고, 내용을 수정해서 전송할 수 있다.
4. 데이터 삭제
- server.js 파일에서 데이터 삭제를 위한 요청 메소드와 callback 함수를 작성한다.
- 삭제 버튼은 직원 상세보기 페이지에 있으며, 삭제 페이지가 따로 필요 없다.
// 4. 데이터 삭제
app.delete("/delete/:id", employeeDelete); // 여기도 id를 파라미터로 받는다.
// 데이터 삭제
async function employeeDelete(request, response) {
const {id} = request.params; // url로 전달된 id를 저장
console.log(id);
try {
const result = await Employee.destroy({where:{id}}); // 해당 id의 직원 정보를 삭제한다.
response.redirect('/employee'); // 제거가 완료되면 직원 조회 페이지로 이동해야 하는데 이동이 잘 안된다
} catch (error) { // 에러가 발생하면 처리
console.log(error);
response.status(500).send('서버 에러 발생');
}
}
- 삭제 동작을 위한 버튼 액션 지정용 Javascript 파일을 작성한다.
- Sequelize로 CRUD 수행하기 (Request Method)#1. 데이터 조회에 있는 직원 상세보기 페이지의 삭제 버튼의 액션이다.
// 삭제 버튼
let del = document.querySelector(".del");
if (del) {
del.addEventListener('click', function (e) {
// 버튼을 눌렀을 때 버튼에 설정한 사용자 지정 data attribute를 가져온다.
// data-customName="value"
let id = e.target.dataset.id;
// fetch로 delete 메소드 방식으로 app.delete("/delete:/id", callback)를 호출한다.
fetch('/delete/'+id, {method : 'delete'})
.then((response) => {response.text()})
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});
});
}
- 1번 직원을 삭제해서 전체 직원 목록에 뜨지 않는다.