스터디/KAKAOCLOUDSCHOOL
[Node & MariaDB] 개발자 지망생 스터디 - 18일차
shineIT
2022. 11. 25. 20:54
17일차 실습 이어서..
더보기
Node.js에서 SQL을 이용해서 Select 구문을 실행했을 때 결과는 무조건 배열
Select 구문을 사용하게 되면 행과 열이 몇 개가 나올지 알 수 없음.
[{컬럼이름:값,컬럼이름:값...}, {}, {}]
SELECT count(*) from goods;
>> 30
results : [{count(*):30}]
results[0].count(*)
5. 데이터 일부분 가져오기
1) App.js 파일에 요청을 처리하는 메서드를 구현
//데이터 일부분 가져오기
//URL은 /item/list
//파라미터는 pageno 1개 인데 없으면 1로 설정
app.get('/item/list', (req, res) => {
//파라미터 읽어오기
let pageno = req.query.pageno;
if(pageno == undefined){
pageno = 1;
}
console.log(pageno);
//브라우저에서 테스트 - 콘솔 확인
//localhost:9000/item/list
//localhost:9000/item/list?pageno=3
//item 테이블에서 itemid 를 가지고 내림차순 정렬해서
//페이지 단위로 데이터 가져오기
//select * from item order by itemid desc limit 시작번호, 5
//시작번호=(pageno-1)*5
//파라미터는 무조건 문자열입니다.
//파라미터를 가지고 산술연산을 할 때는 숫자로 변환을 수행
//성공 과 실패 여부를 저장
let result = true;
//성공했을 때 데이터를 저장
let list;
//데이터 목록 가져오기
connection.query(
"select * from goods order by itemid desc limit ?, 5",
[(parseInt(pageno)-1)*5], (err, results, fields) => {
if(err){
console.log(err);
result = false;
}else{
list = results;
//console.log(list);
}
//goods 테이블의 전체 데이터 개수를 가져오기
let cnt = 0;
connection.query("select count(*) cnt from goods",
[], (err, results, fields)=>{
if(err){
//에러가 발생했을 때
console.log(err);
result = false;
}else{
//정상적으로 구문이 실행되었을 때
//하나의 행만 리턴되므로 0 번째 데이터를 읽어내면 됩니다.
cnt = results[0].cnt;
}
//응답 생성해서 전송
if(result === false){
res.json({"result":false});
}else{
res.json({"result":true, "list":list, "count":cnt});
}
});
});
});
2) index.html 파일에 페이지 단위 보기 구현
- body부분에 페이지 단위 전체 데이터를 가져올 버튼을 생성
<a href="#" id="listbtn">페이지 단위 전체 데이터 가져오기</a><br/>
- 스크립트 코드 추가
let listbtn = document.getElementById("listbtn");
//현재 페이지 번호를 저장할 변수를 선언
let pageno = 1;
listbtn.addEventListener("click", (e) => {
let request = new XMLHttpRequest();
request.open('GET', '/item/list?pageno=' + pageno);
request.send('');
request.addEventListener('load', () => {
//출력 영역을 초기화
content.innerHTML = '';
//데이터를 파싱
let data = JSON.parse(request.responseText);
if(data.result === true){
//데이터 개수 와 목록을 가져오기
let count = data.count;
let list = data.list;
//출력 내용 만들기
let display = "<div align='center' class='body'>";
display += "<h2>상품 목록</h2>";
display += "<table border='1' id='tbldata'>";
//전체 데이터 개수 출력
display += "<tr><td colspan='3' align='right'>";
display += "전체 데이터 개수:" + count + "개</td></tr>"
//항목 헤더 출력
display += "<tr class='header'>";
display += "<th width='80'>ID</th>";
display += "<th width='320'>상품명</th>";
display += "<th width='100'>가격</th>";
display += "</tr>";
//데이터 목록 출력
for(item of list){
display += "<tr class='record'>";
display += "<td align='center'>"
+ item.itemid + "</td>";
display += "<td align='left'>"
+ item.itemname + "</td>";
display += "<td align='right'>"
+ item.price + " 원</td>";
display += "</tr>";
}
display += "</table></div>";
//더보기 구현
//현재 페이지가 마지막 페이지가 아닌 경우만 출력
if((pageno-1) * 5 < count){
display += "<table align='center'";
display += " width='500' id='tblbtn'>";
display += "<tr><td align='center' colspan='3'>";
display += "<span id='addbtn'><a href='#'>더보기</a></span></td>";
display += "</tr></table>";
}
content.innerHTML = display;
//더보기 버튼을 눌렀을 때 처리
let addbtn = document.getElementById("addbtn");
//addbtn이 존재하는 경우에만 수행
if(addbtn != undefined){
addbtn.addEventListener('click', (e) => {
pageno = pageno + 1;
let request = new XMLHttpRequest();
request.open('GET', '/item/list?pageno=' + pageno);
request.send('');
//전체 데이터 개수보다 더 많이 출력하면 더보기 영역을 삭제
if((pageno) * 5 >= data.count){
pageno = pageno - 1;
document.getElementById("tblbtn").remove();
}
//데이터를 가져오면
request.addEventListener('load', ()=>{
let data = JSON.parse(request.responseText);
let list = data.list;
//데이터 테이블 출력
const table = document.getElementById('tbldata');
let display = "";
for(item of list){
display += "<tr class='record'>";
display += "<td align='center'>" + item.itemid +
"</td>";
display += "<td align='left'>" + item.itemname +
"</td>";
display += "<td align='right'>" + item.price +
" 원</td>";
display += "</tr>";
}
table.innerHTML += display;
})
});
}
}else{
content.innerHTML =
'<p>데이터를 가져오는데 실패</p>';
}
});
});
6. 데이터 상세보기
- 데이터 한개의 정보를 전부 가져와서 출력
- 테이블에서 데이터 1개를 가져오는 방법은 기본키나 unique 속성을 이용한 조회만 가능
- 기본키나 unique 속성의 값을 받아서 서버에서 처리
- itemid를 URL에 포함시켜 받아서 처리하는 구조로 구현
1)App.js 파일에 상세보기를 위한 코드를 추가
//상세보기 처리를 위한 코드
app.get('/item/detail/:itemid', (req, res) => {
//파라미터 읽기
let itemid = req.params.itemid;
//itemid를 이용해서 1개의 데이터를 찾아오는 SQL을 실행
connection.query("select * from goods where itemid=?",
[itemid], (err, results, fields) => {
if(err){
console.log(err);
res.json({"result":false});
}else{
console.log(results);
res.json({"result":true, "item":results[0]});
}
});
});
- 테스트는 localhost:9000/item/detail/번호
2)index.html 파일에서 상세보기를 위한 코드를 작성
- itemname을 출력하는 코드를 수정
display += "<td align='left'>" + "<a href='#' id='item" + item.itemid + "'>" + item.itemname + "</a></td>";
- itemname을 클릭했을 때 동작할 스크립트 코드를 작성
//itemname을 눌렀을 때 수행할 코드
//링크 하나 하나 이벤트 처리를 하는 것은 자원 낭비
//부모에 이벤트 처리 코드를 작성
content.addEventListener('click', (e)=>{
//클릭한 대상의 id가 item 으로 시작하는 경우만 동작
if(e.target.id.startsWith('item')){
//클릭한 대상의 아이디에서 item을 제외한 부분 - itemid
let itemid = e.target.id.substring(4).trim();
//alert(itemid);
let request = new XMLHttpRequest();
request.open('GET', '/item/detail/' + itemid);
request.send('');
request.addEventListener('load', () => {
let data = JSON.parse(request.responseText);
if(data.result == true){
//데이터 가져오기
let item = data.item;
//출력 내용 생성
let display = "<div align='center' class='body'>";
display += '<h2>상세보기</h2>';
display += '<table>';
display += "<tr><td><img src='/img/"
+ item.pictureurl + "'/><td>";
display += "<td align='center'><table>";
display += "<tr height='50'><td width='80'>상품명</td>";
display += "<td width='160'>" + item.itemname + "</td>";
display += "<tr height='50'><td width='80'>가격</td>";
display += "<td width='160'>" + item.price + " 원</td>";
display += "<tr height='50'><td width='80'>비고</td>";
display += "<td width='160'>" + item.description +
"</td></tr>";
display += "</table></td></tr></table>";
content.innerHTML = display;
}
});
}
});
7. 첨부 파일 (이미지) 다운로드
- 웹 브라우저에서는 파일에 링크가 걸려있으면 자신이 출력할 수 있는 파일은 출력을 하고 출력할 수 없는 파일은 다운로드를 수행
1) 이미지 파일 출력하는 부분을 수정
display += "<tr><td><a href='/img/" + item.pictureurl + "'>" + "<img src='/img/" + item.pictureurl + "'/></a><td>";
2)App.js 파일에 /img/이미지파일명 을 처리하는 코드를 추가
//이미지 다운로드 처리
app.get('/img/:pictureurl', (req, res) => {
let pictureurl = req.params.pictureurl;
//이미지 파일의 절대경로를 생성
let file =
"C:\Users\Administrator\Documents\node\mariadb\public\img"
+ "/" + pictureurl;
console.log(__dirname);
//파일 이름을 가지고 타입을 생성
let mimetype = mime.lookup(pictureurl);
res.setHeader('Content-disposition',
'attachment; filename=' + pictureurl);
res.setHeader('Content-type', mimetype);
//파일의 내용을 읽어서 res에 전송
let filestream = fs.createReadStream(file);
filestream.pipe(res);
})
8. API 테스트를 위한 postman 프로그램 다운로드 및 설치
- 홈페이지 또는 맥의 경우 Brew를 통해 설치 가능함 (설치 방법은 구글을 이용해 쉽게 찾을수 있음)
9. 데이터 삽입
- 데이터 삽입은 삽입 화면을 먼저 출력하고 데이터를 입력받은 후 데이터를 서버에게 전송하면 처리
- 데이터 삽입은 get 방식이 post 방식으로 처리
- post 방식은 화면을 만들지 않고 웹브라우저에서 테스트가 불가능
- 서버에 post 방식 요청을 만들고 테스트를 할 때는 별도의 프로그램을 설치해서 하게 됨
1)App.js 파일에 데이터 삽입을 위한 코드를 작성
- 어떤 데이터를 받아야 하는지 결정
itemid: 가장 큰 itemid를 찾아서 +1
itemname, price, description, pictureurl(파일) 은 직접 입력
updatedate는 현재 날짜를 문자열로 입력
- 삽입, 삭제, 갱신 작업이 발생하면 updatedate.txt 파일에 발생한 시간을 기록
- 현재 데이터가 업데이트 된 시간을 알기 위해서 기록
- App.js 파일에 현재 날짜를 문자열로 리턴하는 함수 와 현재 날짜 및 시간을 문자열로 리턴하는 함수 작성
더보기
1월부터 12월까지 날의 숫자를 배열로 생성
[0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
//현재 날짜를 문자열로 리턴하는 함수
//요즈음 등장하는 자바스크립트 라이브러리들의 샘플 예제는
//특별한 경우가 아니면 function 을 사용하지 않습니다.
const getDate = () => {
let date = new Date();
let year = date.getFullYear();
//월은 +1을 해야 우리가 사용하는 월이 됩니다.
let month = date.getMonth() + 1;
let day = date.getDate();
month = month >= 10 ? month : '0' + month;
day = day >= 10 ? day : '0' + day;
return year + "-" + month + "-" + day;
}
//날짜 와 시간을 리턴하는 함수
const getTime = () => {
let date = new Date();
let hour = date.getHours();
let minute = date.getMinutes();
let second = date.getSeconds();
hour >= 10 ? hour : '0' + hour;
minute >= 10 ? minute : '0' + minute;
second >= 10 ? second : '0' + second;
return getDate() + " "
+ hour + ":" + minute + ":" + second;
}
- 삽입 요청을 처리하는 함수 추가
//데이터 삽입을 처리해주는 함수
app.post('/item/insert', upload.single('pictureurl'),
(req, res) => {
//파라미터 읽어오기
const itemname = req.body.itemname;
const description = req.body.description;
const price = req.body.price;
//파일 이름 - 업로드하는 파일이 없으면 default.png
let pictureurl;
if(req.file){
pictureurl = req.file.filename
}else{
pictureurl = 'default.jpg';
}
//가장 큰 itemid 찾기
connection.query("select max(itemid) maxid from goods",
[], (err, results, fields) => {
let itemid;
//최대값이 있으면 + 1 하고 없으면 1로 설정
if(results.length > 0 ){
itemid = results[0].maxid + 1;
}else{
itemid = 1;
}
//데이터 삽입
connection.query("insert into goods(" +
"itemid, itemname, price, description,"
+ "pictureurl, updatedate) values(?, ?, ?, ?, ?, ?)",
[itemid, itemname, price, description, pictureurl,
getDate()], (err, results, fields) => {
if(err){
console.log(err);
res.json({"result":false});
}else{
//현재 날짜 및 시간을 update.txt에 기록
const writeStream = fs.createWriteStream('./update.txt');
writeStream.write(getTime());
writeStream.end();
res.json({"result":true});
}
})
});
})
2)index.html 파일에 삽입 요청을 위한 코드를 작성
- 삽입 요청을 위한 DOM을 추가
<a href="#" id="insertbtn">데이터 삽입</a><br/>
- DOM을 클릭했을 때 처리하는 코드를 script에 작성
let insertbtn = document.getElementById("insertbtn");
insertbtn.addEventListener('click', (e) => {
//삽입화면 출력
content.innerHTML = '';
let html =
`
<div>
<p></p>
<form id='insertform'
enctype='multipart/form-data'
method='post'>
아이템이름<input type='text'
name='itemname' id='itemname'/><br/>
가격<input type='text'
name='price' id='price'/><br/>
설명<input type='text'
name='description' id='description'/><br/>
이미지<input type='file'
name='pictureurl' id='pictureurl'/><br/>
<input type='submit' value='삽입'/>
</form></div>
`
content.innerHTML = html;
//폼안에서 삽입 버튼을 눌렀을 때 처리
let f = document.getElementById('insertform');
if(f != undefined){
f.addEventListener('submit', (e) => {
//기본 이벤트 제거
e.preventDefault();
//폼 데이터 찾아오기
const formData = new FormData(
document.getElementById("insertform"));
//폼 데이터를 전송
let request = new XMLHttpRequest();
request.open("POST", "/item/insert", true);
request.send(formData);
request.addEventListener('load', () => {
let data = JSON.parse(request.responseText);
if(data.result){
//데이터 다시 불러오기
document.getElementById("listbtn").click();
}else{
alert("삽입 실패");
}
})
})
}
});
10. 데이터 삭제
- 기본키를 매개변수로 받아서 삭제를 합니다.
- 삭제는 GET 이나 POST 별 상관 없습니다.
1)App.js 파일에 itemid를 받아서 데이터를 삭제하는 메서드를 구현
//데이터를 삭제하는 함수
app.post('/item/delete', (req, res) => {
//post 방식으로 전송된 데이터 읽기
let itemid = req.body.itemid;
//itemid를 받아서 goods 테이블에서 삭제하기
connection.query("delete from goods where itemid=?",
[itemid], (err, results, fields)=>{
if(err){
console.log(err);
res.json({"result":false});
}else{
//현재 날짜 및 시간을 update.txt에 기록
const writeStream = fs.createWriteStream(
'./update.txt');
writeStream.write(getTime());
writeStream.end();
res.json({"result":true});
}
});
});
2)index.html 파일에서 삭제를 구현
- 상세보기 출력하는 부분에 삭제를 위한 DOM을 추가
//삭제를 위한 DOM을 추가
display += "<tr>" + "<td colspan='2' align='center' width='240'>"
+ "<a href='#' id='deletebtn'>데이터 삭제</a>" + "</td></tr>";
- 상세보기를 출력하는 하단에 삭제를 위한 스크립트 코드를 추가
//데이터 삭제를 눌렀을 때 처리
let deletebtn = document.getElementById("deletebtn");
if(deletebtn != undefined){
deletebtn.addEventListener('click', (e) => {
//폼이 없는 경우의 POST 방식 파라미터 만들기
let params = 'itemid=' + item.itemid;
let request = new XMLHttpRequest();
request.open('POST', '/item/delete');
//폼이 아닌 경우는 form 형식으로 인코딩
request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
request.send(params);
request.addEventListener('load', () => {
let data = JSON.parse(request.responseText);
if(data.result){
document.getElementById("listbtn").click();
alert("삭제 성공");
}else{
alert("삭제 실패");
}
})
})
11. 데이터 수정
- 현재 데이터를 수정할 수 있도록 화면에 출력하고 데이터를 입력받아서 수정
- 텍스트는 입력받은 내용을 수정하면 되는데 이전 내용을 화면에 출력한 상태에서 내용을 고치지 않았어도 set으로 출력하면 되는데 파일은 input 에 설정할 수 없음
- 텍스트는 입력받은 내용을 수정하면 되는데 이전 내용을 화면에 출력한 상태에서 내용을 고치지 않았어도 set으로 출력하면 되는데 파일은 input 에 설정할 수 없음
1) 수정 화면을 위한 html 파일을 생성 - public 디렉토리에 update.html
<form method="post" id="updateform"
enctype="multipart/form-data">
아이템 아이디:<input type="text" name="itemid" id="itemid"/>
<br/>
아이템 이름:<input type="text" name="itemname" id="itemname"/>
<br/>
아이템 가격:<input type="text" name="price" id="price"/>
<br/>
설명:<input type="text" name="description" id="description"/>
<br/>
새로운 이미지:<input type="file" name="pictureurl" />
<br/>
이전 이미지<img width="100" height="100" id="picture">
<br/>
<!-- hidden 은 화면에 출력되지는 않지만 가지고 있어야 하는
데이터가 있을 때 사용-->
<input type="hidden" name="oldpictureurl" id="oldpictureurl"/>
<br />
<input type="submit" value="수정" />
</form>
2)App.js 파일에 수정의 get 요청이 오면 처리할 함수를 작성
//수정을 get으로 요청했을 때 - 수정 화면으로 이동
app.get('/item/update', (req, res) => {
//public 디렉토리의 update.html을 읽어내서 리턴
fs.readFile('./public/update.html', (err, data)=>{
res.end(data);
});
});
3)index.html 파일에 수정 화면으로 이동하기 위한 코드를 작성
- 상세보기를 출력하는 부분에 데이터 수정 DOM을 추가(삭제를 추가한 부분의 위나 아래에 추가)
//수정을 위한 DOM을 추가
display += "<tr>" + "<td colspan='2' align='center' width='240'>"+ "<a href='#' id='updatebtn'>데이터 수정</a>" + "</td></tr>";
- 수정 버튼을 눌렀을 때 처리를 위한 스크립트 코드를 추가
//데이터 수정을 눌렀을 때 처리
let updatebtn = document.getElementById("updatebtn");
if(updatebtn != undefined){
updatebtn.addEventListener('click', (e) => {
let request = new XMLHttpRequest();
request.open('GET', '/item/update');
request.send('');
request.addEventListener('load', () => {
let html = request.responseText;
content.innerHTML = html;
//수정은 수정하기 위한 원본 데이터를 화면에 출력
document.getElementById("itemid").value =
item.itemid;
document.getElementById("itemid").readOnly =
true;
document.getElementById("itemname").value =
item.itemname;
document.getElementById("price").value =
item.price;
document.getElementById("description").value =
item.description;
//원본의 이름을 숨김
document.getElementById("oldpictureurl").value =
item.pictureurl;
//원본을 다른 방식으로 출력
document.getElementById("picture").src =
"/img/" + item.pictureurl;
//수정 폼을 찾아오기
let updateform = document.getElementById("updateform");
if(updateform != undefined){
//폼 안의 submit 버튼을 눌렀을 때
updateform.addEventListener('submit', (e) => {
//기본 이벤트 제거
e.preventDefault();
//전송할 데이터 생성 - 폼 안에 입력한 데이터 생성
const formData = new FormData(updateform);
//서버에게 요청
let request = new XMLHttpRequest();
request.open("POST", "/item/update");
request.send(formData);
//응답을 받았을 때 처리
request.addEventListener('load', () => {
let data = JSON.parse(request.responseText);
if(data.result === true){
document.getElementById("listbtn").click();
alert("성공");
}else{
alert("실패");
}
});
})
}
})
})
}
4) App.js 파일에 실제 수정을 처리하는 코드를 작성
app.post('/item/update', upload.single('pictureurl'), (req, res) => {
//파라미터 가져오기
const itemid = req.body.itemid;
const itemname = req.body.itemname;
const price = req.body.price;
const description = req.body.description;
//예전 파일 이름
const oldpictureurl = req.body.oldpictureurl;
//수정할 파일 이름 만들기
let pictureurl;
//새로 선택한 파일이 있다면
if(req.file){
pictureurl = req.file.filename;
}else{
pictureurl = oldpictureurl;
}
//데이터베이스 작업
connection.query("update goods set itemname=?, price=?," +
"description=?, pictureurl=?, updatedate=? where itemid=?",
[itemname, price, description, pictureurl, getDate(),
itemid],
(error, results, fields) => {
//console.log(results);
//console.log(fields);
if(error){
//에러가 발생한 경우
console.log(error);
res.json({"result": false});
}else{
//성공했을 때 처리
const writeStream =
fs.createWriteStream("./update.txt");
writeStream.write(getTime());
writeStream.end();
res.json({"result": true});
}
})
});