[React] node js를 활용한 게시판!

P1ush·2021년 4월 16일
6

React

목록 보기
6/10
post-thumbnail

React와 Node js를 활용해서 데이터베이스를 연동시킨 게시판을 만들어보았습니다.

1. 폴더구조

프론트와 백엔드 구분을 위하여 클라이언트 폴더와 서버 폴더 2가지로 나눴습니다.



2. 사용된 라이브러리 / 모듈

  • ckEditor [설치하기]
    -> 게시판에 글을 쓸때 쉽게 편집할 수 있도록 도와주는 툴입니다.

  • React Html Parser (yarn add node-html-parser)
    -> HTML 문자열을 React 구성요소로 변환해줍니다.

  • express
    -> Node js를 위한 웹 프레임워크입니다.

  • body-Parser
    -> POST 방식의 데이터 요청을 처리하는 모듈입니다.

  • mySQL (yarn add express body-parser mysql)
    -> 오픈소스 RDBMS입니다. (DB종류는 원하시는거 쓰시면됩니다.)

  • nodemon (yarn add nodemon)
    -> 서버코드가 수정되면 서버를 자동으로 재시작하는 모듈입니다.

  • Axios (yarn add axios)
    -> GET , POST , PUT 등의 메서드로 API를 요청할 수 있습니다.

  • CORS (yarn add cors)
    -> 도메인 또는 포트가 다른 서버의 데이터를 요청할 수 있습니다.



3. 코드

전체코드

server > index.js

const express = require('express');
const app = express();
const mysql = require('mysql');
const bodyParser = require('body-parser');
const { urlencoded } = require('body-parser');
const PORT = process.env.port || 8000;
const cors = require('cors');

const db = mysql.createPool({
    host: "localhost",
    user: "root",
    password: "패스워드",
    database: "DB명"
});


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

app.get("/api/get", (req, res)=>{
    const sqlQuery = "SELECT * FROM simpleboard;";
    db.query(sqlQuery, (err, result)=>{
        res.send(result);
    })
})

app.post("/api/insert", (req, res) =>{
    const title = req.body.title; 
    const content = req.body.content; 
    const sqlQuery = "INSERT INTO simpleboard (title, content) VALUES (?,?)";
    db.query(sqlQuery, [title, content], (err, result) => {
        res.send('success!'); 
    });
});

app.listen(PORT, ()=>{
    console.log(`running on port ${PORT}`);
});

모듈 불러오기

const express = require('express');
const app = express();
const mysql = require('mysql');
const bodyParser = require('body-parser');
const { urlencoded } = require('body-parser');
const PORT = process.env.port || 8000;
const cors = require('cors');

yarn add로 설치한 모듈을 불러오는 코드입니다.

line 6 :

const PORT = process.env.port || 8000;

node.js의 포트를 설정합니다. 기본 포트는 8000으로 사용하고, 환경변수에 PORT라는 이름으로
포트명을 지정했을경우 그 포트명을 사용한다는 의미입니다.


line 9 ~ 14 :

const db = mysql.createPool({
    host: "localhost",
    user: "root",
    password: "패스워드",
    database: "DB명"

데이터베이스와 연결되는 객체를 생성하는 구문입니다. 객체에는 호스트 (host) , 유저 권한 (user) , DB패스워드 (password) , 데이터베이스명 등 여러가지 정보가 들어가게 됩니다.


line 17 ~ 19 :

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

app.js에서 cors , express.json을 사용합니다.
extended : true | false 옵션은 true 일경우 따로 설치가 필요한 npm qs (querystring) 라이브러리를 사용합니다. false 일경우 node.js에 기본적으로 내장되어있는 qs 라이브러리를 사용하겠다는 의미입니다.


line 21 ~ 26 :

app.get("/api/get", (req, res)=>{
    const sqlQuery = "SELECT * FROM simpleboard;";
    db.query(sqlQuery, (err, result)=>{
        res.send(result);
    })
})

api/get 경로로 sql 쿼리가 "SELECT * FROM simpleboard;"와 같이 들어올경우 쿼리 결과값과 에러 (에러가 있으면) 를 출력합니다.


line 28 ~ 35 :

app.post("/api/insert", (req, res) =>{
    const title = req.body.title; 
    const content = req.body.content; 
    const sqlQuery = "INSERT INTO simpleboard (title, content) VALUES (?,?)";
    db.query(sqlQuery, [title, content], (err, result) => {
        res.send('success!'); 
    });
});

(?,?) : 문자열 구분을 위해 따옴표를 쓸필요없이 하나의 변수로 통합합니다.
title , content에 sql쿼리문을 통하여 값을 삽입합니다.


client > App.js

import { useEffect, useState} from 'react';
import './App.css';
import { CKEditor } from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import ReactHtmlParser from 'react-html-parser';
import Axios from 'axios';

function App() {
  const [movieContent, setMovieContent] = useState({
    title: '',
    content: ''
  })

  const [viewContent , setViewContent] = useState([]);

  useEffect(()=>{
    Axios.get('http://localhost:8000/api/get').then((response)=>{
      setViewContent(response.data);
    })
  },[viewContent])

  const submitReview = ()=>{
    Axios.post('http://localhost:8000/api/insert', {
      title: movieContent.title,
      content: movieContent.content
    }).then(()=>{
      alert('등록 완료!');
    })
  };

  const getValue = e => {
    const { name, value } = e.target;
    setMovieContent({
      ...movieContent,
      [name]: value
    })
  };

  return (
    <div className="App">
      <h1>Movie Review</h1>
      <div className='movie-container'>
        {viewContent.map(element =>
          <div className="title">
            <h2>{element.title}</h2>
            <div className="cont">
              {ReactHtmlParser(element.content)}
            </div>
          </div>
        )}
      </div>
      <div className='form-wrapper'>
        <input className="title-input"
          type='text'
          placeholder='제목'
          onChange={getValue}
          name='title'
        />
        <CKEditor
          editor={ClassicEditor}
          data="Hello from CKEditor 5!"
          onReady={editor => {
          }}
          onChange={(event, editor) => {
            const data = editor.getData();
            console.log({ event, editor, data });
            setMovieContent({
              ...movieContent,
              content: data
            })
          }}
          onBlur={(event, editor) => {
            console.log('Blur.', editor);
          }}
          onFocus={(event, editor) => {
            console.log('Focus.', editor);
          }}
        />
      </div>
      <button className="submit-button" onClick={submitReview}>입력</button>
    </div>
  );
}

export default App;

line 1 ~ 6 :

import { useEffect, useState} from 'react';
import './App.css';
import { CKEditor } from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import ReactHtmlParser from 'react-html-parser';
import Axios from 'axios';

설치한 모듈들을 import 하고, hook을 선언합니다.


line 8 ~ 12 :

function App() {
  const [movieContent, setMovieContent] = useState({
    title: '',
    content: ''
  })

입력한 내용을 state에 저장합니다.


line 22 ~ 29 :

const submitReview = ()=>{
    Axios.post('http://localhost:8000/api/insert', {
      title: movieContent.title,
      content: movieContent.content
    }).then(()=>{
      alert('등록 완료!');
    })
  };

포트 8000으로 값을 전송하기 위해 axios를 사용하였고, title , content 데이터를 post 방식으로 보내고 완료 시 alert창이 뜨게됩니다.


line 31 ~ 37 :

const getValue = e => {
    const { name, value } = e.target;
    setMovieContent({
      ...movieContent,
      [name]: value
    })
  };

이벤트가 발생하면 그 이벤트의 name과 value를 가져옵니다. 이벤트가 발생되면 (input에 텍스트를 입력하면) 입력한 내용을 콘솔창에서 확인할 수 있습니다.




4. 결과물

Input창에 제목과 내용을 씁니다.





그리고, 입력 버튼을 누르면 등록 완료! 라는 alert창이 뜨게됩니다.





방금 Input창에 입력했던 제목과 내용이 그대로 표시됩니다. (DB에 저장됩니다.)





입력한 제목과 내용이 DB에 저장된 모습입니다. (delete 명령을 통하여 값을 DB에서 삭제하면 화면에 표시되는 내용도 함께 삭제됩니다.)


4/22 추가사항!

상단에 SVG 이미지를 추가했습니다!



path01

SVG 이미지의 path 좌표값을 이용하여 이미지 하나에 여러 개의 영역들을 설정할 수 있습니다.
(path 좌표값은 직접 설정해도 되지만, 필자의 경우는 SVG 이미지 자체를 다운로드해서 사용하였습니다.)

또한, fill 속성을 통하여 path 부분에 색상을 정할 수도있고, hover , before & after 등 가상선택자도 가능합니다.



path02



path03



SVG부분 코드


App.js

<div className="react-svg">
	<SVG/>
</div>



client > src > SVG.js

import React from 'react';
const SVG = () => {
    return (
            <svg id="레이어_1" data-name="레이어 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 149.88"><defs></defs><path id="svg-path01" d="M18.14,83c-4.55,11.39-4.68,21,.61,26.25,2.71,2.72,6.54,4,11.17,4A41.9,41.9,0,0,0,45,109.87C49.85,121.13,56.52,128,64,128s14.15-6.87,19-18.13a41.9,41.9,0,0,0,15.08,3.38c4.63,0,8.46-1.29,11.17-4,5.29-5.28,5.16-14.86.61-26.25C121.13,78.15,128,71.48,128,64s-6.87-14.15-18.14-19c4.55-11.39,4.68-21-.61-26.25S94.39,13.59,83,18.14C78.15,6.87,71.48,0,64,0S49.85,6.87,45,18.14c-11.39-4.55-21-4.68-26.25.61S13.59,33.61,18.14,45C6.87,49.85,0,56.52,0,64S6.87,78.15,18.14,83Zm3.43,23.43c-4.12-4.12-3.69-12.19.28-22a100.11,100.11,0,0,0,17.21,4.48,100.11,100.11,0,0,0,4.48,17.21c-9.78,4-17.84,4.4-22,.28Zm15.5-38.07c-1.17-1.45-2.29-2.91-3.36-4.36,1.07-1.45,2.19-2.91,3.36-4.36C37,61.08,37,62.53,37,64s0,2.92.07,4.36Zm-5.82-7.81a98.76,98.76,0,0,1-7.78-13.34A100,100,0,0,1,38.4,43.28c-.44,3.13-.77,6.36-1,9.7Q34.11,56.76,31.25,60.55ZM37.39,75q.34,5,1,9.7a100,100,0,0,1-14.93-3.93,98.76,98.76,0,0,1,7.78-13.34Q34.1,71.23,37.39,75ZM47.74,47.74q3.38-3.39,6.85-6.44c3.08-.2,6.21-.3,9.41-.3s6.33.1,9.41.3q3.47,3,6.85,6.44T86.7,54.6q.3,4.6.3,9.4c0,3.19-.1,6.33-.3,9.4q-3,3.46-6.44,6.86T73.4,86.7q-4.61.3-9.4.3c-3.19,0-6.33-.1-9.4-.3q-3.47-3-6.86-6.44T41.3,73.4Q41,68.79,41,64c0-3.19.1-6.33.3-9.4Q44.35,51.14,47.74,47.74Zm-5.87.3q.3-2.77.71-5.46,2.67-.4,5.46-.71-1.57,1.49-3.13,3T41.87,48Zm17.75-11q2.19-1.76,4.38-3.36c1.46,1.07,2.91,2.19,4.38,3.36C66.93,37,65.47,37,64,37S61.07,37,59.62,37.07ZM80,41.87q2.78.3,5.46.71.4,2.67.71,5.46-1.48-1.57-3-3.13T80,41.87ZM86.13,80q-.3,2.78-.71,5.46-2.67.4-5.45.71,1.56-1.48,3.12-3t3-3.13Zm-17.77,11c-1.45,1.17-2.91,2.29-4.36,3.36-1.45-1.07-2.91-2.19-4.36-3.36,1.44,0,2.89.07,4.36.07S66.92,91,68.36,90.93ZM48,86.13q-2.78-.3-5.45-.71-.4-2.67-.71-5.46,1.49,1.58,3,3.13T48,86.13ZM43.28,89.6q4.69.65,9.69,1,3.8,3.28,7.58,6.14a98.76,98.76,0,0,1-13.34,7.78A100,100,0,0,1,43.28,89.6ZM64,124c-5.83,0-11.23-6-15.33-15.72a100.76,100.76,0,0,0,15.33-9,100.76,100.76,0,0,0,15.33,9C75.23,118,69.83,124,64,124Zm16.79-19.47a98.76,98.76,0,0,1-13.34-7.78Q71.23,93.9,75,90.61c3.33-.24,6.56-.57,9.69-1A100,100,0,0,1,80.79,104.53Zm25.64,1.9c-4.12,4.12-12.19,3.69-22-.28a100.11,100.11,0,0,0,4.48-17.21,100.11,100.11,0,0,0,17.21-4.48c4,9.78,4.4,17.85.28,22ZM90.93,59.64c1.17,1.45,2.29,2.91,3.36,4.36-1.07,1.45-2.19,2.91-3.36,4.36,0-1.44.07-2.89.07-4.36S91,61.08,90.93,59.64ZM90.61,53q-.35-5-1-9.7a100,100,0,0,1,14.93,3.93,98.76,98.76,0,0,1-7.78,13.34Q93.9,56.77,90.61,53Zm0,22q3.28-3.78,6.14-7.57a98.76,98.76,0,0,1,7.78,13.34A100,100,0,0,1,89.6,84.72C90,81.59,90.37,78.36,90.61,75ZM124,64c0,5.83-6,11.23-15.72,15.34a101.2,101.2,0,0,0-9-15.34,101.2,101.2,0,0,0,9-15.34C118,52.77,124,58.17,124,64ZM98,18.67c3.54,0,6.41.93,8.39,2.9,4.12,4.12,3.69,12.19-.28,22a100.11,100.11,0,0,0-17.21-4.48,100.11,100.11,0,0,0-4.48-17.21A37.5,37.5,0,0,1,98,18.67ZM84.72,38.4c-3.13-.44-6.36-.77-9.7-1q-3.78-3.28-7.57-6.14a100.91,100.91,0,0,1,13.34-7.78A100,100,0,0,1,84.72,38.4ZM64,4c5.83,0,11.23,6,15.34,15.72a101.2,101.2,0,0,0-15.34,9,101.2,101.2,0,0,0-15.34-9C52.77,10,58.17,4,64,4ZM47.21,23.47a100.91,100.91,0,0,1,13.34,7.78Q56.77,34.1,53,37.39q-5,.34-9.7,1A100,100,0,0,1,47.21,23.47Zm-25.64-1.9c2-2,4.85-2.9,8.39-2.9a37.5,37.5,0,0,1,13.58,3.18,100.11,100.11,0,0,0-4.48,17.21,100.11,100.11,0,0,0-17.21,4.48C17.88,33.76,17.45,25.69,21.57,21.57ZM19.72,48.66a101.2,101.2,0,0,0,9,15.34,101.2,101.2,0,0,0-9,15.34C10,75.23,4,69.83,4,64S10,52.77,19.72,48.66Z"/><path id="svg-path02" d="M64,81A17,17,0,1,0,47,64,17,17,0,0,0,64,81Zm0-30A13,13,0,1,1,51,64,13,13,0,0,1,64,51Z"/><path id="svg-path03" d="M64,72a8,8,0,1,0-8-8A8,8,0,0,0,64,72Zm0-12a4,4,0,1,1-4,4A4,4,0,0,1,64,60Z"/></svg>
    );
};
export default SVG;



SVG에 대한 자세한 내용들은 아래 링크를 참고하세요.


PNG/SVG 아이콘 다운로드 사이트 >
리액트에서 SVG 사용하기 >
웹에서 SVG 사용하기 >
SVG란? >

0개의 댓글