클라우드 7일차

soso·2024년 6월 18일

클라우드 부트캠프

목록 보기
8/77

Node.js에서 MySQL을 연동하여 게시판 만들기

1. 프로젝트 구성

npm init
npm i express
npm i -g nodemon (이미 깔려있다면 생략)

2. server.js 생성

const express = require("express");
const app = express();

app.listen(8080, function () {
  console.log("server ready...");
});
nodemon server

3. index.html 작성

bootstrap template download 후 zip 파일을 풀고 작업 폴더 내의 public 폴더를 생성해 모든 리소스를 카피
(https://startbootstrap.com/template/sb-admin)

4. index.html이 보이도록 server.js에서 라우팅

const express = require("express");
const app = express();

app.listen(8080, function () {
  console.log("server ready...");
});

**app.get("/", function (req, res) {
  res.sendFile(__dirname + "/public/index.html");
});**

5. bootstrap 소스가 다운로드 되었음에도 화면에 css가 적용되지 않음

이유는 express내의 라우터가 모든 요청을 인터셉트하기 때문에 static요소들에 직접적인 접근이 불가능하기 때문

6. 이를 해결하기 위해 server.js에 미들웨어를 사용하겠다는 코드를 추가

express 모듈의 static 메소드를 사용해 static resource를 직접적으로 찾도록 설정, 라우팅 코드가 필요 없기 때문에 주석 처리

const express = require("express");
const app = express();

**app.use(express.static("public"));**

app.listen(8080, function () {
  console.log("server ready...");
});

// app.get("/", function (req, res) {
//   res.sendFile(__dirname + "/public/index.html");
// });

7. 화면 확인

8. MySQL 드라이버 설치

npm i node-mysql
  • Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client

    • MySQL 서버가 caching_sha2_password 인증 플러그인을 사용하는 경우 발생

    • MySQL 클라이언트 라이브러리 업그레이드를 통해 해결

npm install mysql2

9. server.js에서 MySQL 설정을 하고 /list 라우팅

**const mysql = require("mysql2");
const conn = mysql.createConnection({
  host: "localhost",
  user: "test",
  password: "a12341234!",
  database: "myboard",
});

conn.connect();**

const express = require("express");
const app = express();

app.use(express.static("public"));

app.listen(8080, function () {
  console.log("server ready...");
});

**app.get("/list", function (req, res) {
  conn.query("select * from post", function (err, rows, fields) {
    if (err) throw err;
    console.log(rows);
    res.send(rows);
  });
});**

conn.query

conn.query 함수는 세 가지 주요 매개변수를 가짐

1. err

  • err는 쿼리를 실행하는 동안 발생한 오류를 나타내는 매개변수

  • 쿼리 실행 중 오류가 발생하면 err 객체에 오류 정보가 담김

  • if (err) throw err; 구문을 통해 오류가 발생했을 때 예외를 던져 서버가 적절히 오류를 처리하도록 함

  • 일반적으로 오류 객체에는 오류 메시지, 오류 코드, 스택 트레이스 등의 정보가 포함

2. rows

  • rows는 쿼리 결과로 반환된 행(row)들을 담고 있는 배열

  • select * from post 쿼리의 결과로 반환된 데이터베이스 테이블의 각 행이 이 배열의 요소로 포함됨

  • 각 요소는 데이터베이스 테이블의 하나의 행을 나타내며, 객체 형태로 필드 이름과 값을 포함

  • 예를 들어, rows 배열의 첫 번째 요소는 {id: 1, title: 'First Post', content: 'This is the first post.'} 와 같은 형태일 수 있음

3. fields

  • fields는 쿼리 결과의 각 필드(column)에 대한 메타데이터를 담고 있는 배열

  • 각 필드는 데이터베이스 테이블의 컬럼에 해당하며, 이름, 데이터 유형, 기본값 등의 정보를 포함

  • 이 배열은 일반적으로 쿼리 결과의 구조를 이해하거나 결과를 가공할 때 사용될 수 있음

  • 예를 들어, fields 배열의 첫 번째 요소는 {name: 'id', type: 'INT', ...} 와 같은 형태일 수 있음

10. XHR 객체 사용

  • 지금까지의 순서대로 수행했다면 브라우저 화면에 출력되지 않고 서버 콘솔에 출력될 것

  • 현재는 동기 방식의 요청 처리를 하고 있기 때문에 처리 결과를 주면 이전 화면이 유지되지 않는 문제를 해결하기 위해 XHR 객체 사용

index.html
index.html의 적당한 위치에 다음 두 라인을 추가

```
...
<button id="testBtn">test</button>
...
<script src="js/my.js></script>
```

my.js

testBtn.addEventListener("click",  function () {
   var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
           // Typical action to be performed when the document is ready:
           document.getElementById("demo").innerHTML = xhttp.responseText;
        }
    };
    xhttp.open("GET", "/list", true);
    xhttp.send();
});

11. fetch()라는 내장 함수를 이용하도록 my.js를 수정

testBtn.addEventListener("click", async function () {

  let resObj = await fetch("/list");  // "/list" 경로로 GET 요청을 보내고 응답을 기다림
  
  let data = await resObj.json();  // 응답 본문을 JSON 형식으로 파싱


  let displayData =  // 데이터를 HTML 테이블로 변환하여 표시
    `<thead>
        <td>ID</td>
        <td>Title</td>
        <td>writer</td>
        <td>created</td>
    </thead>
    <tbody>`;
    data.forEach((item,index) => {
        displayData +=
            `<tr>
                <td>${item.id}</td>
                <td>${item.title}</td>
                <td>${item.profile_id}</td>
                <td>${item.created}</td>
            </tr>`
    });

    displayData += `</tbody>`;
    
    document.getElementById("datatablesSimple").innerHTML = displayData;
});

fetch 함수

  • fetch 함수는 네트워크 요청을 생성하고, 원격 서버와의 데이터를 교환하는 데 사용
    기본적으로 HTTP GET 요청을 보냄

동작 과정

1. 요청 생성

  • fetch("/list")는 현재 도메인에서 "/list" 경로로 HTTP GET 요청을 생성
  • 이 요청은 웹 브라우저를 통해 서버로 전송

2. 서버 처리

  • 서버는 "/list" 경로에 대한 요청을 처리, 서버 코드에서 "/list"경로에 대한 핸들러가 정의되어 있어야 함
app.get("/list", function (req, res) {
    conn.query("select * from post", function (err, rows, fields) {
        if (err) throw err;
        res.send(rows);
    });
});

이 핸들러는 데이터베이스에서 post 테이블의 모든 데이터를 조회하고, 이를 클라이언트로 응답으로 보냄

3. 응답 처리

  • 클라이언트 측에서는 fetch("/list") 호출이 완료되면 Promise가 반환

  • await 키워드를 사용하여 이 Promise가 해결될 때까지 기다릴 수 있음

  • 응답이 성공적으로 도착하면 Response 객체가 반환, 이를 통해 응답 본문을 다양한 형식으로 처리할 수 있음(JSON, 텍스트, ...)

1. 서버 측에서는 "/list" 경로로 들어오는 GET 요청을 처리하여 데이터베이스의 post 테이블에서 모든 데이터를 가져와 응답으로 보냄

2. 클라이언트 측에서는 페이지 로드 시 fetch 함수를 사용하여 서버로부터 데이터를 비동기적으로 가져옴

3. 데이터를 JSON 형식으로 파싱하고, 이를 HTML 테이블 형식으로 변환

4. 변환된 HTML 문자열을 data-table 아이디를 가진 테이블 요소에 삽입

이런 원리로 서버로부터 데이터를 가져와서 동적으로 HTML 테이블에 표시함

12. sql에 오타를 넣어봄

서버 실행 중 오류가 났을 때 server.js에서 이 오류를 리턴해보기 위해 일부러 sql에 오타를 넣어봄

const mysql = require("mysql");
const conn = mysql.createConnection({
  host: "localhost",
  user: "test",
  password: "1234",
  database: "myboard",
});

conn.connect();

const express = require("express");
const app = express();

app.use(express.static("public"));

app.listen(8080, function () {
  console.log("8080 server ready...");
});

app.get("/list", function (req, res) {
  conn.query(
    `**selectt** post.id, title, content,created, writer,email 
from post
left join profile
on post.profile_id=profile.id`,
    function (err, rows, fields) {
      if (err) {
        throw err;
      } else {
        res.send(rows);
      }
    }
  );
});

13. my.js에서는 오류가 났을 때 alert를 띄우도록 함

const getPostListBtn = document.getElementById("getPostListBtn");
getPostListBtn.addEventListener("click", async function () {
  // var xhttp = new XMLHttpRequest();
  // xhttp.onreadystatechange = function() {
  //     if (this.readyState == 4 && this.status == 200) {
  //        // Typical action to be performed when the document is ready:
  //         //document.getElementById("demo").innerHTML = xhttp.responseText;
  //         console.log(xhttp.responseText);
  //     }
  // };
  // xhttp.open("GET", "/list", true);
  // xhttp.send();
  try {
    let resObj = await fetch("/list");
    let data = await resObj.json();
    //console.log(data);

    let displayData = `<thead>
          <td>ID</td>
          <td>Title</td>
          <td>writer</td>
          <td>created</td>
      </thead>
      <tbody>`;
    data.forEach((item, index) => {
      displayData += `<tr>
                  <td>${item.id}</td>
                  <td>${item.title}</td>
                  <td>${item.writer}</td>
                  <td>${item.created}</td>
              </tr>`;
    });

    displayData += `</tbody>`;

    document.getElementById("datatablesSimple").innerHTML = displayData;
**  } catch (err) {
    alert("게시물 가져오기 실패");
  }**
});

14. 화면에서 title 부분에 <a>태그를 넣어봄

<td><a href='#'>${item.title}</a></td>

15. Title을 클릭했을 때 모달을 띄우기

index.html
button 태그 위에 modal 태그 추가

<div id="myModal" class="modal">
    <div class="modal-content">
         <span class="close" id="closeBtn">&times;</span>
         <p id="modalText">모달</p>
    </div>
</div>
<button id="getPostListBtn">데이터 가져오기</button>

my.js
event 추가

const getPostListBtn = document.getElementById("getPostListBtn");
getPostListBtn.addEventListener("click", async function () {
    // var xhttp = new XMLHttpRequest();
    // xhttp.onreadystatechange = function() {
    //     if (this.readyState == 4 && this.status == 200) {
    //        // Typical action to be performed when the document is ready:
    //         //document.getElementById("demo").innerHTML = xhttp.responseText;
    //         console.log(xhttp.responseText);
    //     }
    // };
    // xhttp.open("GET", "/list", true);
    // xhttp.send();
    try {
      let resObj = await fetch("/list");
      let data = await resObj.json();
      //console.log(data);
  
      let displayData = `<thead>
            <td>ID</td>
            <td>Title</td>
            <td>writer</td>
            <td>created</td>
        </thead>
        <tbody>`;
      data.forEach((item, index) => {
        displayData += `<tr>
                    <td>${item.id}</td>
                    <td class="title-cell" data-content="${item.content}"><a href="#">${item.title}</a></td>
                    <td>${item.writer}</td>
                    <td>${item.created}</td>
                </tr>`;
      });
  
      displayData += `</tbody>`;
  
      document.getElementById("datatablesSimple").innerHTML = displayData;
  } catch (err) {
      alert("게시물 가져오기 실패");
    }
  

  // Title cell에 클릭 이벤트 추가, 클릭 시 모달 열기(가시화)
  let titleCells = document.querySelectorAll(".title-cell");
  titleCells.forEach(cell => {
    cell.addEventListener("click", function () {
        document.getElementById("modalText").innerText = cell.getAttribute("data-content");
        document.getElementById("myModal").style.display = "block";
    });
  });

});

  // 클릭 시 모달 닫기 기능
  document.querySelector(".close").addEventListener("click", function () {
    document.getElementById("myModal").style.display = "none";
  });

  // 모달 외부 클릭 시 닫기
  window.addEventListener("click", function (event) {
    if(event.target == document.getElementById("myModal")) {
        document.getElementById("myModal").style.display = "none";
    }
  });

style.css
css 추가

// display: none 속성으로 버튼 클릭 전엔 모달이 보이지 않음
.modal {
  display: none;
  position: fixed;
  z-index: 1;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: auto;
  background-color: rgb(0,0,0);
  background-color: rgba(0,0,0,0.4);
  padding-top: 60px;
}

.modal-content {
  background-color: #fefefe;
  margin: 5% auto;
  padding: 15px;
  border: 1px solid #888;
  width: 30%;
  height: 30%;
}

.close {
  color: #aaa;
  float: right;
  font-size: 28px;
  font-weight: bold;
}

.close:hover,
.close:focus {
  color: black;
  text-decoration: none;
  cursor: pointer;
}

1개의 댓글

comment-user-thumbnail
2024년 6월 19일

유용한정보 감사합니다!

답글 달기