MERN 동영상 메모

2020-07-08

MERN 예제 동영상을 무작정 따라치면서 알게된 내용 간단 메모

백엔드용 폴더는 주로 프로젝트 폴더 바깥에다 만듬.
간단한 실습이면 안에다가 만들어도 별 상관 X.
npm init을 통해 package.json 파일 생성

npm install express cors mongoose dotenv
npm install nodemon

express는 백엔드용 프레임워크.
백엔드 설계에 필요한 여러 기능들을 포함하고 있음.

cors는 same-origin policy에 의해 외부 서버에서
데이터를 요청하지 못하는 상황을 해결해줌.

mongoose는 mongoDB를 node.js에서 객체 형태로 편하게 쓰기 위함.

dotenv는 환경변수같은거를 파일에다가 따로 저장할 수 있게 해줌.
(.env 파일에는 mongoDB에 연결하는 URI를 저장했음)

nodemon은 서버 파일에 변경사항이 생길 때마다
계속 서버를 껐다 키지 않아도 되게 해줌(자동 리로딩).

Routing

백엔드 라우팅은 express router를 사용.
const router = require(‘express’).Router();

프론트엔드 라우팅은 react-router-dom 사용.
import { BrowserRouter as Router, Route } from ‘react-router-dom’;
(Router 종류는 많음. 찾아볼 것.)
Router element를 생성할 수 있게 됨.
라우터와 관련된 모든 컴포넌트들을 Router element 안에다가 집어넣으면 됨.

import Navbar from "./components/navbar.component";
import ExercisesList from "./components/exercises-list.component";
import EditExercise from "./components/edit-exercise.component";
import CreateExercise from "./components/create-exercise.component";
import CreateUser from "./components/create-user.component";

function App() {
  return(
    <Router>
      <Navbar />
      <br />
      <Route path="/" exact component={ExercisesList} />
      <Route path="/edit/:id" component={EditExercise} />
      <Route path="/create" component={CreateExercise} />
      <Route path="/user" component={CreateUser} />
    </Router>
  );
}

Navbar, ExerciseList, EditExercise 등등은 전부 component 이름임.
각 route를 전부 Route라는 element로 설정해줬음.
대충, 해당 path URL로 가면 해당 component를 불러온다 뭐 이런 뜻임.

react-router-dom을 활용하면 이런식으로
각 component를 서로 다른 URL path에다가 맵핑할 수 있다.

// navbar.component.js
import React, { Component } from 'react';
import { Link } from 'react-router-dom';

export default class Navbar extends Component {
  render() {
    return(
      // bootstrap 문서에 나와있는 navigation bar.
      <nav className="navbar navbar-dark bg-dark navbar-expand-lg">
        <Link to="/" className="navbar-brand">ExcerTracker</Link>
        <div className="collapse navbar-collapse">
          <ul className="navbar-nav mr-auto">
            <li className="navbar-item">
              <Link to="/" className="nav-link">Exercises</Link>
            </li>
            <li className="navbar-item">
              <Link to="/create" className="nav-link">Create Exercise Log</Link>
            </li>
            <li className="navbar-item">
              <Link to="/user" className="nav-link">Create User</Link>
            </li>
          </ul>
        </div>
      </nav>
    );
  }
}

Link는 각 route로 연결할 수 있게 해주는 element.
(react-router에서는 a 태그 대신 Link 태그를 사용한다)

백엔드의 express.js와 프론트엔드의 react-router-dom을 활용하여 라우팅을 할 수 있다.


Connect Back and Front

여기까지는 백엔드와 프론트엔드가 분리되어 있는 상태.

프론트엔드에서 백엔드의 server endpoint로 HTTP request를 날려보냄으로써 둘을 연결.
백엔드로 HTTP 요청을 보낼 때는 axios 라이브러리를 사용.
npm install axios
import axios from 'axios';

백엔드로 HTTP Post 요청

onSubmit(e) {
  e.preventDefault();
  const user = {
    username : this.state.username
  }
  console.log(user);
  
  axios.post('http://localhost:5000/users/add', user)
    .then(res => console.log(res.data));

  window.location = '/';
}

http://localhost:5000/users/add라는 backend endpoint로 HTTP post 요청을 보냄.
endpoint는 request body로 JSON 객체를 가져옴.
객체의 형태는 두 번째 인자인 user.
(user 파일에서 mongoose 객체로 정의)

axios에서 promise가 반환되므로 then으로 받음.
(async await을 사용하던가)

window.location = '/';
Home 화면으로 돌아가도록 하는 코드.
(url path를 ‘/’로 고치라는 뜻이니깐)
여기서는 폼에서 submit을 하고 난 뒤에 다시 home으로 돌아가게 만듬.

백엔드로 HTTP Get 요청

componentDidMount() {
  axios.get('http://localhost:5000/users/')
    .then(response => {
      if(response.data.length > 0) {
        this.setState({
          users : response.data.map(user => user.username),
          username : response.data[0].username
// user는 사용자의 username으로 이루어진 배열(선택목록)
// username은 현재 선택한 username.
// default값으로 그냥 맨 처음의 user를 지정함.
        });
      }
    });
}

처음 화면을 불러올 때 화면의 Form을 세팅하는 코드(초기값을 넣어주는)
backend로 HTTP get 요청을 보낸다.
endpoint가 ‘/’이고, get 메소드이기 때문에 backend 코드에 가서 보면
router.route(‘/’).get ~ 함수가 실행되어 DB로 요청을 보낸다.

DB에서 데이터를 가져오라고 프론트가 백엔드한테 시킴.

백엔드로 HTTP Delete 요청

deleteExercise(id) {
  axios.delete('http://localhost:5000/exercises/' +id)
    .then(res => console.log(res.data));
  this.setState({
    exercises : this.state.exercises.filter(el => el._id !== id)
  })
}

삭제 요청 예시 코드.

setState부분을 유의해서 볼 것.
exercises 배열에서, 삭제하기로 한 id를 가진 놈만 제외하고 다 남김.
_id는 mongoDB에서 데이터마다 자동으로 부여한 고유 id.

삭제 버튼 & Function component

function component는 class component에서
state와 lifecycle method가 없는 버젼.
props와 JSX만을 사용하는 간단한 component를 만들 때 사용.
이런 간단한 컴포넌트들은 별개의 파일로 만들어 줄 필요 없음.

const Exercise = props => (
  <tr>
    <td>{props.exercise.username}</td>
    <td>{props.exercise.description}</td>
    <td>{props.exercise.duration}</td>
    <td>{props.exercise.date.substring(0, 10)}</td>
    <td>
      <Link to={"/edit/" + props.exercise._id}>edit</Link> | <a href="#" onClick={() => {props.deleteExercise(props.exercise._id) }}>delete</a>
    </td>
  </tr>
)

delete는 링크로 구현하지 않음.
a 태그로 구현하되, 아무곳에도 가지 않게 하기 위해 href=’#’를 사용.
해당 링크를 클릭했을 때의 onClick event로 삭제를 구현했음.

사실, 버튼으로 만드는 게 가장 좋지만
영상에서는 버튼으로 만들 시간이 없어서 간단하게 a 태그로 만든 듯.

this.props.match.params

<Route path="/about/:username" component={About} />

react-router를 사용해서 URL에 username이라는 parameter를 넘겨줬다.
(username 앞에 저 ‘:’는 parameter임을 의미)
그러면 저 About이라는 component에서는

class About extends Component {
  render() {
    return (
      <div>
        {this.props.match.params.username}입니다.
      </div>
    );
  }
}

이런 식으로 데이터를 받아올 수 있다.
Routing을 통해 페이지를 이동할 때 데이터를 넘겨주는 방법.

axios.get('http://localhost:5000/exercises/' + this.props.match.params.id) ...

이런 식으로 uri를 지정할 때 많이 사용한다.

원본 강의 영상