React작업시 클라이언트와 서버를 각각 실행해줘야하는 불편함을 해결하기 위해 필요한 모듈을 설치해보겠습니다.

 

작업진행순서

  • nodemon과 nodemon concurrently모듈 설치
  • package.json 설정추가
  • 클라이언트 디렉토리에서 명령어 입력후 실행확인

 

 

클라이언트와 서버 동시실행 모듈 설치

먼저 nodemon모듈을 설치해줍니다.

npm install nodemon -g
yarn global add nodemon

 

그리고나서 nodemon concurrently를 설치해줍니다.

npm install nodemon concurrently --save-dev
yarn add nodemon concurrently --dev

 

 

 

package.json에서 scripts항목 추가

"scripts": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "server": "nodemon server/server.js",
    "dev": "concurrently \"nodemon server/server.js\" \"node scripts/start.js\"",
    "test": "node scripts/test.js"
  },

 

 

 

React프로젝트위치(클라이언트 디렉토리)에서 실행명령어 입력

npm run dev
yarn dev

 

Sequelize를 사용해 테이블명.js를 만들면 기본적으로 DB에 table생성시 '테이블명s'로 생성이 됩니다.

그런데 기존의 table명을 그대로 사용하고 싶고 끝에 's'표기를 사용하고 싶지 않다면 models/테이블명.js추가시 설정을 추가해줍니다.

 

module.exports = (sequelize, DataTypes) => {
    return sequelize.define(
        'sample1', 
        {
            name: {
                type: DataTypes.STRING(50),
                allowNull: true
            },
            email: {
                type: DataTypes.STRING(50),
                allowNull: true
            }
        },
        {
            charset: 'utf8',
            collate: 'utf8_general_ci',
            timestamps: false,
            freezeTableName: true,
        }
    )
};

 

테이블명, 데이터항목정의 부분 다음 기타설정 부분에 아래 코드를 추가해줍니다.

freezeTableName: true,

데이터삭제를 위한 기본준비

먼저 App.js에서 DB에 저장된 전체 목록을 화면에 보여주고 각 레코드마다 삭제버튼을 추가해줍니다.

import React, { Component } from "react";
import axios from 'axios';


class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      sample1List : [],
    }
  };

  componentDidMount() {
    this._getData();
  }

  _getData = async () => {
    const res = await axios.get('/get/data');
    this.setState({ 
      sample1List : res.data
    })
  }

  // 이후 추가할 코드영역
  
  render() {
    const { sample1List } = this.state;

    return(
      <div className='App'>
        <h3>Hello, You are testing React!</h3>

        <h4> Sample1 List </h4>

        {sample1List.length !== 0 ? 
        sample1List.map( (el, key) => {
          return(
            <div key={key}>
              <span> ID: {el.id} </span>/
              <span> NAME: {el.name} </span>/
              <span> EMAIL: {el.email} </span>
              <button onClick={() => this._delete(el)}>delete</button>
            </div>
          )
        })
        : <div>데이터가 없습니다.</div>}

      </div> 
    )
  };
};


export default App;

 

그리고 server.js에서 전체목록을 가져오기위한 코드를 넣어줍니다.

 

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

const sequelize = require('./models').sequelize;
const bodyParser = require('body-parser');

sequelize.sync();

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

const {
    Sample1,
    Sequelize: { Op }
  } = require('./models');
sequelize.query('SET NAMES utf8;');

app.get('/get/data', (req, res) => {
   Sample1.findAll()
    .then( result => { res.send(result) })
    .catch( err => { throw err })
})

// 이후 추가할 코드영역

const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
    console.log(`Server On : http://localhost:${PORT}/`);
})

 

 

 

 

데이터 삭제 - destroy({ where : {삭제대상} })

App.js에서 목록마다 추가된 삭제버튼 클릭시 삭제요청코드를 추가해줍니다.

 

  _delete = async (el) => {
    const remove = window.confirm(el.name + '을 삭제하시겠습니까?');

    if(remove) {
      const target = { id : el.id }
      const res = await axios('/delete/data', {
        method : 'POST',
        data : { 'delete' : target },
        headers: new Headers()
      })
      
      if(res.data) {
        alert('데이터를 삭제했습니다.')
        return window.location.reload();
      }
    }
  }

 

server.js에서 '/delete/data' 요청을 처리하기 위한 코드를 넣어줍니다.

 

app.post('/delete/data', (req, res) => {
    Sample1.destroy({
        where : { id : req.body.delete.id }
    })
    .then( res.sendStatus(200) )
    .catch( err => { throw err })
})

 

서버와 React를 실행하여 화면에서 확인해봅니다.

 

처음 로딩화면

 

마지막 목록 삭제버튼 클릭

 

삭제 완료된 화면

한가지 데이터 변경하는 방법, 여러개의 데이터를 변경하는 법을 알아보겠습니다. 

데이터변경 역시 조회와 작업내용은 비슷합니다. 먼저 변경테스트를 위한 기본코드를 준비하고 App.js와 server.js에 각각 화면용 렌더링/데이터요청 코드와 데이터처리코드를 추가해주겠습니다.

 

 

 

데이터변경을 위한 기본준비

먼저 App.js에서 DB에 저장된 전체 목록을 화면에 보여주고 각 레코드마다 수정버튼을 추가해줍니다. 

 

import React, { Component } from "react";
import axios from 'axios';


class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      sample1List : [],
    }
  };

  componentDidMount() {
    this._getData();
  }

  _getData = async () => {
    const res = await axios.get('/get/data');
    this.setState({ 
      sample1List : res.data
    })
  }

  // 이후 추가할 코드영역
  
  render() {
    const { sample1List } = this.state;

    return(
      <div className='App'>
        <h3>Hello, You are testing React!</h3>

        <h4> Sample1 List </h4>

        {sample1List.length !== 0 ? 
        sample1List.map( (el, key) => {
          return(
            <div key={key}>
              <span> ID: {el.id} </span>/
              <span> NAME: {el.name} </span>/
              <span> EMAIL: {el.email} </span>
              <button onClick={() => this._modify(el)}>modify</button>
            </div>
          )
        })
        : <div>데이터가 없습니다.</div>}
		
        <button onClick={this._modifyMulti}>modify 여러개</button>
      </div> 
    )
  };
};


export default App;

 

위의 onClick시 실행될 this._modify(el)부분은 해당하는 sample1List의 각 목록(el)을 인자로 넘겨 함수내부에서 해당하는 목록에 대한 값을 수정해주기 위함입니다. (_modify와 _modifyMulti함수는 이후에 추가해주겠습니다.)

server.js도 아래와 같이 코드를 준비해줍니다.

 

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

const sequelize = require('./models').sequelize;
const bodyParser = require('body-parser');

sequelize.sync();

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

const {
    Sample1,
    Sequelize: { Op }
  } = require('./models');
sequelize.query('SET NAMES utf8;');

app.get('/get/data', (req, res) => {
   Sample1.findAll()
    .then( result => { res.send(result) })
    .catch( err => { throw err })
})

// 이후 추가할 코드영역

const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
    console.log(`Server On : http://localhost:${PORT}/`);
})

 

 

 

한 개의 데이터변경 - update({ 변경할 값 }, { where : { 변경대상 }})

아래 코드는 name을 수정하기 위한 코드입니다.

App.js에서 _modify가 호출되면 prompt창을 통해 변경할 name값을 저장(변경할 대상을 확인하기 위해 id값을 함께 저장)하여 서버에 전송합니다. 수정완료응답이 도착하면 변경된 리스트를 다시 화면에 표시해주겠습니다.

_modify = async (el) => {
    const newName = prompt(el.name + '의 변경할 이름을 입력해주세요.')

    if(newName !== null) {
      const dataToModify = {
        newName : newName,
        id : el.id
      }

      const res = await axios('/modify/data', {
        method : 'POST',
        data : { 'modify' : dataToModify },
        headers: new Headers()
      })

      if(res.data) {
        console.log(res.data[0]);
        alert('이름이 수정되었습니다.')
        return window.location.reload();
      }
    }
  }

 

server.js에서 '/modify/data'에 대한 요청을 처리할 코드를 넣어줍니다.

 

app.post('/modify/data', (req, res) => {
    Sample1.update({ name : req.body.modify.newName }, {
        where : { id : req.body.modify.id }
    })
    .then( result => { res.send(result) })
    .catch( err => { throw err })
})

 

서버와 react를 실행하여 브라우저에서 확인해봅니다.

 

처음 로딩시 화면

 

마지막 목록의 modify버튼을 클릭하여 이름 수정요청
수정된 이름이 적용됨

 

 

 

여러 개의 데이터 변경 - update({ 변경할 값 }, { where : {[Op.or] : [ 변경대상, 변경대상..]} }) 

id와 name으로 변경할 2개의 대상을 넘겨 모두 같은 이름으로 수정하는 작업을 테스트해보겠습니다.

App.js에서 아래 코드를 추가해줍니다. 'modify 여러개' 버튼 클릭시 변경할 2개의 대상 id/이름을 입력받습니다.

 

_modifyMulti = async (el) => {
    const targetId = prompt('변경할 대상 ID를 입력해주세요.')
    const targetName = prompt('변경할 대상 이름을 입력해주세요.')
    const newName = prompt('변경할 대상들의 새로운 이름을 입력해주세요.')

    if(newName !== null) {
      const dataToModify = {
        targetId : targetId,
        targetName : targetName,
        newName : newName
      }

      const res = await axios('/modify/multiData', {
        method : 'POST',
        data : { 'modify' : dataToModify },
        headers: new Headers()
      })

      if(res.data) {
        console.log(res.data[0]);
        alert('요청하신 대상들의 이름이 수정되었습니다.')
        return window.location.reload();
      }
    }
  }

 

server.js에서도'/modify/multiData'에 대한 처리코드를 넣어줍니다.

 

app.post('/modify/multiData', (req, res) => {
    Sample1.update({ name : req.body.modify.newName }, {
        where : { [Op.or]: [{ id : req.body.modify.targetId }, { name : req.body.modify.targetName }]}
    })
    .then( result => { res.send(result) })
    .catch( err => { throw err })
})

 

서버를 재실행하여 id : 1번과 name : anne4인 대상의 이름을 anne10으로 수정요청했습니다.

 

이번에는 브라우저화면에서 특정(sample1.js - sample1s in DB) table의 전체데이터 조회, 키워드로 해당데이터 조회, 여러개 키워드로 조회하는 방법을 알아보겠습니다. (기본 다른 세팅은 이전 글에서 설명한 것으로 이미 준비되었다고 가정하고 진행하겠습니다.)

 

진행할 작업 내용:

  • 사용할 데이터를 위한 state요소와 구성할 기본화면 준비
  • App.js에서 server로 데이터요청을 위한 코드 추가
  • server/server.js에서 각 요청url에 대한 처리코드 추가

 

 

 

데이터조회 테스트를 위한 기본준비 

state에서 관리할 name, email, sample1List를 준비하고 render될 화면에서는 데이터목록이 있을 때와 없을 때의 코드를 넣어줍니다. 받아올 데이터는 배열형태로 전달되기 때문에 map메소드를 사용했으며 Search버튼과 ListAll버튼의 실행할 함수는 진행과정에서 정의해주겠습니다. 

 

//App.js

import React, { Component } from "react";
import axios from 'axios';


class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      name : '',
      email : '',
      sample1List : [],
    }
  };

  componentDidMount() {
  }
  
  // 이후 추가할 코드 영역
  
  _nameUpdate(e) {
    this.setState({ name : e.target.value })
  }
  _emailUpdate(e) {
    this.setState({ email : e.target.value })
  }
  
  render() {
    const { sample1List } = this.state;

    return(
      <div className='App'>
        <h3>Hello, You are testing React!</h3>

        <h4> Sample1 List </h4>

        <input type='text' maxLength='10' placeholder='검색키워드(name)' onChange={(e) => this._nameUpdate(e)} />
        <input type='text' maxLength='20' placeholder='검색키워드(email)' onChange={(e) => this._emailUpdate(e)}/>
        <button onClick={this._getKeywordData}>Search</button>
        <button onClick={this._getData}>ListAll</button>

        {sample1List.length !== 0 ? 
        sample1List.map( (el, key) => {
          return(
            <div key={key}>
              <span> ID: {el.id} </span>/
              <span> NAME: {el.name} </span>/
              <span> EMAIL: {el.email} </span>
            </div>
          )
        })
        : <div>데이터가 없습니다.</div>}

      </div> 
    )
  };
};


export default App;
//server/server.js

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

const sequelize = require('./models').sequelize;
const bodyParser = require('body-parser');

sequelize.sync();

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

const {
    Sample1,
    Sequelize: { Op }
  } = require('./models');
sequelize.query('SET NAMES utf8;');

// 이후 추가할 코드 영역

const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
    console.log(`Server On : http://localhost:${PORT}/`);
})

 

(목록을 보여주기 위해 먼저 DB table에 데이터가 있는지 확인해주세요.)

지금 상태에서 서버와 react를 실행하면 화면에서 다음과 같이 나옵니다.

 

 

 

 

전체데이터 조회 - findAll()

ListAll버튼시 전체데이터를 요청하기 위해 App.js에 아래 코드를 추가해줍니다. 처음 화면의 모든 요소가 mount됬을 때에도 전체목록을 보여줄 것이므로componentDidMount()에도 전체목록 실행함수를 넣어줍니다.

 

  componentDidMount() {
    this._getData();
  }

  _getData = async () => {
    const res = await axios.get('/get/data');
    this.setState({ 
      sample1List : res.data
    })
  }

 

그리고 server/server.js에서 요청받은 '/get/data'에 대한 코드를 넣어줍니다.

 

app.get('/get/data', (req, res) => {
   Sample1.findAll()
    .then( result => { res.send(result) })
    .catch( err => { throw err })
}) 

 

위의 findAll()은 'SELECT FROM * sample1s'의 쿼리와 동일합니다.

그 다음 서버를 재실행하여 브라우저화면에서 잘 동작하는지 확인합니다.

 

 

 

 

키워드로 데이터검색 조회 - findAll( where : {xxx : yyy} )

이번에는 name에서 입력한 키워드로 데이터를 조회해보겠습니다. App.js에서 위에 추가한 코드에 이어 다음 코드를 넣어줍니다. 

 

  _getKeywordData = async() => {
    const res = await axios('/get/keywordData', {
      method : 'POST',
      data : { 
        'name' : this.state.name
     },
      headers: new Headers()
    });
    this.setState({ 
      sample1List : res.data
    })
  }

 

server.js에도 마찬가지로 아래 코드를 넣어주고 서버를 재실행합니다.

findAll({ where : {...}})은 'SELECT FROM * sample1s WHERE name LIKE 키워드'와 동일합니다.

 

app.post('/get/keywordData', (req, res) => {
    Sample1.findAll({
        where: { name : req.body.name }
    })
    .then( result => { res.send(result) })
    .catch( err => { throw err })
})

 

 

여러개 키워드로 데이터 조회 - findAll({ where: {[Op.or] : [{xxx : yyy}, {xxx : yyy}]} })

name과 email키워드로 데이터를 검색하는 방법을 알아보겠습니다.

App.js에서 아래의 함수를 넣어주고 검색버튼 클릭시 새로추가한 함수를 호출하도록 onClick시 함수명을 변경합니다.

 

  _getMultiKeywordData = async() => {
    const res = await axios('/get/multiKeywordData', {
      method : 'POST',
      data : { 
        'name' : this.state.name,
        'email' : this.state.email
     },
      headers: new Headers()
    });
    this.setState({ 
      sample1List : res.data
    })
  }
<button onClick={this._getMultiKeywordData}>Search</button>

 

그리고나서 '/get/multiKeywordData'에 대한 요청처리를 위한 코드를 server.js에 추가합니다.

 

app.post('/get/multiKeywordData', (req, res) => {
    Sample1.findAll({
        where: { [Op.or]: [{ name : req.body.name }, { email : req.body.email }] }
    })
    .then( result => { res.send(result) })
    .catch( err => { throw err })
})

 

Op.or은 Sequelize에서 or연산자로 데이터를 조회하기 위한 메소드입니다. or(또는)이므로 name과 email중 한개만 입력해도 해당리스트가 조회됩니다.  

그럼 서버를 재실행하여 브라우저에서 확인해보겠습니다.

 

 

 

한 개의 데이터만 조회 - findOne( where : {xxx : yyy} )

findOne은 where조건에 따라 table레코드를 한개만 가져오는 메소드입니다. findAll로 가져올 때랑 다른 점은 findAll의 경우 배열형태로 데이터목록을 담아보내지만 findOne은 객체형태로 보낸다는 점입니다. 그래서 App.js에서 setState를 할때 sample1List를 배열형태로 한번 감싸서 코드를 작성해줍니다.

App.js에서 키워드는 name값으로 하여 아래 함수를 추가해줍니다.

 

  _getKeywordSingleData = async() => {
    const res = await axios('/get/keywordSingleData', {
      method : 'POST',
      data : { 
        'name' : this.state.name
     },
      headers: new Headers()
    });
    this.setState({ 
      sample1List : [res.data]
    })
  }
<button onClick={this._getKeywordSingleData}>Search</button>

 

server.js에도 실행코드를 추가하고 서버를 재실행합니다.

 

app.post('/get/keywordSingleData', (req, res) => {
    Sample1.findOne({
        where: { name : req.body.name }
    })
    .then( result => { res.send(result) })
    .catch( err => { throw err })
})

 

브라우저에서 확인하면 모든 리스트의 공통키워드인 단어로 검색해도 첫번째 1개의 레코드만 출력됩니다.

 

브라우저 화면에서 폼태그로 입력된 값을 DB의 sample1s테이블(컬럼 : id, name, email)에 추가하는 방법을 알아보겠습니다.

 

진행할 작업내용 :

  • 화면단에서 보낸 데이터를 서버에서 읽을 수 있도록 body-parser모듈 설치
  • server/models/index.js에 대상table(sample1 - sample1s in DB)이 정의되었는지 확인
  • server/server.js에 sample1s테이블(from DB)을 서버로 불러와 읽을 수 있도록 코드 추가
  • server/server.js에 정해진 URI로 보내온 데이터를 DB에 저장하는 코드 추가
  • App.js에 폼태그화면 구성과 버튼클릭시 setState된 데이터를 서버로 전송하는 코드 작성
  • 최종 작업된 화면에서 데이터전송하여 DB저장여부 확인 

 

 

 

body-parser모듈 설치

npm install body-parser
yarn add body-parser

 

설치완료 후 server/server.js에 body-parser을 불러서 적용하는 코드를 넣어줍니다.

 

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

const sequelize = require('./models').sequelize;
const bodyParser = require('body-parser');

sequelize.sync();

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

const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
    console.log(`Server On : http://localhost:${PORT}/`);
})

 

 

 

models/index.js에서 데이터를 추가할 table정의

 

models/index.js의 전체 코드입니다.

 

'use strict';

const path = require('path');
const Sequelize = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require(path.join(__dirname, '..', 'config', 'db.json'))[ env ];
const db = {};

let sequelize = new Sequelize(
    config.database,
    config.username,
    config.password,
    config,
    {
      define: {
        charset: 'utf8',
        collate: 'utf8_general_ci'
      }
    }
  );
  
    db.sequelize = sequelize;
    db.Sequelize = Sequelize;

    db.sequelize
    .authenticate()
    .then(() => {
        console.log('Connection has been established successfully.');
    })
    .catch(err => {
        console.log('Unable to connect to the database: ', err);
    });

    db.Sample1 = require('./sample1')(sequelize, Sequelize);

db.secret = '(9*)5$&!3%^0%^@@2$1!#5@2!4';
module.exports = db;

 

 

 

 

DB의 대상table을 연결하는 코드를 server/server.js에 추가

bodyParser을 사용하는 코드 아래에 Sample1테이블을 읽어오는 코드를 추가합니다.

 

const {
    Sample1,
    Sequelize: { Op }
  } = require('./models');
sequelize.query('SET NAMES utf8;');

 

 

 

 

지정된 URI로 데이터를 받아 DB에 저장하는 코드 추가

server/server.js에서 위에 추가한 코드부분 아래 post로 받은 데이터를 서버콘솔에서 확인하고 DB에 추가하는 코드를 넣어줍니다.

 

app.post('/add/data', (req, res) => {
    console.log(req.body);

    Sample1.create({
        name : req.body.name,
        email : req.body.email
    })
    .then( result => {
        res.send(result)
    })
    .catch( err => {
        console.log(err)
        throw err;
    })
})

 

 

최종 수정된 server.js파일의 코드입니다.

 

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

const sequelize = require('./models').sequelize;
const bodyParser = require('body-parser');

sequelize.sync();

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

const {
    Sample1,
    Sequelize: { Op }
  } = require('./models');
sequelize.query('SET NAMES utf8;');

app.post('/add/data', (req, res) => {
    console.log(req.body);

    Sample1.create({
        name : req.body.name,
        email : req.body.email
    })
    .then( result => {
        res.send(result)
    })
    .catch( err => {
        console.log(err)
        throw err;
    })
})

const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
    console.log(`Server On : http://localhost:${PORT}/`);
})

 

 

 

화면구성 및 form데이터 전송 코드 추가

다음 코드의 내용을 해석해보면 화면에 render되는 폼에서 각각의 input값이 변경될 때마다 _nameUpdate()와 _emailUpdate()가 호출되어 state에 준비된 name과 email의 값을 담아줍니다. 그리고 Add버튼을 클릭하면 _addData가 호출되어 state에 저장된 값을 post로 서버에 보내게 됩니다.

 

import React, { Component } from "react";
import axios from 'axios';


class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      name : '',
      email : ''
    }
  };

  _addData = async(e) => {
    const { name, email } = this.state;
    e.preventDefault();

    const res = await axios('/add/data', {
      method : 'POST',
      data : { 
        'name' : name,
        'email' : email
     },
      headers: new Headers()
    });

    if(res.data) {
      alert('데이터를 추가했습니다.');
      return window.location.reload();
    }
  }

  _nameUpdate(e) {
    this.setState({ name : e.target.value })
  }
  _emailUpdate(e) {
    this.setState({ email : e.target.value })
  }

  render() {
    return(
      <div className='App'>
        <h3>Hello, You are testing React!</h3>
        <form method='POST' onSubmit={this._addData}>
          <input type='text' maxLength='10' placeholder='name' onChange={(e) => this._nameUpdate(e)}/>
          <input type='text' maxLength='20' placeholder='email' onChange={(e) => this._emailUpdate(e)}/>
          <input type='submit' value='Add' />
        </form>
      </div>
    )
  };
};


export default App;

 

 

 

 

브라우저화면에서 데이터추가 요청 및 DB에 추가여부 확인

이제 서버와 react를 시작(또는 재시작)하여 잘 동작되는지 여부를 확인합니다.

 

 

서버콘솔에 데이터가 잘 들어왔네요.

 

 

DB에도 데이터가 잘 저장되었습니다.

 

1:1(one-to-one), 1:N(one-to-many), N:M(many-to-many)관계를 DB에 추가하는 방법을 알아보겠습니다.

 

 

Sample테이블 준비

server/models폴더에 sample1.js와 sample2.js를 생성합니다.

// sample1.js
module.exports = (sequelize, DataTypes) => {
    return sequelize.define(
        'sample1', 
        {
            name: {
                type: DataTypes.STRING(50),
                allowNull: true
            },
            email: {
                type: DataTypes.STRING(50),
                allowNull: true
            }
        },
        {
            charset: 'utf8',
            collate: 'utf8_general_ci',
            timestamps: false,
        }
    )
};


// sample2.js
module.exports = (sequelize, DataTypes) => {
    return sequelize.define(
        'sample2', 
        {
            name2: {
                type: DataTypes.STRING(50),
                allowNull: true
            },
            email2: {
                type: DataTypes.STRING(50),
                allowNull: true
            }
        },
        {
            charset: 'utf8',
            collate: 'utf8_general_ci',
            timestamps: false,
        }
    )
};

 

그리고나서 server/models/index.js에 추가된 테이블생성을 위한 코드를 입력합니다.

 

'use strict';

const path = require('path');
const Sequelize = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require(path.join(__dirname, '..', 'config', 'db.json'))[ env ];
const db = {};

let sequelize = new Sequelize(
    config.database,
    config.username,
    config.password,
    config,
    {
      define: {
        charset: 'utf8',
        collate: 'utf8_general_ci'
      }
    }
  );
  
    db.sequelize = sequelize;
    db.Sequelize = Sequelize;

    db.sequelize
    .authenticate()
    .then(() => {
        console.log('Connection has been established successfully.');
    })
    .catch(err => {
        console.log('Unable to connect to the database: ', err);
    });

    db.Sample1 = require('./sample1')(sequelize, Sequelize);
    db.Sample2 = require('./sample2')(sequelize, Sequelize);
    
    //테이블 관계표현 코드위치

db.secret = '(9*)5$&!3%^0%^@@2$1!#5@2!4';
module.exports = db;

 

서버를 실행시켜 2개의 table이 잘 생성되었는지 확인해봅니다.

 

 

그리고나서 새로 추가될 테이블관계를 적용하기위해 생성한 sample1s와 sample2s를 db에서 삭제합니다.

(정상적인 확인을 위해 새로운 관계를 적용할 때마다 먼저 테이블을 삭제해주세요.)

drop table sample1s, sample2s; // 관계표현 test준비 후

drop table sample1s cascade; // 1:1관계생성 test후

drop table sample2s;
drop table sample1s; // 1:N관계생성 test후

drop table reletion;
drop table sample1s;
drop table sample2s; // N:M관계생성 test후

 

 

 

1:1관계표현 - hasOne메소드

server/models/index.js의 테이블 추가코드 아래에 다음과 같은 코드를 입력합니다. 

db.Sample1.hasOne(db.Sample2);

 

 

서버를 재실행시킨 후 터미널에서 추가된 테이블을 확인해보면 sample2s테이블에 sample1Id컬럼이 추가된 것을 볼 수 있습니다. sample1s의 id가 sample2s의 외래키로 등록되었습니다.

 

 

 

 

1:N관계표현 - hasMany/belongsTo메소드

'Sample1_id'라는 이름으로 외래키 컬럼을 추가합니다. 

db.Sample1.hasMany(db.Sample2, {
    foreignKey: 'Sample1_id',
    sourceKey : 'id'
});
db.Sample2.belongsTo(db.Sample1, {
    foreignKey: 'Sample1_id',
    targetKey : 'id'
});

 

 

기존 테이블을 삭제하고 서버를 재실행시켜 새로 설정한 관계의 테이블을 확인합니다.

 

 

 

 

N:M관계표현 - belongsToMany메소드

    db.Sample1.belongsToMany(db.Sample2, {
      through: 'reletion',
      foreignKey: 'sample1_id'
    });
    db.Sample2.belongsToMany(db.Sample1, {
      through: 'reletion',
      foreignKey: 'sample2_id',
    }); 

 

sample1s와 sample2s의 관계를 관리하는 reletion테이블을 생성하게 됩니다.

 

 

 

models폴더에 sample파일 추가

저는 cstmr.js이름으로 파일을 생성했습니다.

module.exports = (sequelize, DataTypes) => {
    return sequelize.define(
        'cstmr', 
        {
            name: {
                type: DataTypes.STRING(50),
                allowNull: true
            },
        },
        {
            charset: 'utf8',
            collate: 'utf8_general_ci',
            timestamps: false,
        }
    )
};
Sequelize 데이터타입 상세정보 : https://sequelize.org/master/manual/model-basics.html#data-types

 

 

 

models/index.js에서 sequelize에 sample파일추가

'use strict';

const path = require('path');
const Sequelize = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require(path.join(__dirname, '..', 'config', 'db.json'))[ env ];
const db = {};

let sequelize = new Sequelize(
    config.database,
    config.username,
    config.password,
    config,
    {
      define: {
        charset: 'utf8',
        collate: 'utf8_general_ci'
      }
    }
  );
  
    db.sequelize = sequelize;
    db.Sequelize = Sequelize;

    db.sequelize
    .authenticate()
    .then(() => {
        console.log('Connection has been established successfully.');
    })
    .catch(err => {
        console.log('Unable to connect to the database: ', err);
    });

     db.Cstmr = require('./cstmr')(sequelize, Sequelize);

db.secret = '(9*)5$&!3%^0%^@@2$1!#5@2!4';
module.exports = db;

 

 

 

서버재시작하여 table생성여부 확인

터미널에 아래와 같은 실행문구가 출력됩니다.

 

 

DB를 확인해보면 다음과 같이 테이블이 생성되어 있는 것을 볼 수 있습니다. id는 따로 cstmr.js에 추가하지 않았지만 primary키로 자동지정되어 생성되어있습니다. 그리고 js파일에서 지정한 'cstmr'에 's'가 붙은 'cstmrs'로 테이블명이 지정되었습니다.

 

Sequelize란?

nodejs에서 mysql을 쉽게 다룰 수 있도록 도와주는 
ORM(Object-Relational Mapping)방식의 라이브러리로서 ORM은 객체와 관계형DB의 관계를 매핑해주는 도구입니다.
Sequelize를 사용하면 직접 쿼리를 날리지않고도 자바스크립트 코드로 mysql을 제어(DB테이블생성, select, insert, update, delete..) 할 수 있습니다.

 

 

 

Sequelize, mysql2, path 설치

Sequelize와 함께 기타 효율적인 작업을 위해 mysql2와 path도 함께 설치합니다.

 

mysql2

node.js환경에서 mysql을 사용하기 위한 편의성 및 효율성을 제공하는 API. 

javascript환경에서 발생하는 콜백과 관련한 복잡한 처리와 에러상황들을 편리하게 다룰 수 있도록 도와줍니다.

detail : www.npmjs.com/package/mysql2

 

path

파일 및 디렉토리경로를 다루는 작업시 유용성을 제공하는 모듈

 

npm install sequelize path mysql2
yarn add sequelize path mysql2

 

 

 

models디렉토리 추가

Sequelize를 적용하기 전에 DB에 추가할 테이블들의 정보를 서버에 전달하기위해 server 디렉토리 안에 'models' 디렉토리를 추가해줍니다.

 

models안에 추가할 js파일들은 서버와 연동되면서 DB테이블 역할을 하게됩니다.

 

 

 

db.js를 db.json으로 변경

네트워크상에서 데이터를 주고 받을 때 좀더 효율적인 성능을 위해 server/config안의 db.js를 json형식으로 바꿔줍니다.

 

{
    "development" : {
        "host" : "localhost",
        "username" : "react_test",
        "password" : "1111",
        "database" : "react_test",
        "dialect" : "mysql"
    }
}

 

 

 

models디렉토리에 index.js추가하여 db.json읽어오기

서버가 models디렉토리를 읽을 수 있도록 models폴더 안의 테이블js파일들을 연결, 관리해 줄 index.js를 추가해줍니다.

 

'use strict';

const path = require('path');
const Sequelize = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require(path.join(__dirname, '..', 'config', 'db.json'))[ env ];
const db = {};

let sequelize = new Sequelize(
    config.database,
    config.username,
    config.password,
    config,
    {
      define: {
        charset: 'utf8',
        collate: 'utf8_general_ci'
      }
    }
  );
  
    db.sequelize = sequelize;
    db.Sequelize = Sequelize;

    db.sequelize
    .authenticate()
    .then(() => {
        console.log('Connection has been established successfully.');
    })
    .catch(err => {
        console.log('Unable to connect to the database: ', err);
    });

db.secret = '(9*)5$&!3%^0%^@@2$1!#5@2!4';
module.exports = db;

 

 

 

server.js와 Sequelize연동

server폴더에 있는 server.js의 기존 db연결코드를 모두 지우고 아래 코드를 넣어줍니다.

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

const sequelize = require('./models').sequelize;
sequelize.sync();

app.use(express.json());

const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
    console.log(`Server On : http://localhost:${PORT}/`);
})

 

 

 

server와 react를 재시작하여 연결확인

sequelize 연결 성공시 models/index.js안의 'Connection has been established successfully.' 문구가 콘솔창에 출력됩니다.

 

React프로젝트에 아래목록 작업이 모두 완료되었다면 이번엔 로컬DB mysql과 연결을 해보겠습니다. 

 

Mysql이 설치되어있지 않다면 Mysql사이트(링크)에서 다운받아주세요.
이후 설명과정은 DB 사용자정보와 샘플용 테이블데이터가 모두 준비되었다는 가정하에 진행합니다.

 

 

 

프로젝트폴더에 Mysql관련 패키지 설치하기

npm install mysql
yarn add mysql

 

 

 

server폴더에 DB정보파일 생성

프로젝트폴더의 server디렉토리안에 'config'폴더를 생성한 뒤 'db.js'파일을 생성합니다.

 

 

그리고나서 아래 코드를 입력합니다.

이 정보들은 server.js에서 db에 연결요청시 사용됩니다.

var mysql = require('mysql');
const db = mysql.createPool({
    host : 'localhost',
    user : 'react_test',
    password : '1111',
    database : 'react_test'
});

module.exports = db;

 

 

server.js에서 db정보 요청

파일 상단에 DB커넥션 객체를 추가해주고 '/api/products'로 데이터요청 발생시 쿼리를 날려

DB로부터 가져온 정보를 products라는 이름으로 보내줍니다. (DB data는 배열형태로 가져옴)

const express = require('express');
const app = express();
const PORT = process.env.PORT || 4000;
const db = require('./config/db');

app.get('/api/products', (req, res) => {
    db.query("SELECT * FROM mall_test", (err, data) => {
        if(!err) res.send({ products : data });
        else res.send(err);
    })
})

app.listen(PORT, () => {
    console.log(`Server On : http://localhost:${PORT}/`);
})

 

 

 

브라우저 콘솔에서 출력된 db데이터 확인

app.js를 조금 수정했습니다. db정보가 레코드마다 배열의 원소로 저장되어 보내지므로 this.state.hello를 빈배열로 초기화합니다. 그리고 받은 데이터는 개발모드 콘솔로 확인해봅니다.

 

import React, { Component } from 'react';
import axios from 'axios';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      hello : [],
    }
  }

  componentDidMount() {
    this._getHello();
  }

  _getHello = async() => {
    const res = await axios.get('/hello');
    this.setState({ hello : res.data.hello })
    console.log(this.state.hello);
  }

  render() {
    return(
      <>
        <h3>get DB data(브라우저 개발모드 콘솔확인)</h3>
      </>
    )
  }
}

export default App;

 

server.js를 재실행하여 mysql에 저장된 데이터가 잘 출력되는지 확인합니다.

React프로젝트에 설치된 서버와 React로 구성된 화면단을 연결해보겠습니다.

연결확인을 위해 서버에서 만들어둔 샘플text를 App.js에서 요청시 해당경로를 통해 화면에서 보여주도록 하겠습니다.

 

 

eject실행하기

eject는 해당프로젝트에 숨겨져있던 모든 설정들을 밖으로 추출해주는 명령어입니다.

eject를 실행하면 webpack과 babel등 프로젝트에 의존된 수많은 설정들이 package.json에 등록됩니다. 

 

npm run eject
yarn eject

 

한번 실행하면 되돌릴 수 없으니 eject를 원하는게 확실한지 확인하는 메시지가 나옵니다. y를 눌러 계속 진행합니다.

 

 

 

GIT에 커밋되지 않은 changes들 commit하기

저의 경우 git저장소로 등록된 프로젝트폴더에 커밋되지 않은 파일들이 있어 진행이 중단되었습니다.

이런 경우 커밋을 먼저하고 다시 'npm run eject'를 실행합니다.

 

 

 

 

eject가 완료되면 성공적으로 추출되었다는 메시지와 함께 'config'와 'scripts' 디렉토리가 추가됩니다.

 

 

이 때 config폴더안에 'webpack.config.dev.js'이 들어있을 경우(React 이전 버전) 추가 세팅들이 필요할 수 있습니다. 저는 해당하지 않아서 추가 세팅은 pass했습니다.

 

 

 

 

클라이언트요청을 받기위한 server.js세팅

화면단에서 /hello경로로 데이터요청(by Port번호 4000)시 'Hello react'라는 메시지를 보내기 위한 코드입니다.

 

const express = require('express');
const app = express();
const PORT = process.env.PORT || 4000;

app.get('/hello', (req, res) => {
    res.send({ hello : 'Hello react' });
})

app.listen(PORT, () => {
    console.log(`Server On : http://localhost:${PORT}/`);
})

 

 

 

 

Axios모듈을 활용한 http요청(웹서버와 통신)

HTTP통신을 위해 요즘 많은 장점으로 인기있는 Axios라이브러리를 사용해보겠습니다. 

 

npm install axios
yarn add axios

 

axios를 설치한 뒤 App.js를 수정합니다. 아래 코드는 화면이 처음 mount(랜더링)되었을 때 _getHello를 실행하여 axios를 통해 '/hello'경로로 ajax통신요청을 합니다. 그럼 응답받은 hello데이터를 setState하여 화면을 다시 랜더링하게 됩니다.

 

import React, { Component } from 'react';
import axios from 'axios';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      hello : '',
    }
  }

  componentDidMount() {
    this._getHello();
  }

  _getHello = async() => {
    const res = await axios.get('/hello');
    this.setState({ hello : res.data.hello })
  }

  render() {
    return(
      <>
        <h3> {this.state.hello} - connected to server</h3>
      </>
    )
  }
}

export default App;

 

 

 

데이터요청을 위한 Port설정

서버에 데이터요청시 사용할 port를 package.js 하단에 추가해줍니다. (proxy정보를 지정해주지 않으면 React와 백엔드서버 중 어디와 통신해야할지 알 수 없기 때문에 기본적으로 React포트인 3000으로 요청을 보내게 됩니다.)

 

"proxy": "http://localhost:4000"

 

 

 

브라우저에서 확인하기

정상적으로 동작하는지 확인하기위해 서버와 클라이언트를 각각 실행해줍니다.

server경로로 이동하여 'node server.js'를 실행하고 프로젝트폴더로 돌아와 'npm start' 또는 'yarn start'를 실행합니다.

node server.js

'Hello react'라는 메시지가 정상적으로 출력되었습니다.

서버디렉토리 만들기

프로젝트폴더에 server용 코드를 위한 디렉토리와 비어있는 server.js파일을 먼저 생성합니다.

 

 

 

Express모듈 설치하기

React에서 만들어진 작업물은 클라이언트단에서 동작하는 파일들입니다. 여기에 서버(node.js)를 구축하기위해 서버관리모듈 Express를 프로젝트폴더에 설치합니다. 

npm install express
yarn add express

 

 

설치가 완료되면 package.json을 열어 잘 추가되었는지 확인해봅니다.

 

 

 

Server코드 작성

server.js에 아래 코드를 추가해줍니다. express모듈을 불러와 app변수를 통해 포트번호를 할당하고 localhost:4000/으로 접속시(접속경로: app.get('/')) 확인용 응답메시지를 출력합니다.

const express = require('express');
const app = express();
const PORT = process.env.PORT || 4000;

app.get('/', (req, res) => {
    res.send('Server Response Success');
})

app.listen(PORT, () => {
  console.log(`Server On : http://localhost:${PORT}/`);
})

(console확인부분에 따옴표 `가 '와 다르므로 조심해주세요.)

 

 

 

Server실행

server.js파일이 있는 경로로 이동하여 아래 실행코드를 입력해줍니다.

node server.js

 

 

브라우저를 통해 확인 

http://localhost:4000/로 접속하여 정상적으로 응답메시지가 출력되는지 확인합니다.

 

React프로젝트를 크롬 개발자모드에서 확인하면 아래와 같이 React를 위한 개발자툴을 설치해보라고 권유하는 것을 볼 수 있습니다. 

 

콘솔의 해당링크를 따라 이동하면 아래와 같이 리액트사이트로 이동하게됩니다. 크롬 앱스토어 링크가 보이네요. 파이어폭스와 익스플로어용 툴도 제공하고 있습니다.

 

 

파란색 버튼을 눌러 설치를 진행합니다. (저는 이미 설치를 했기때문에 삭제버튼으로 바뀌었네요.)

 

 

 

설치 후 브라우저를 껐다가 다시 실행시킵니다. 그러면 React로 제작된 사이트의 경우 개발자모드에서 새로운 탭항목들이 생긴 것을 확인할 수 있습니다.

 

절대경로 Path설정의 필요성

index.js 혹은 App.js등 각 파일에서 다른 파일들을 import할때 기본적으로 상대경로로 잡혀있는 것을 볼 수 있습니다. 나중에 프로젝트의 규모가 커지고 또 복잡해지면 상대경로에 의한 관리시 다른 파일들을 import할 때 굉장히 번거로워지기 때문에 절대경로로 세팅해주어야 합니다.

 

상대경로 적용상태

 

jsconfig.json파일 생성

프로젝트 최상위폴더(root)에 jsconfig.json파일을 추가한 뒤 아래 코드를 넣어줍니다. src를 기본경로로 세팅합니다.

{
    "compilerOptions": {
        "baseUrl": "src"
    },
    "include": [
        "src"
    ]
}

절대경로 적용상태

 

그리고나서 VSCode를 재실행해주면 절대경로에 의해 동작하는 것을 확인할 수 있습니다.

SPA와 React

React프로젝트를 시작하려면 기본적으로 SPA의 개념을 이해할 필요가 있습니다. 
SPA는 Single Page Application 즉 단일페이지 어플리케이션이라는 의미인데요, 일반적으로 어떤 웹사이트를 이용할 때 url을 통해 유저가 새로운 페이지를 요청하면 해당되는 페이지리소스를 서버로부터 받아 새로운 화면을 브라우저를 통해 보게 됩니다. 이에 따른 서버의 역할을 라우팅(Routing) 이라고 하지요. 그래서 전통적으로 웹 어플리케이션은 각 url에 해당하는 여러 페이지로 구성되어 있었고 규모가 큰 어플리케이션의 경우 요즘과 같이 사용자와의 많은 상호작용과 데이터전송량에 따라 렌더링을 위한 서버자원사용에 따른 불필요한 트래픽, 속도저하 등의 문제가 생기게 되었습니다. 

React는 이와 같은 문제점을 해결하기위해 전통방식과 다른 개념을 도입한 라이브러리 또는 프레임워크입니다. 
서버측에서는 한 개의 페이지만 제공하고 해당하는 뷰 렌더링은 유저의 브라우저에게 역할을 넘겨 필요한 데이터만 전달한 뒤 적절한 뷰를 보여주도록 한 것입니다. 이때 해당뷰에 따른 url이 필요한 경우가 많은데 이 역할이 React에는 기본적으로 내장되어있지 않아 React Router를 별도 설치 후 사용해야 합니다.

 

 

 

react-router-dom 설치

cmd 또는 콘솔창을 열어 생성한 프로젝트폴더로 이동한 뒤 아래 설치명령어를 입력합니다.

npm install --save react-router-dom

 

설치완료 후 프로젝트 root폴더의 package.json파일을 열어 dependencies항목에 "react-router-dom"이 추가되었는지 확인합니다.

 

 

안타깝게도 리액트의 실행화면은 IE프라우저에서는 확인할 수가 없습니다. 그래서 별도의 지원모듈을 설치해줘야 하는데요. 콘솔창에서 해당 프로젝트 폴더로 이동하여 다음 코드를 입력합니다.

npm install react-app-polyfill

 

 

그리고 기본 React프로젝트 생성시 보여주는 index화면 내용에 해당하는 App.js파일을 열어 맨 상단에 아래 코드를 입력합니다.

import 'react-app-polyfill/ie11';
import "react-app-polyfill/stable";

 

 

 

그리고나서 프로젝트폴더 root에 있는 package-lock.json를 열어 브라우저리스트에 ie11을 추가해줍니다. 

 

 

 

그 다음, 프로젝트폴더 > node_modules에 있는 .cache폴더를 삭제합니다. 캐쉬폴더는 서버실행시 다시 생성되므로 삭제해도 문제없습니다.

 

마지막으로 Internet Explorer의 캐쉬를 모두 삭제한 후 서버를 재실행합니다. 

React를 시작하기 위한 준비사항

  • Node.js : 리액트 프로젝트 작업시 필요한 도구를 사용하기 위한 기반
  • npm : 프로젝트에서 사용될 라이브러리를 설치/버전을 관리해주는 도구. Node.js와 함께 설치되며 React역시 npm을 통해 설치할 것임. 동일한 기능의 Yarn을 사용해도 좋음
  • VS Code : 사용할 코드에디터. VS Code이외에 다른 좋은 에디터를 선택해도 됨
  • Git : 소스코드의 버전관리를 위한 도구

 

 

Node.js와 npm설치

리액트를 사용하기 위해서는 먼저 기본 환경을 위한 node.js를 설치합니다. 

리액트는 컴포넌트단위로 여러개의 파일로 분리저장 하는데 이 파일들을 한개로 결합하기 위해 Webpack을 사용합니다. 또 각 컴포넌트들은 일반 자바스크립트가 아닌 JSX라는 문법으로 작성하게 되는데 이 JSX를 사용하기 위해 Babel이라는 도구가 필요하구요. node.js를 사용하는 이유는 Webpack과 Babel이 자바스크립트 런타임인 Node.js기반으로 만들어져 있기 때문입니다.

 

먼저 Node.js사이트 https://nodejs.org/ko/를 방문하여 최신버전이 아닌 안정적인 LTS버전을 다운로드합니다.

 

 

기본 설치위치는 C:\Program Files로 지정되며 nodejs란 폴더가 생성되어 설치됩니다.

 

위 화면에서 필요한 툴들 자동설치는 해제해주세요. 설치했다가 뭐하는지도 모르는 엄청난 양의 툴들이 언제 끝날지 모르게 계속 설치되는 암담함을 느낄 수 있습니다. 참고로 저는 중간에 강제로 멈추고 node.js 삭제했다가 다시 깔았습니다.

 

 

설치를 다 했으면 Node.js와 npm이 잘 설치되어있는지 cmd창을 통해 확인합니다. 먼저 node -v로 버전을 확인하고 node를 실행하여 간단한 코드를 작성해봤습니다.

npm도 마찬가지로 버전을 확인해봅니다. 한꺼번에 설치되니 간단하네요.

 

 

 

 

React.js 설치하기

저는 이미 VS code를 설치해뒀기때문에 설명은 pass하고 본격적으로 React.js를 설치합니다.

cmd창에 아래 코드를 입력합니다. (yarn을 사용하실 경우 install코드가 다르니 별도로 찾아보세요.)

npm install -g create-react-app

 

설치가 다되면 버전확인 또 해봅니다.

create-react-app --version

 

 

 

React 프로젝트폴더 생성하기

아래 코드를 입력하여 원하는 이름의 폴더를 생성합니다. 참고로 저는 카멜형식의 reactTest로 했다가 npm이름규칙에 어긋난다고하여 react_test로 변경하였습니다.

create-react-app react_test

 

 

설치가 쭉 진행됩니다.

 

뭐 때문인지 몰라도 제 경우는 Type스크립트를 직접 설치하라네요. 나중에 따로 설치를 해줘야 할 것 같습니다.

그리고 나머지는 옵션항목이라 skip하고 일단 진행상황을 지켜봅니다.

 

 

설치가 완료되었습니다.

간단하게 몇 가지 설명이 나오네요. 안내되는 내용대로 리액트가 잘 설치되었는지 확인해 보겠습니다.

일단 생성한 디렉토리로 이동을 한 후 서버를 실행해보겠습니다.

 

npm start

 

npm start를 입력하고 나니 브라우저가 켜지면서 로컬서버 index화면을 자동으로 보여주네요.

이렇게 리액트 프로젝트를 시작할 준비가 모두 끝났습니다.

 

 

 

+ Recent posts