MERN 스택..기초..

김주언·2022년 9월 21일
0

MERN

목록 보기
1/4
post-thumbnail

나도뭔지모르겟다..

그런데 MERN 스택인... 풀스택 가능한..
도전~~

  • 프론트엔드 클라이언트
    • 프레젠테이션 레이어 : 리액트
  • 백엔드 서버
    • 미들레이어, 애플리케이션 레이어 : Express, Node.js
      데이터베이스 레이어 : MongoDB

SETUP

mern 디렉터리: 전체 프로젝트 파일 모아두는 곳
mern 디렉터리 내부에 리액트 클라이언트 앱 디렉터리를 생성한다.

client

mkdir mern  
cd mern
npx create-react-app client

server

리액트 앱 생성 후 백엔드를 위한 폴더를 생성하고 server라고 이름 설정

mkdir server
cd server
npm init -y

package.json 초기화 후에 의존성 설치해주기

npm install mongodb express cors dotenv
  • mongodb
    MongoDB 데이터베이스 드라이버 설치. (Node.js 애플리케이션과 데이터 베이스를 연결하기 위해 드라이버가 필요하다.

  • express
    Node.js를 위한 웹 프레임워크 설치

  • cors
    cross origin resource sharing을 허용하기 위한 모듈

  • dotenv
    .env 파일에서 환경변수를 로드하는 모듈을 설치

모듈 설치된 것 확인 후에 server.js파일 생성하기

mern/server/server.js

// 설치한 express module을 불러와서 변수(express)에 저장
const express = require("express");
//e xpress를 실행하여 app object를 초기화 
const app = express();
const cors = require("cors");
require("dotenv").config({ path: "./config.env" });

// 사용할 포트 번호를 port 변수에
const port = process.env.PORT || 5000;

app.use(cors());
app.use(express.json());
app.use(require("./routes/record"));

// get driver connection
const conn = require("./db/conn");

// port변수를 이용하여 포트에 node.js 서버를 연결
app.listen(port, () => {
  // 서버 시작시 DB 연결 수행
  conn.connectToServer(function (err) {
    if (err) console.log(err);
  });
  console.log(`Server is now running on port : ${port}`);
});



MongoDB Atlas 연결

MongoDB Atlas를 데이터베이스로 사용한다. 사용하기 위해서 계정을 생성하고 클러스터를 배포하고 클러스터의 connection string이 필요하다.

server 디렉터리 아래에 config.env 파일 생성

mern/server/config.env

ATLAS_URI=mongodb+srv://jueon-admin:<password>@cluster0.g2eem.mongodb.net/?retryWrites=true&w=majority

db 폴더 생성 후 내부에 conn.js 파일 생성

mern/server/db/conn.js

const { MongoClient } = require("mongodb");
const db = process.env.ATLAS_URI;
const client = new MongoClient(db, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

var _db;

module.exports = {
  connectToServer: function (callback) {
    client.connect(function (err, db) {
      if (db) {
        _db = db.db("employees");
        console.log("Successfully connected to MongoDB.");
      }
      return callback(err);
    });
  },

  getDB: function () {
    return _db;
  },
};

  • _db = db.db("employees")
    현재 소켓 커넥션을 공유하는 새로운 DB 인스턴스를 생성한다.
    • @param dbName : 사용하고자하는 데이터베이스의 이름

데이터베이스 설정 완료했고, 서버 설정도 완료했다

Server API Endpoints

routes 폴더 생성하고 내부에 record.js 파일을 생성한다.

server/routes/record.js

const express = require("express");

// recordRoutes : express router 인스턴스
// 루트 정의를 위해 사용한다.
// 라우터는 미들웨어로 추가될 것이고 /record 로 시작하는 요청에 대한 제어를 수행

const recordRoutes = express.Router();

// DB와 연결을 위해서 사용한다.
const dbconn = require("../db/conn");

// 문자열 id를 객체 id로 변환한다. (id -> _id)
const ObjectId = require("mongodb").ObjectId;

// 레코드 리스트 받기
recordRoutes.route("/record").get(function (req, res) {
  let db_connect = dbconn.getDB("employees");
  db_connect
    .collection("records")
    .find({})
    .toArray(function (err, result) {
      if (err) throw err;
      res.json(result);
    });
});

// 개별 레코드 받기
recordRoutes.route("/record/:id").get(function (req, res) {
  let db_connect = dbconn.getDB();
  let query = { _id: ObjectId(req.params.id) };
  db_connect.collection("records").findOne(query, function (err, result) {
    if (err) throw err;
    res.json(result);
  });
});

// 레코드 생성
recordRoutes.route("/record/add").post((req, res) => {
  let db_connect = dbconn.getDB();
  let item = {
    name: req.body.name,
    position: req.body.position,
    level: req.body.level,
  };

  db_connect.collection("records").insertOne(item, (err, result) => {
    if (err) throw err;
    res.json(result);
  });
});

// 레코드 수정
recordRoutes.route("/update/:id").post((req, res) => {
  let db_connect = dbconn.getDB();
  let query = { _id: ObjectId(req.params.id) };
  let newValues = {
    $set: {
      name: req.body.name,
      position: req.body.position,
      level: req.body.level,
    },
  };
  db_connect
    .collection("records")
    .updateOne(query, newValues, (err, result) => {
      if (err) throw err;
      console.log("1 document updated");
      res.json(result);
    });
});

recordRoutes.route("/:id").delete((req, res) => {
  let db_connect = dbconn.getDB();
  let query = { _id: ObjectId(req.params.id) };
  db_connect.collection("records").deleteOne(myquery, function (err, obj) {
    if (err) throw err;
    console.log("1 document deleted");
    response.json(obj);
  });
});

module.exports = recordRoutes;


프론트 - 리액트

SETUP

모듈 설치

npm install bootstrap react-router-dom

create-react-app 명령어로 생성된 리액프 프로젝트 내부의 src 폴더 안의 모든 파일을 지우고 index.js, App.js 파일을 생성한다.

mern/client/src/index.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter } from "react-router-dom";

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

그냥 리액트 ...
컴포넌트를 생성해주면 된다..
fetch 를 이용하여 리액트 앱과 백엔드 서버를 연결한다. Create.jsx, Edit.jsx, RecordList.jsx는 HTTP Request를 다루기 때문에 fetch함수를 사용하고 있다.


Create.jsx


import React, { useState } from "react";
import { useNavigate } from "react-router-dom";

const Create = () => {
  const [form, setForm] = useState({
    name: "",
    position: "",
    level: 1,
  });

  const navigate = useNavigate();

  function updateForm(value) {
    return setForm((prev) => {
      return { ...prev, ...value };
    });
  }

  async function onSubmit(e) {
    e.preventDefault();

    const newPerson = { ...form };

    await fetch("http://localhost:5050/record/add", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(newPerson),
    }).catch((err) => {
      window.alert(err);
      return;
    });

    setForm({ name: "", position: "", level: 1 });
    navigate("/");
  }

  return (
    <div>
      <h3>CREATE NEW RECORD</h3>
      <form onSubmit={onSubmit}>
        <div className="form-group">
          <label htmlFor="name">Name</label>
          <input
            type="text"
            className="form-control"
            id="name"
            value={form.name}
            onChange={(e) => updateForm({ name: e.target.value })}
          />
        </div>
        <div className="form-group">
          <label htmlFor="position">Position</label>
          <input
            type="text"
            className="form-control"
            id="position"
            value={form.position}
            onChange={(e) => updateForm({ position: e.target.value })}
          />
        </div>
        <div className="form-group">
          <div className="form-check form-check-inline">
            <input
              type="radio"
              className="form-check-input"
              id="positionIntern"
              value="Intern"
              checked={form.level === 1}
              onChange={(e) => {
                updateForm({ level: e.target.value });
              }}
            />
            <label htmlFor="positionIntern" className="form-check-label">
              Intern
            </label>
          </div>
        </div>

        <div className="form-check form-check-inline">
          <input
            className="form-check-input"
            type="radio"
            name="positionOptions"
            id="positionJunior"
            value="Junior"
            checked={form.level === 2}
            onChange={(e) => updateForm({ level: e.target.value })}
          />
          <label htmlFor="positionJunior" className="form-check-label">
            Junior
          </label>
        </div>
        <div className="form-check form-check-inline">
          <input
            className="form-check-input"
            type="radio"
            name="positionOptions"
            id="positionSenior"
            value="Senior"
            checked={form.level === "Senior"}
            onChange={(e) => updateForm({ level: e.target.value })}
          />
          <label htmlFor="positionSenior" className="form-check-label">
            Senior
          </label>
        </div>
        <div className="form-group">
          <input
            type="submit"
            value="Create person"
            className="btn btn-primary"
          />
        </div>
      </form>
    </div>
  );
};

export default Create;

Edit.jsx

import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { useNavigate } from "react-router-dom";

const Edit = () => {
  const [form, setForm] = useState({
    name: "",
    position: "",
    level: 1,
    records: [],
  });

  const params = useParams();
  const navigate = useNavigate();

  useEffect(() => {
    async function fetchData() {
      const id = params.id.toString();
      const response = await fetch(
        `http://localhost:5050/record/${params.id.toString()}`
      );
      if (!response.ok) {
        const message = `An error has occurred: ${response.statusText}`;
        window.alert(message);
        return;
      }

      const record = await response.json();

      if (!record) {
        window.alert(`Record with id ${id} not found`);
        navigate("/");
        return;
      }
      setForm(record);
    }

    fetchData();
    return;
  }, [params.id, navigate]);

  function updateForm(value) {
    return setForm((prev) => {
      return { ...prev, ...value };
    });
  }

  async function onSubmit(e) {
    e.preventDefault();
    const editedPerson = {
      name: form.name,
      position: form.position,
      level: form.level,
    };

    // POST 요청 전송
    await fetch(`http://localhost:5000/update/${params.id}`, {
      method: "POST",
      body: JSON.stringify(editedPerson),
      headers: {
        "Content-Type": "application/json",
      },
    });

    navigate("/");
  }

  return (
    <div>
      <h3>Update Record</h3>
      <form onSubmit={onSubmit}>
        <div className="form-group">
          <label htmlFor="name">Name: </label>
          <input
            type="text"
            className="form-control"
            id="name"
            value={form.name}
            onChange={(e) => updateForm({ name: e.target.value })}
          />
        </div>
        <div className="form-group">
          <label htmlFor="position">Position: </label>
          <input
            type="text"
            className="form-control"
            id="position"
            value={form.position}
            onChange={(e) => updateForm({ position: e.target.value })}
          />
        </div>
        <div className="form-group">
          <div className="form-check form-check-inline">
            <input
              className="form-check-input"
              type="radio"
              name="positionOptions"
              id="positionIntern"
              value="Intern"
              checked={form.level === 1}
              onChange={(e) => updateForm({ level: e.target.value })}
            />
            <label htmlFor="positionIntern" className="form-check-label">
              Intern
            </label>
          </div>
          <div className="form-check form-check-inline">
            <input
              className="form-check-input"
              type="radio"
              name="positionOptions"
              id="positionJunior"
              value="Junior"
              checked={form.level === 2}
              onChange={(e) => updateForm({ level: e.target.value })}
            />
            <label htmlFor="positionJunior" className="form-check-label">
              Junior
            </label>
          </div>
          <div className="form-check form-check-inline">
            <input
              className="form-check-input"
              type="radio"
              name="positionOptions"
              id="positionSenior"
              value="Senior"
              checked={form.level === 3}
              onChange={(e) => updateForm({ level: e.target.value })}
            />
            <label htmlFor="positionSenior" className="form-check-label">
              Senior
            </label>
          </div>
        </div>
        <br />

        <div className="form-group">
          <input
            type="submit"
            value="Update Record"
            className="btn btn-primary"
          />
        </div>
      </form>
    </div>
  );
};

export default Edit;

import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { useNavigate } from "react-router-dom";

const Record = (props) => (
  <tr>
    <td>{props.record.name}</td>
    <td>{props.record.position}</td>
    <td>{props.record.level}</td>
    <td>
      <Link className="btn btn-link" to={`/edit/${props.record._id}`}>
        Edit
      </Link>{" "}
      |
      <button
        className="btn btn-link"
        onClick={() => {
          props.deleteRecord(props.record._id);
        }}
      >
        Delete
      </button>
    </td>
  </tr>
);

const RecordList = () => {
  const [records, setRecords] = useState([]);

  // DB에서 데이터 가져오기
  useEffect(() => {
    async function getRecords() {
      const response = await fetch(`http://localhost:5050/record/`);

      if (!response.ok) {
        const message = `An error occurred: ${response.statusText}`;
        window.alert(message);
        return;
      }

      const records = await response.json();
      setRecords(records);
    }

    getRecords();

    return;
  }, [records.length]);

  async function deleteRecord(id) {
    await fetch(`http://localhost:5050/${id}`, {
      method: "DELETE",
    });

    const newRecords = records.filter((el) => el._id !== id);
    setRecords(newRecords);
  }

  function recordList() {
    return records.map((record) => {
      return (
        <Record
          record={record}
          deleteRecord={() => deleteRecord(record._id)}
          key={record._id}
        />
      );
    });
  }

  return (
    <div>
      <h3>Record List</h3>
      <table className="table table-striped" style={{ marginTop: 20 }}>
        <thead>
          <tr>
            <th>Name</th>
            <th>Position</th>
            <th>Level</th>
            <th>Action</th>
          </tr>
        </thead>
        <tbody>{recordList()}</tbody>
      </table>
    </div>
  );
};

export default RecordList;

RecordList.jsx

import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { useNavigate } from "react-router-dom";

const Record = (props) => (
  <tr>
    <td>{props.record.name}</td>
    <td>{props.record.position}</td>
    <td>{props.record.level}</td>
    <td>
      <Link className="btn btn-link" to={`/edit/${props.record._id}`}>
        Edit
      </Link>{" "}
      |
      <button
        className="btn btn-link"
        onClick={() => {
          props.deleteRecord(props.record._id);
        }}
      >
        Delete
      </button>
    </td>
  </tr>
);

const RecordList = () => {
  const [records, setRecords] = useState([]);

  // DB에서 데이터 가ㅈㅕ오기
  useEffect(() => {
    async function getRecords() {
      const response = await fetch(`http://localhost:5050/record/`);

      if (!response.ok) {
        const message = `An error occurred: ${response.statusText}`;
        window.alert(message);
        return;
      }

      const records = await response.json();
      setRecords(records);
    }

    getRecords();

    return;
  }, [records.length]);

  async function deleteRecord(id) {
    await fetch(`http://localhost:5050/${id}`, {
      method: "DELETE",
    });

    const newRecords = records.filter((el) => el._id !== id);
    setRecords(newRecords);
  }

  function recordList() {
    return records.map((record) => {
      return (
        <Record
          record={record}
          deleteRecord={() => deleteRecord(record._id)}
          key={record._id}
        />
      );
    });
  }

  return (
    <div>
      <h3>Record List</h3>
      <table className="table table-striped" style={{ marginTop: 20 }}>
        <thead>
          <tr>
            <th>Name</th>
            <th>Position</th>
            <th>Level</th>
            <th>Action</th>
          </tr>
        </thead>
        <tbody>{recordList()}</tbody>
      </table>
    </div>
  );
};

export default RecordList;

App.js

import React from "react";

import { Route, Routes } from "react-router-dom";
import Create from "./components/Create";
import Edit from "./components/Edit";
import Navbar from "./components/Navbar";
import RecordList from "./components/RecordList";

const App = () => {
  return (
    <div>
      <Navbar />
      <Routes>
        <Route exact path="/" element={<RecordList />} />
        <Route path="/edit/:id" element={<Edit />} />
        <Route path="/create" element={<Create />} />
      </Routes>
    </div>
  );
};

export default App;
profile
학생 점심을 좀 차리시길 바랍니다

0개의 댓글