[20/12/04] - TIL ⎮ ChatterBox Database

12월 04일 (금)

🌻 Today I Learned Database with MySQL

👨🏼‍💻 ChatterBox Database


이번 스프린트는 Chatterbox Server가 영속적인 데이터를 가질 수 있도록 만드는 것이다. MySQL을 통해 나의 로컬 데이터베이스에 데이터들을 저장하고, 서버가 재시작해도 저장된 데이터가 지워지지 않게 해야한다.


1. 스키마 작성


먼저 Chatterbox Server에 필요한 스키마를 디자인했다. server/schema.sql 파일에서 데이터베이스 테이블의 구조를 정의하고 MySQL 서버에 로드할 CREATE TABLE 문을 작성했다. messages 테이블 구조에는 username, text, date, roomname이 포함되고, users 테이블 구조에는 username이 포함되게 작성했다.

schema.sql

DROP DATABASE IF EXISTS chat; // 만약 이미 데이터베이스가 존재하면
CREATE DATABASE chat;         // 없애고 새로 만든다
USE chat;

// messages 테이블
CREATE TABLE messages (
  id int not NULL PRIMARY KEY AUTO_INCREMENT,
  username varchar(255) not NULL,
  text text(1024),
  date timestamp not NULL DEFAULT CURRENT_TIMESTAMP,
  roomname varchar(255) not NULL
);

// users 테이블
CREATE TABLE users (
  id int not NULL PRIMARY KEY AUTO_INCREMENT,
  username varchar(255) not NULL
);

아래 사진은 mysql -u root -p < server/schema.sql을 통해 내 MySQL server에 작성한 schema.sql을 로드하고 mysql에서 해당 내용을 확인한 모습이다.

2. 서버


자, 이제 서버를 작성하고 실행해야 한다. 먼저 MySQL 비밀번호를 보안상/편의상 이유로 환경 변수로 분리해놓았다. 아래 터미널 명령을 통해 환경 변수를 설정하고 node.js 상에서 process.env.DATABASE_SPRINT_PASSWORD라는 변수에서 설정한 값에 접근할 수 있게 했다.

  • $ export DATABASE_SPRINT_PASSWORD='my_value'

그런데 이렇게 환경 변수를 설정하면 서로 다른 터미널에서는 접근이 안되는 점이 불편했고, dotenv를 사용하면 .env 파일을 통해 접근 가능하게 할 수 있다는 것을 배웠다.

다음으로는 주어진 server/app.js에서 express 프레임워크를 통한 node.js 서버를 실행시키고, server/db/index.js에서 mysql npm module을 사용하여 실행 중인 데이터베이스 서버에 연결한다.

app.js

var express = require('express');

// Middleware
var morgan = require('morgan');
var parser = require('body-parser');

// Router
var router = require('./routes.js');

var app = express();
module.exports.app = app;

// Set what we are listening on.
app.set('port', 3000);

// Logging and parsing
app.use(morgan('dev'));
app.use(parser.json());

// Set up our routes
app.use('/classes', router);

// Serve the client files
app.use(express.static(__dirname + '/../client'));

// If we are being run directly, run the server.
if (!module.parent) {
  app.listen(app.get('port'));
  console.log('Listening on', app.get('port'));
}

db/index.js

const mysql = require('mysql');
const password = process.env.DATABASE_SPRINT_PASSWORD;
const host = 'localhost';

// user는 root, 패스워드는 위 password 변수
// 실제로 연결할 데이터베이스의 위치(host)는 host 변수
// 데이터베이스 이름(database)은 "chat"
// 데이터베이스 연결을 만들고, 연결 객체 export
module.exports = mysql.createConnection({
  host,
  user: 'root',
  password,
  database: 'chat',
});

server/models/index.js에서는 메시지와 사용자 모델을 정의한다. SQL query문을 작성하여 실질적인 데이터를 데이터베이스에서 가져와 controller에게 넘겨주는 역할을 담당한다.

비동기 호출을 구현하기 위해 프로미스 객체를 넘겨주었고 코드는 w3schools.com에 Node.js MySQL을 참고하여 작성했다.

models/index.js

// 위에서 작성한 db/index.js에서 연결 객체를 가져오고
var db = require('../db');

// 연결한다
db.connect((err) => {
  if (err) throw err;
  console.log('Connected!');
});

module.exports = {
  messages: {
    // a function which produces all the messages
    get: function () {
      return new Promise((resolve, reject) => {
        db.query('SELECT * FROM messages', function (err, result) {
          if (err) reject(err.message);
          resolve(result);
        });
      });
    },
    // a function which can be used to insert a message into the database
    post: function (data) {
      return new Promise((resolve, reject) => {
        const sql = 'INSERT INTO messages (username, text, roomname) VALUES ?';
        const values = [data.username, data.text, data.roomname];
        db.query(sql, [[values]], function (err) {
          if (err) reject(err.message);
          db.query('SELECT * FROM messages', function (err, result) {
            if (err) reject(err.message);
            resolve(result);
          });
        });
      });
    },
  },
  // users: {...} 생략
};

server/controllers/index.js는 서버 내에서 클라이언트의 요청에 알맞게 모델 객체 안의 함수를 호출하고, 결과 데이터를 전달하는 역할을 한다.

controllers/index.js

// 위에서 작성한 models/index.js에서 모델 객체를 가져온다
var models = require('../models');

module.exports = {
  messages: {
    // a function which handles a get request for all messages
    get: function (req, res) {
      models.messages.get().then((result) => res.send(result));
    },
    // a function which handles posting a message to the database
    post: function (req, res) {
      models.messages
        .post(req.body)
        .then((result) => res.status(201).send(result));
    },
  },
  // users: {...} 생략
};

주어진 server/routes.js는 요청 URL에 따라 서버 라우팅 역할을 한다. node.js 웹 서버 코드 app.js에서 이 파일을 require하여 라우터로 사용한다.

routes.js

var controller = require('./controllers');
var router = require('express').Router();

//Connect controller methods to their corresponding routes
router.get('/messages', controller.messages.get);
router.post('/messages', controller.messages.post);
router.get('/users', controller.users.get);
router.post('/users', controller.users.post);

module.exports = router;

클라이언트 연결 몇 결과 👍


클라이언트 연결은 기존에 진행했던 Chatterbox client sprintreference 코드를 가져왔고, 현재 스프린트에 client 디렉토리를 만들어 그 안에 담았다. app.js에서 server URI를 나의 로컬 MySQL 서버로 바꿔주고, fetch 하는 부분과 render 하는 부분만 살짝 수정했다. 결과 화면이다.

서버를 실행시키고, 클라이언트 페이지를 새로고침하여 메시지 하나를 post 요청하면 MySQL 데이터베이스에 저장되어 나타나는 모습이다.

또한, 서버를 재시작하여도 데이터베이스에 남아있는 데이터를 영속성 있게 가져올 수 있는 모습이다.


느낀 점 🌻


이제는 클라이언트의 HTTP 요청으로 인한 데이터를 서버 컴퓨터의 in-memorynode.jsfs 모듈을 통한 파일이 아니라 서버와 연결된 데이터베이스에 저장할 수 있다. 데이터베이스에 대해 이해했고, 어떻게 사용하는지와 그 필요성을 인지하긴 했지만 아직 스프린트 코드의 model, view, controller에 대해서는 잘 모르겠다. 어째서 이렇게 나누는 것이고, 왜 필요한 것이고, 이것을 왜 MVC 디자인 패턴이라 부르는 것인가. 다음 스프린트에서 공부하게 될 내용이다. 그래서 이러한 형태의 코드를 데이터베이스 스프린트를 통해 먼저 접하게 한 것 같다.

배우는 단계라 오류가 있을 수 있습니다. 틀린 내용은 댓글 달아주시면 수정하겠습니다. 감사합니다 :)

좋은 웹페이지 즐겨찾기