본문 바로가기

토이 프로젝트

[자바스크립트] 작은 도서관 프로그램 만들기 (MySQL, Express, ejs, Node.js) - Mysql, express, ejs crud example

2020/09/03 - [토이 프로젝트] - [자바스크립트] 작은 도서관 프로그램 만들기 (쿠키, 로컬, 세션 스토리지의 개념 포함)

 

[자바스크립트] 작은 도서관 프로그램 만들기 (쿠키, 로컬, 세션 스토리지의 개념 포함)

회사에서 내준 작은 프로젝트 과제이다. 힘들었던 부분은 외부 라이브러리를 일절 사용하지 않아야 한다는 점이었다. 아래는 개발 제약 사항과 화면 요구 사항이다. // 개발 제약 사항 // 책의 추

ukcasso.tistory.com

  지난번에 만들었던 작은 도서관 프로그램이다. 그 당시에는 LocalStorage라는 것을 이용해 데이터를 해당 ip주소에 맞게 저장하는 개념을 사용하였다. 하지만 이번에는 조금더 제대로 된 사이트를 만들어보고자 MySQL과 ejs그리고 Node.js를 활용하여 CRUD를 정확히 구현할 계획이다. MySQL은 얼마전 포스팅한 자료들이 많으니 블로그에서 MySQL 관련 글을 활용하면 될것같다.

 

 

 

 

사전 작업

npm install --save express

npm install --save mysql

npm install --save sequelize

npm install --save body-parser

npm install --save-dev sequelize-cli

 


PART 1 - 분할된 파일들

app.js

let express = require('express');
let app = express();
let db_config = require(__dirname + '/config/database.js');
let conn = db_config.init();
let bodyParser = require('body-parser');

db_config.connect(conn);

app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended : false}));
app.use(express.static('public'));
app.use(express.static('.'));

app.get('/', function (req, res) {
  let sql = 'SELECT * FROM bookstable';    
  conn.query(sql, function (err, result, fields) {
    if(err) console.log('query is not excuted. select fail...\n' + err);
    else res.render('index.ejs', {list : result});
  });
});


app.post('/find', function (req, res) {
  let searchInput = req.body.searchInput;
  let dateStart = req.body.dateStart;
  let dateEnd = req.body.dateEnd;
  let sql = 'SELECT * FROM bookstable WHERE title|content|author = ? OR date BETWEEN ? AND ?';
  let params = [searchInput, dateStart, dateEnd];
  conn.query(sql, params, function(err, result) {
    console.log(result)
    if(err) console.log('query is not excuted. search fail...\n' + err);
    else res.render('index.ejs', {list : result});
  });
});

app.post('/inputBook', function (req, res) {
  let title = req.body.title;
  let content = req.body.content;
  let author = req.body.author;
  let date = req.body.date;
  let sql = 'INSERT INTO bookstable VALUES(id, ?, ?, ?, ?)';
  let params = [title, content, author, date];
  console.log(sql);
  conn.query(sql, params, function(err, result, fields) {
    console.log(result)
    if(err) {
      console.log('query is not excuted. input fail...\n' + err);
    }
    else {
      res.redirect('/')
    }
  });
});

app.post('/edit/:id', function (req, res) {
  let title = req.body.title;
  let content = req.body.content;
  let author = req.body.author;
  let date = req.body.date;
  let id = req.params.id;
  console.log(id);
  let sql = `UPDATE bookstable SET title=?, content=?, author=?, date=? WHERE id=?`;
  let params = [title, content, author, date, id];
  console.log(sql);
  conn.query(sql, params, function(err) {
    if(err) {
      console.log('query is not excuted. edit fail...\n' + err);
    }
    else {
      res.redirect('/');
    }
  });
});

app.get('/delete/:id', function (req, res) {
  let sql = 'DELETE FROM bookstable WHERE id=?';
  console.log(req.params.id)
  conn.query(sql, [req.params.id], function() {
    res.redirect('/');
  })
})

app.listen(3000, () => console.log('Server is running on port 3000...'));

 

 

package.json

{
  "name": "test_libaray",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.0",
    "ejs": "^3.1.5",
    "express": "^4.17.1",
    "mysql": "^2.18.1"
  }
}

 

 

/public/library.js

function popInfo(id, title, content, author, date) {
  console.log(id);
  document.getElementById("title").value = title;
  document.getElementById("content").value = content;
  document.getElementById("author").value = author;
  document.getElementById("date").value = date;
};

function btnUrl(id, btn) {
  if(btn === "putBtn") {
    frm.action = "/inputBook";
  } else if(btn === "editBtn") {
    frm.action = "/edit/" + id;
  } else if(btn === "deleteBtn") {
    frm.action = "/delete/" + id;
    frm.method = "GET";
  }
}

 

 

/config/database.js

let mysql = require('mysql');
let db_info = {
  host: 'localhost',
  port: '3306',
  user: 'root',
  password: ****,
  database: 'library'
}

module.exports = {
  init: function() {
    return mysql.createConnection(db_info);
  },
  connect: function(conn) {
    conn.connect(function(err) {
      if(err) {
        console.log('mysql connection error : ' + err);
      } else {
        console.log('mysql is connected successfully!');
      };
    });
  }
}

 

 

/views/index.ejs

<!DOCTYPE html>
<html>
    <head>
        <title>작은 도서관</title>
    </head>
    <style>
        .dateSet {
            width: 110px;
            height: 16px;
        }
        #logo {
            background-color: #5E6698;
            width: 950px;
            padding: 5px;
        }
        #search {
            background-color: #5E6698;
            width: 950px;
            padding: 5px;
        }
        #table {
            background-color: white;
            width: 950px;
            padding: 5px;
            height: 275px;
            overflow: auto;
        }
        #newBooks {
            background-color: #5E6698;
            width: 950px;
            padding: 5px;
        }
        #searchInput {
            width: 440px;
        }
        #searchBtn {
            width: 100px;
        }
        #putBtn {
            width: 400px;
            height: 50px;
        }
        #title {
            width: 495px;
        }
        #content {
            width: 798px;
            height: 100px;
            resize: none;
        }
        table {
            background-color: white;
            width: 810px;
            table-layout: fixed;
            word-break: break-all;
        }
        tr {
            background-color: white;
            padding: 10px;
            border: 0px solid black;
        }
        th {
            padding: 10px;
            border: 0px solid black;
        }
        td {
            padding: 10px;
            border: 0px solid black;
            text-align: center;
            white-space: nowrap;
            text-overflow: ellipsis;
            overflow: hidden;
        }
        a {
            color: black;
            text-decoration: none;
        }
        a:hover {
            color: rgb(74, 138, 211);
        }
    </style>
    <body>
        <div id="library">
            <center>
                <div id="logo"><br>
                    <center>
                        <a href="/"><img src="../vendit-logo-white.png" onclick="location.reload()" width="150px"></a>
                    </center>
                </div>
            </center>
            <center>
                <div id="search">
                    <center>
                        <!--<b>서적 검색</b><br>-->
                        <form action="/find" method="POST">
                          <input type="text" id="searchInput" name="searchInput" placeholder="제목, 내용, 저자로 검색">
                          <input class="dateSet" type="date" id="dateStart" name="dateStart">
                          ~
                          <input class="dateSet" type="date" id="dateEnd" name="dateEnd">
                          <button id="searchBtn" type="submit">검색</button>
                        </form>
                    </center><br>
                </div>
            </center>
            <center>
                <div id="table">
                    <!--<b>찾은 서적 목록</b><br>-->
                    <center>
                        <table>
                            <thead>
                                <tr><th>순번</th><th>제목</th><th>내용</th><th>저자</th><th>출간날짜</th><th>수정/삭제</th></tr>
                            </thead>
                            <tbody id="bTable">
                              <%
                              for(let i = 0; i < list.length; i++)
                              {
                              %>
                              <tr>
                                <td><%=i+1%></td>
                                <td><a href="#" id='<%=list[i].id%>' onclick="popInfo(this.id, '<%=list[i].title%>', '<%=list[i].content%>', '<%=list[i].author%>', '<%=list[i].date%>')"><%=list[i].title%></a></td>
                                <td><%=list[i].content%></td>
                                <td><%=list[i].author%></td>
                                <td><%=list[i].date%></td>
                                <td>
                                  <form name="frm" method="POST">
                                  <button id="<%=list[i].id%>" type="submit" onclick='btnUrl(this.id ,"editBtn")' style="float:left">수정</button>                                  
                                  <button id="<%=list[i].id%>" type="submit" onclick='btnUrl(this.id, "deleteBtn")' style="float:right">삭제</button>                                
                                </td>
                              </tr>
                              <%
                              }
                              %>
                              <%
                              for(let j = 0; j < 5 - list.length; j++)
                              {
                              %>
                              <tr>
                                <td>-</td>
                                <td>-</td>
                                <td>-</td>
                                <td>-</td>
                                <td>-</td>
                                <td>-</td>
                              </tr>
                              <%
                              }
                              %>
                            </tbody>
                        </table>
                    </center>    
                </div>
            </center>
            <center>
                <div id="newBooks">
                    <center><br>
                        <%
                        if(list.length === 0) {
                        %>
                        <form name="frm" method="POST">
                        <%
                        }
                        %>
                        <!--<b>새로운 서적 등록</b><br>-->
                          <input class="new" type="text" id="title" name="title" placeholder="제목" > 
                          <input class="new" type="text" id="author" name="author" placeholder="저자" >
                          <input class="dateSet" type="date" id="date" name="date" placeholder="출간날짜" ><br><br>
                          <textarea type="text" id="content" name="content" placeholder="내용"></textarea><br><br>
                          <button id="putBtn" type="submit" onclick = 'btnUrl(this.id ,"putBtn")'>등록</button>                          
                        </form>
                    </center><br>
                </div>
            </center>
        </div>
        <script src="/library.js"></script>
        <script>
          
        </script>
    </body>
</html>

 


 

PART 2 - 하나로 합친 파일

/server/index.js

'use strict';
const mysql = require('mysql');
const express = require('express');
const path = require('path');
const app = express();
const { Sequelize, DataTypes } = require('sequelize');
const Op = Sequelize.Op;
const env = process.env.NODE_ENV || 'development';
const config = {
  "development": {
    "username": "root",
    "password": ****,
    "database": "library",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "database_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": null,
    "database": "database_production",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}[env];

let http = require('http');
let bodyParser = require('body-parser');
let sequelize;
if (config.use_env_variable) {
  sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
  sequelize = new Sequelize(config.database, config.username, config.password, config);
}

const books = sequelize.define('books', {
  title: {
    type: DataTypes.STRING(200),
    allowNull: true,
  },
  content: {
    type: DataTypes.STRING,
    allowNull: true,
  },
  author: {
    type: DataTypes.STRING,
    allowNull: true,
  },
  date: {
    type: DataTypes.DATE,
    allowNull: true,
  }
}, {
  // Other model options go here
});

// 한번만 실행
sequelize.sync({ force: false }).then( () => {
  console.log("db 연결 완료");
  
}).catch(err => {
  console.log("db 연결 실패");
  console.log(err);
});


app.set('views', '../');
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended : false}));

app.get('/', async(req, res) => { 
  await books.findAll().then(result => {
    res.json(result)
    res.render('index', {
      list : result
    });
  });
});



app.post('/inputBook', async (req, res) => {
  await books.create({
    title : req.body.title,
    content : req.body.content,
    author : req.body.author,
    date : req.body.date
  }).then(result => {
    console.log("success input data" + result);
    res.redirect('/');
  }).catch(err => {
    console.log("fail update data" + err);
  });
});

app.post('/edit/:id', async(req, res) => {
  await books.update({
    title : req.body.title,
    content : req.body.content,
    author : req.body.author,
    date : req.body.date
  }, {
    where : {id: req.params.id}
  }).then(result => {
    console.log("success update data" + result);
    res.redirect('/');
  }).catch(err => {
    console.log("fail update data" + err)
  })
})

app.get('/delete/:id', async(req, res) => {
  await books.destroy({
    where: {id: req.params.id}
  }).then(result => {
    console.log(result)
    res.redirect('/');
  }).catch(err => {
    console.log("데이터 삭제" + err);
  });
});

app.post('/find', async(req, res) => {
  let searchInput = req.body.searchInput;
  let dateStart = req.body.dateStart;
  let dateEnd = req.body.dateEnd;
  await books.findAll({
    where : {
      [Op.or]: [
        {
          title : {
            [Op.like]:"%" + searchInput + "%"
          }
        },
        {
          content : {
            [Op.like]:"%" + searchInput + "%"
          }
        },
        {
          author : {
          [Op.like]:"%" + searchInput + "%"
          }
        },
        {
          date : {
          [Op.between]: [new Date(dateStart), new Date(dateEnd)]
          }
        }
      ]
    }
  }).then(result => {
    res.render('index', {list: result})
  })
});

http.createServer(app).listen(3000, () => {'Server is running on port 3000...'})

 

 

index.ejs

<!DOCTYPE html>
<html>
    <head>
        <title>작은 도서관</title>
    </head>
    <style>
        .dateSet {
            width: 110px;
            height: 16px;
        }
        #logo {
            background-color: #5E6698;
            width: 950px;
            padding: 5px;
        }
        #search {
            background-color: #5E6698;
            width: 950px;
            padding: 5px;
        }
        #table {
            background-color: white;
            width: 950px;
            padding: 5px;
            height: 275px;
            overflow: auto;
        }
        #newBooks {
            background-color: #5E6698;
            width: 950px;
            padding: 5px;
        }
        #searchInput {
            width: 440px;
        }
        #searchBtn {
            width: 100px;
        }
        #putBtn {
            width: 400px;
            height: 50px;
        }
        #title {
            width: 495px;
        }
        #content {
            width: 798px;
            height: 100px;
            resize: none;
        }
        table {
            background-color: white;
            width: 810px;
            table-layout: fixed;
            word-break: break-all;
        }
        tr {
            background-color: white;
            padding: 10px;
            border: 0px solid black;
        }
        th {
            padding: 10px;
            border: 0px solid black;
        }
        td {
            padding: 10px;
            border: 0px solid black;
            text-align: center;
            white-space: nowrap;
            text-overflow: ellipsis;
            overflow: hidden;
        }
        a {
            color: black;
            text-decoration: none;
        }
        a:hover {
            color: rgb(74, 138, 211);
        }
    </style>
    <body onload="init()">
        <div id="library">
            <center>
                <div id="logo" style="color: white; font-size: 30px;"><br>
                  <b>도서관리 프로그램</b>
                  <br><br>
                </div>
            </center>
            <center>
                <div id="search">
                    <center>
                        <!--<b>서적 검색</b><br>-->
                        <form action="/find" method="POST">
                          <input type="text" id="searchInput" name="searchInput" placeholder="제목, 내용, 저자로 검색">
                          <input class="dateSet" type="date" id="dateStart" name="dateStart">
                          ~
                          <input class="dateSet" type="date" id="dateEnd" name="dateEnd">
                          <button id="searchBtn" type="submit">검색</button>
                        </form>
                    </center><br>
                </div>
            </center>
            <center>
                <div id="table">
                    <!--<b>찾은 서적 목록</b><br>-->
                    <center>
                        <table>
                            <thead>
                                <tr><th>순번</th><th>제목</th><th>내용</th><th>저자</th><th>출간날짜</th><th>수정/삭제</th></tr>
                            </thead>
                            <tbody id="bTable">
                              <%
                              for(let i = 0; i < list.length; i++)
                              {
                              %>
                              <tr>
                                <td><%=i+1%></td>
                                <td><a href="#" id='<%=list[i].id%>' onclick="popInfo(this.id, '<%=list[i].title%>', '<%=list[i].content%>', '<%=list[i].author%>', '<%=list[i].date%>')"><%=list[i].title%></a></td>
                                <td><%=list[i].content%></td>
                                <td><%=list[i].author%></td>
                                <td><%=list[i].date%></td>
                                <td>
                                  <form name="frm" method="POST">
                                  <button id="<%=list[i].id%>" type="submit" onclick='btnUrl(this.id ,"editBtn")' style="float:left">수정</button>                                  
                                  <button id="<%=list[i].id%>" type="submit" onclick='btnUrl(this.id, "deleteBtn")' style="float:right">삭제</button>                                
                                </td>
                              </tr>
                              <%
                              }
                              %>
                              <%
                              for(let j = 0; j < 5 - list.length; j++)
                              {
                              %>
                              <tr>
                                <td>-</td>
                                <td>-</td>
                                <td>-</td>
                                <td>-</td>
                                <td>-</td>
                                <td>-</td>
                              </tr>
                              <%
                              }
                              %>
                            </tbody>
                        </table>
                    </center>    
                </div>
            </center>
            <center>
                <div id="newBooks">
                    <center><br>
                        <%
                        if(list.length === 0) {
                        %>
                        <form name="frm" method="POST">
                        <%
                        }
                        %>
                        <!--<b>새로운 서적 등록</b><br>-->
                          <input class="new" type="text" id="title" name="title" placeholder="제목" > 
                          <input class="new" type="text" id="author" name="author" placeholder="저자" >
                          <input class="dateSet" type="date" id="date" name="date" placeholder="출간날짜" ><br><br>
                          <textarea type="text" id="content" name="content" placeholder="내용"></textarea><br><br>
                          <button id="putBtn" type="submit" onclick = 'btnUrl(this.id ,"putBtn")'>등록</button>                          
                        </form>
                    </center><br>
                </div>
            </center>
        </div>
        <script>
        function popInfo(id, title, content, author, date) {
          console.log(id);
          document.getElementById("title").value = title;
          document.getElementById("content").value = content;
          document.getElementById("author").value = author;
          document.getElementById("date").value = date;
        };

        function btnUrl(id, btn) {
          if(btn === "putBtn") {
            frm.action = "/inputBook";
          } else if(btn === "editBtn") {
            frm.action = "/edit/" + id;
          } else if(btn === "deleteBtn") {
            frm.action = "/delete/" + id;
            frm.method = "GET";
          }
        }
        </script>
    </body>
</html>