본문 바로가기

React / Node.js / AWS RDS(MySQL)

by sun__ 2021. 1. 14.

www.youtube.com/watch?v=_yEH9mczm3g&list=PLRx0vPvlEmdD1pSqKZiTihy5rplxecNpz

나동빈님의 유튜브 강의에서 배운 것 정리

 

데이터

데이터베이스를 따로 관리하기 위해선 HeidiSQL같은 것을 쓰면 효율적이다. 삭제 시 실제로 삭제하지 않고 isDeleted를 체크하는 방식으로 하였다.

사용한 데이터

 

 

Read

node server.js에선 다음과 같은 방법으로 REST API를 제공한다.

const express = require(`express`);
const bodyParser = require(`body-parser`);
const fs = require(`fs`);
const mysql = require(`mysql`);

const app = express();
const port = process.env.PORT || 5000;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

//데이터베이스 접근을 위한 아이디/비밀번호/호스트 주소 등(깃허브에 안올라가게 하도록 따로 저장해야 함)
const data = fs.readFileSync(`./database.json`); 
const conf = JSON.parse(data);
const connection = mysql.createConnection({
	host: conf.host,
	user: conf.user,
	password: conf.password,
	port: conf.port,
	database: conf.database
});
connection.connect();		//데이터베이스 연결

//read를 위한 API
app.get(`/api/customers`, (req, res) => {
	connection.query(
		"SELECT * FROM CUSTOMER WHERE isDeleted = 0",
		(err, rows, fields) => res.send(rows)
	);
});

 

read API는 render()직후인 componentDidMount()에서 호출하는 것이 가장 자연스럽다고 한다.

react lifecycle

 

 

React app에선 fetch로 /api/customers 를 GET 방식으로 json 타입으로 가져온다. 

/*app.js*/
class App extends Component {
  constructor(props) {super(props); /*~~~*/}

  componentDidMount() {
    this.timer = setInterval(this.progress, 20);

    this.callApi()
      .then(res => this.setState({ customers: res }))
      .catch(err => console.log(err));
  }

  callApi = async () => {
    const response = await fetch(`/api/customers`);
    const body = await response.json();
    return body;
  }
  
  render() {/*~~~*/;}
}

 

+

데이터 삭제 혹은 추가 후 전체 페이지를 새로고침하지 않고 결과를 반영하기 위한 Read 부분은 다음과 같다.

/* app.js */
class App extends Component {

  constructor(props) ;

  stateRefresh = () => {
    this.setState({
      customers: "",
      completed: 0,
      searchKeyword: ""
    });
    this.callApi()
      .then(res => this.setState({ customers: res }))
      .catch(err => console.log(err));
  }

  callApi = async () => {
    const response = await fetch(`/api/customers`);
    const body = await response.json();
    return body;
  }

  render() {    
    return (
    /*...*/
    //하위컴포넌트인 CustomerAdd에게 전달해주기 위해 stateRefresh 보내줌
    <Customer stateRefresh={this.stateRefresh} key={c.id} id={c.id} image={c.image} name={c.name} birthday={c.birthday} gender={c.gender} job={c.job} />;
    <CustomerAdd stateRefresh={this.stateRefresh} />
    //->add혹은 delete 후 Read API호출해서 고객데이터를 출력하는 부분만 reload해주기 위함
    /*...*/
    );
  }
}

 


 

Create

node server.js에선 다음과 같은 방법으로 REST API를 제공한다.

multer : 파일 업로드 시 필요한 미들웨어 github.com/expressjs/multer/blob/master/doc/README-ko.md

//READ에서 필요한 부분에 추가로

const multer = require('multer');	//파일업로드를 위한 node 미들웨어
const upload = multer({ dest: './upload' });	//실제 사진은 서버 ./upload폴더에 저장

//db에는 실제 이미지를 올리는 것이 아닌 서버에서의 해당 파일 경로를 string(varchar)타입으로 저장할 것
// /image/${filename}꼴로 db에 저장하고, 
// 서버에서 다시 SELECT 등으로 가져올 땐 image폴더 대신 upload폴더에서 가져오도록 함
app.use('/image', express.static('./upload'));

//create을 위한 API
//upload.single('image'): 폼 데이터 중 name=='image'인 데이터를 upload폴더에 저장하겠다는 의미
app.post('/api/customers', upload.single('image'), (req, res) => {
	let sql = 'INSERT INTO CUSTOMER VALUES (null, ?, ?, ?, ?, ?, now(), 0)';
	let image = `/image/${req.file.filename}`; //req.file.filename은 multer가 중복없도록 무작위로 할당
	let name = req.body.name;
	let birthday = req.body.birthday;
	let gender = req.body.gender;
	let job = req.body.job;
	let params = [image, name, birthday, gender, job];
	connection.query(sql, params,
		(err, rows, fields) => {
			res.send(rows);
		});
});

 

es6표준 fetch가 아닌 axios의 post 사용. (이유: hoorooroob.tistory.com/entry/React-React-Naive-TIPS-axios-%EC%99%80-fetch-%EC%96%B4%EB%96%A4-%EA%B2%83%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C)

<form>에 묶인 <input>들을 보내지 않고, FormData와 state를 post를 사용해서 서버로 보냄

class CustomerAdd extends Component {
  constructor(props);   
  /*...*/

  //axios의 post 사용
  //input name:'image' value:this.state.file
  //input name:'name' value:this.state.userName....
  addCustomer = () => {
    const url = `/api/customers`;
    const formData = new FormData();
    formData.append(`image`, this.state.file);
    formData.append(`name`, this.state.userName);
    formData.append(`birthday`, this.state.birthday);
    formData.append(`gender`, this.state.gender);
    formData.append(`job`, this.state.job);
    formData.append(`fileName`, this.state.fileName);

  //파일 전송을 위해 multer와 같이 쓰려면 반드시 multipart/form-data
    const config = {
      headers: {
        'content-type': 'multipart/form-data'
      }
    }
    return post(url, formData, config);
  }

  /*...*/
  render() {
    const { classes } = this.props;
    return (
       /*...*/
       <input className={classes.hidden} accept="image/*" id="raised-button-file" type="file" file={this.state.file} value={this.state.fileName} onChange={this.handleFileChange} /> <br />
       <label htmlFor="raised-button-file">
         <Button variant="contained" component="span" name="file">
           {this.state.fileName === "" ? "프로필 이미지 선택" : this.state.fileName}
         </Button>
       </label><br />
       <TextField label="이름" type="text" name="userName" value={this.state.userName} onChange={this.handleValueChange} /><br />
       <TextField label="생년월일" type="text" name="birthday" value={this.state.birthday} onChange={this.handleValueChange} /><br />
       <TextField label="성별" type="text" name="gender" value={this.state.gender} onChange={this.handleValueChange} /><br />
       <TextField label="직업" type="text" name="job" value={this.state.job} onChange={this.handleValueChange} /><br />
       /*...*/      
       <Button variant="contained" color="primary" onClick={this.handleFormSubmit}>추가</Button>
    )
  }
};

 


Delete

app.delete('/api/customers/:id', (req, res) => {
	let sql = `UPDATE CUSTOMER SET isDeleted = 1 WHERE id = ?`;
	let params = [req.params.id];
	connection.query(sql, params,
		(err, rows, fields) => {
			res.send(rows);
		});
});

 

deleteCustomer(id) {
    const url = `/api/customers/${id}`;
    fetch(url, {
      method: "DELETE"
    });
    this.props.stateRefresh();
  }

 

 

AWS 연동

www.youtube.com/watch?v=G6O-u6FkjpY&list=PLRx0vPvlEmdD1pSqKZiTihy5rplxecNpz&index=10

 

기타

react-dev-server : create-react-app이 제공. 수정사항을 실시간으로 반영할 수 있도록 하여 개발을 도와줌.

->이거때문에 node.js server로 GET,POST 등 요청을 보내려면 proxy설정이 필요하다.

 

proxy: 서버에서 처리할 수 없는 요청을 프록시에 등록된 주소들로 보낼 수 있도록 함.

 

filter(search)기능

 

state==input value인 경우 handleValueChange함수로 실시간으로 바꿔줌

 

로딩창 띄우는 기능

 

ui 적용법

 

Dialog 열고 닫힘 조절

 

 

 

 

 

 

github.com/lsn1106/React-Management-Tutorial