Front-end/React

React 기초(게시판)- AWS 풀스택 과정 43일차

awspspgh 2024. 9. 20. 18:17
목차
1. 게시판
2. 느낀 점

 

1. 게시판

▣ board-app

- App.js

import './App.css';
import BoardHome from './component/BoardHome';

function App() {
  return (
    <div className="App">
      <BoardHome/>
    </div>
  );
}

export default App;

 

- sever.js

// 설치한 라이브러리 변수로 받아오기
const express = require('express');
const bodyParser = require('body-parser');
const mysql = require('mysql');
const cors = require('cors');

//express 사용하기 위한 app 생성
const app = express();

//express 사용할 서버포트 설정
const PORT = 5000;

app.use(cors());
app.use(bodyParser.json());

//DB 접속
const db = mysql.createConnection({
    host : 'localhost',
    user: 'react',  // 생성
    password: 'mysql',
    port:'3306',
    database:'db_react'  // 생성
});

// express 접속
app.listen(PORT, ()=>{
    console.log(`server connecting on : http://localhost:${PORT}`);
});

//db 연결
db.connect((err)=>{
    if(!err){
        console.log("seccuss");

    }else{
        console.log("fail");
    }
});

// DB에서 값을 가져오기

// / => root 연결시 보여지는 기본화면 설정
app.get('/',(req, res) => {
    res.send("React Server Connect Success!!")
})

// 게시글 목록 가져오기
app.get('/list', (req, res) => {
    // console.log('/list');
    const sql = 'select * from board order by id desc';
    db.query(sql, (err, data)=>{
        if(!err){
            res.send(data);
        }else{
            console.log(err);
            res.send('전송오류');
        }
    });
});

// 게시글 하나 가져오기 : id
// 화면에서 서버로 요청하는 값 : request (req)
// 서버에서 화면으로 보내주는 값 : response (res)
// 화면에서 가져온 파라미터 추출 : req.params.id
app.get('/view/:id', (req, res) => {
    // 파라미터 가져오기
    const id = req.params.id;
    console.log(`/view/${id}`);
    const sql = `select * from board where id = ${id}`;
    db.query(sql, (err, data) => {
        if(!err){
            res.send(data);
        }else{
            console.log(err);
            res.send("전송오류");
        }
    })
});

// board 등록
app.post('/insert', (req, res) => {
    // 파라미터 가져오기 requset.body
    // const board = req.body;
    // board.title
    const { title, writer, contents } = req.body;

    const sql = 'insert into board(title, writer, contents) value (?,?,?)';
    db.query(sql, [title, writer, contents], (err, data) => {
        if(!err){
            // res.send("OK");
            res.sendStatus(200); // 전송 잘됨
        }else{
            console.log(err);
            res.send("전송오류");
        }
    })
});

// 수정-불러오기
app.get('/modify/:id', (req, res) => {
    // 파라미터 가져오기
    const id = req.params.id;
    console.log(`/modify/${id}`);
    const sql = `select * from board where id = ${id}`;
    db.query(sql, (err, data) => {
        if(!err){
            res.send(data);
        }else{
            console.log(err);
            res.send("전송오류");
        }
    })
});

// 수정-저장
app.post('/modify/:id', (req, res) => {
    // 파라미터 가져오기 requset.body
    // const board = req.body;
    // board.title
    const id = req.params.id;

    const { title, writer, contents } = req.body;

    const sql = `update board set title=?, writer=?, contents=? where id=?`;
    db.query(sql, [title, writer, contents, id], (err, data) => {
        if(!err){
            // res.send("OK");
            res.sendStatus(200); // 전송 잘됨
        }else{
            console.log(err);
            res.send("전송오류");
        }
    })
});

// 삭제
app.post('/delete/:id', (req, res) => {
    const id = req.params.id;
    const sql = `delete from board where id=${id}`;
    db.query(sql, (err, data) => {
        if(!err){
            // res.send("OK");
            res.sendStatus(200); // 전송 잘됨
        }else{
            console.log(err);
            res.send("전송오류");
        }
    })
});

 

- BoardHome.jsx

import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import BoardList from './BoardList';
import '../component/board-style.css';
import BoardDetail from './BoardDetail';
import BoardRegister from './BoardRegister';
import BoardModify from './BoardModify';

const BoardHome = () => {
    return (
        <div className='boardHome'>
            <h1 className='title'>My First React Board Project</h1>
            <hr />
            <BrowserRouter>
                <Routes>
                    <Route path='/' element={<BoardList/>}/>
                    <Route path='/list' element={<BoardList/>}/>
                    <Route path="/detail/:id" element={<BoardDetail/>} />
                    <Route path='/register' element={<BoardRegister/>}/>
                    <Route path='/modify/:id' element={<BoardModify/>}/>
                </Routes>
            </BrowserRouter>
        </div>
    );
};

export default BoardHome;

 

- BoardList.jsx

import React, { useState } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
import { useEffect } from 'react';

const BoardList = () => {
    // db에 저장되어 있는 board 요소를 가져오기 => boardList 저장
    const [ boardList, setBoardList ] = useState({});

    // 비동기로 db에 접속하여 select로 가져오기
    // get : 데이터 가져올 때 (생략 가능)
    // post : 데이터를 보낼 때 (반드시 써야 함)
    const getBoardData = async () => {
        try{
            const boards = await axios('/list');
            console.log(boards);
            setBoardList(boards.data); 
        } catch (error) {
            console.log(error);
        }
    }

    // 컴포넌트가 랜더링 될 때, 혹은 업데이트 될 때 실행되는 hooks
    /*
        useEffect(()=>{
            function},[deps]);
        - function : 실행시킬 함수
        - deps :  배열형태로 배열 안에서 검사하고자 하는 특정값
    */
    useEffect(() => {
        getBoardData();
    },[])

    // 서버에서 데이터를 가져오는 것보다 화면에서 핸더링 되는 속도가 더 빠름
    // 조건을 걸어줘서 error 방지

    if(boardList.length > 0){
        return (
            <div className='boardList'>
                <h2>Board List Page</h2>
                <table>
                    <thead>
                        <tr className='subTitle'>
                            <th>번호</th>
                            <th>제목</th>
                            <th>작성자</th>
                            <th>작성일</th>
                        </tr>
                    </thead>
                    <tbody>
                        {boardList.map(b => (
                            <tr key={b.id}>
                                <td className='center'>{b.id}</td>
                                <td><Link to={`/detail/${b.id}`}>{b.title}</Link></td>
                                <td>{b.writer}</td>
                                <td>{b.reg_date.substring(0, b.reg_date.indexOf("T"))}</td>
                            </tr>
                        ))}
                    </tbody>
                </table>
                    <Link to={`/register`}><button className='textButton'>글쓰기</button></Link>
            </div>
        );
    }
};

export default BoardList;

 

- BoardDetail.jsx

import React, { useEffect, useState } from 'react';
import { Link, useParams } from 'react-router-dom';
import axios from 'axios';
// import { boardList } from '../data/data';

const BoardDetail = () => {

    const { id } = useParams();

    // 특정 조건을 만족하는 요소의 index를 찾는 함수 findIndex()
    // boardList.findIndex(b => b.id === Number(id))
    // params는 String으로 값을 가져옴 ==> 따라서 Number로 형변환
    // 굳이 findIndex를 사용하는 이유는 id의 값과 index(boardList의 index)가 맞지 않기 때문

    // const idx = boardList.findIndex(b => b.id === Number(id));
    // console.log(idx);
    
    // const board = boardList[idx];
    // console.log(board);

    const [ board, setBoard ] = useState(null);

    const getBoard = async () => {
        try{
            const res = await axios(`/view/${id}`);
            // res.data : 데이터가 1개 더라도 배열로 돌어옴
            setBoard(res.data[0]);
            console.log(res);
        }catch(error){
            console.log(error);
        }
    };

    useEffect(()=>{
        getBoard();
    },[]);

    const onDelete = async () => {
        if(window.confirm('삭제하시겠습니까?')){
            try{
                await axios.post(`/delete/${id}`);
                window.location.href = `/list`;
            }catch(error){
                console.log(error);
            }
        }
    };

    if(board != null){
        return (
            <div className='boardDetailBoard'>
                <h2>No.{board.id} / Board Detail Page</h2>
                <div className='boardDetailContainer'>
                    <span className='boardTitle'>{board.title}</span>
                    <span className='bold'>{board.writer} [{board.reg_date.substring(0, board.reg_date.indexOf("T"))}]</span>
                    <div>{board.contents}</div>
                </div>
                <div>
                    <Link to={`/modify/${board.id}`}><button>Modify</button></Link>
                    <button onClick={onDelete}>Remove</button>
                    <Link to={'/list'}><button>List</button></Link>
                </div>
            </div>
        );
    }
};

export default BoardDetail;

 

- BoardRegister.jsx

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

const BoardRegister = () => {
    const [ board, setBoard ] = useState({
        title: '',
        writer: '',
        contents: ''
    });

    const { title, writer, contents } = board;

    const onChange = (e) => {
        const { name, value } = e.target;
        setBoard({
            ...board,
            [name]:value
        });
    }

    const onReset = () => {
        setBoard({
            ...board,
            title: '',
            writer: '',
            contents: ''
        })
    }

    const onCreate = async () => {
        // board 객체를 서버로 전송
        // board 객체의 내용 중 하나라도 null이면 안 됨.
        if(title === ''){
            alert('title is null');
            return;
        }
        if(writer === ''){
            alert('wirter is null');
            return;
        }
        if(contents === ''){
            alert('contents is null');
            return;
        }
        if(window.confirm('등록하시겠습니까?')){
            try{
                const res = await axios.post('/insert', board);
                console.log(res);
                // if(res.data[0] === 'OK'){}
                // 데이터 전송 후 이동
                window.location.href = "/list";
            }catch(error){
                console.log(error);
            }
        }
    }

    return (
        <div className='boardRegister'>
            <h2>Board Register</h2>
            <div className='content'>
                <input type="text" className='content-box' name='title' value={title} placeholder='Title' onChange={onChange}/>
                <input type="text" className='content-box' name='writer' value={writer} placeholder='Writer' onChange={onChange}/>
                <div className='contentContainer'>
                <textarea type="text" className='content-box' name='contents' value={contents} placeholder='Contents' onChange={onChange}/>
                </div>
            </div>
            <button onClick={onCreate}>Register</button> 
            <button onClick={onReset}>Init</button> 
        </div>
    );
};

export default BoardRegister;

 

- BoardModify.jsx

// 설치한 라이브러리 변수로 받아오기
const express = require('express');
const bodyParser = require('body-parser');
const mysql = require('mysql');
const cors = require('cors');

//express 사용하기 위한 app 생성
const app = express();

//express 사용할 서버포트 설정
const PORT = 5000;

app.use(cors());
app.use(bodyParser.json());

//DB 접속
const db = mysql.createConnection({
    host : 'localhost',
    user: 'react',  // 생성
    password: 'mysql',
    port:'3306',
    database:'db_react'  // 생성
});

// express 접속
app.listen(PORT, ()=>{
    console.log(`server connecting on : http://localhost:${PORT}`);
});

//db 연결
db.connect((err)=>{
    if(!err){
        console.log("seccuss");

    }else{
        console.log("fail");
    }
});

// DB에서 값을 가져오기

// / => root 연결시 보여지는 기본화면 설정
app.get('/',(req, res) => {
    res.send("React Server Connect Success!!")
})

// 게시글 목록 가져오기
app.get('/list', (req, res) => {
    // console.log('/list');
    const sql = 'select * from board order by id desc';
    db.query(sql, (err, data)=>{
        if(!err){
            res.send(data);
        }else{
            console.log(err);
            res.send('전송오류');
        }
    });
});

// 게시글 하나 가져오기 : id
// 화면에서 서버로 요청하는 값 : request (req)
// 서버에서 화면으로 보내주는 값 : response (res)
// 화면에서 가져온 파라미터 추출 : req.params.id
app.get('/view/:id', (req, res) => {
    // 파라미터 가져오기
    const id = req.params.id;
    console.log(`/view/${id}`);
    const sql = `select * from board where id = ${id}`;
    db.query(sql, (err, data) => {
        if(!err){
            res.send(data);
        }else{
            console.log(err);
            res.send("전송오류");
        }
    })
});

// board 등록
app.post('/insert', (req, res) => {
    // 파라미터 가져오기 requset.body
    // const board = req.body;
    // board.title
    const { title, writer, contents } = req.body;

    const sql = 'insert into board(title, writer, contents) value (?,?,?)';
    db.query(sql, [title, writer, contents], (err, data) => {
        if(!err){
            // res.send("OK");
            res.sendStatus(200); // 전송 잘됨
        }else{
            console.log(err);
            res.send("전송오류");
        }
    })
});

// 수정-불러오기
app.get('/modify/:id', (req, res) => {
    // 파라미터 가져오기
    const id = req.params.id;
    console.log(`/modify/${id}`);
    const sql = `select * from board where id = ${id}`;
    db.query(sql, (err, data) => {
        if(!err){
            res.send(data);
        }else{
            console.log(err);
            res.send("전송오류");
        }
    })
});

// 수정-저장
app.post('/modify/:id', (req, res) => {
    // 파라미터 가져오기 requset.body
    // const board = req.body;
    // board.title
    const id = req.params.id;

    const { title, writer, contents } = req.body;

    const sql = `update board set title=?, writer=?, contents=? where id=?`;
    db.query(sql, [title, writer, contents, id], (err, data) => {
        if(!err){
            // res.send("OK");
            res.sendStatus(200); // 전송 잘됨
        }else{
            console.log(err);
            res.send("전송오류");
        }
    })
});

// 삭제
app.post('/delete/:id', (req, res) => {
    const id = req.params.id;
    const sql = `delete from board where id=${id}`;
    db.query(sql, (err, data) => {
        if(!err){
            // res.send("OK");
            res.sendStatus(200); // 전송 잘됨
        }else{
            console.log(err);
            res.send("전송오류");
        }
    })
});

 

- board-style.css

.boardList {
    font-family: Arial, sans-serif;
    margin: 20px;
}
.title{
    background-color: black;
    color: transparent;
    -webkit-background-clip: text;
    margin-bottom: 30px;
}
h2 {
    text-align: center;
    color: #667eea;
}
table {
    width: 1024px;
    border-collapse: collapse;
    margin: 20px auto;
}
.subTitle{
    background: linear-gradient(to right top, #12c2e975, #c471ed7a, #f64f5a8c);
}
.subTitle>th{
    color: #764ba2;
}
th, td {
    border: 1px solid #ddd;
    padding: 8px;
    text-align: left;
}
.center{
    text-align: center;
}
th {
    color: #333;
}
tr:nth-child(even) {
    background-color: #F9F9F9;
}
tr:hover {
    background: linear-gradient(to right, rgba(135, 207, 235, 0.452), rgba(255, 192, 203, 0.479));
    color: #667eea;
    font-weight: 700;
}
table {
    box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}
/* th:nth-child(1), td:nth-child(1) {
    width: 3%;
    word-break: break-word;
} */
.textButton{
    width: 150px;
    height: 60px;
    color: white;
    font-size: 20px;
    font-weight: 700;
    /* border 그라데이션 - button */
    border: 1px solid transparent;
    border-radius: 20px;
    background-image: linear-gradient(to right top, #12c2e975, #c471ed7a, #f64f5a8c);
    background-origin: border-box;
    background-clip: content-box, border-box;
}
.textButton:hover{
    cursor: pointer;
    transform: scale(1.05);
}
.textButton:active {
    transform: scale(0.95);
}

/* BoardDetail */
a{
    text-decoration-line: none;
}
.boardDetailContainer{
    font-family: Arial, sans-serif;
    margin: 40px;
}
.boardDetailBoard button{
    width: 150px;
    height: 60px;
    color: white;
    font-size: 20px;
    font-weight: 700;
    /* border 그라데이션 - button */
    border: 1px solid transparent;
    border-radius: 20px;
    background-image: linear-gradient(to right top, #12c2e975, #c471ed7a, #f64f5a8c);
    background-origin: border-box;
    background-clip: content-box, border-box;
}
button>a{
    color: white;
}
.boardDetailBoard button:hover{
    cursor: pointer;
    transform: scale(1.05);
}
.boardDetailBoard button:active {
    transform: scale(0.95);
}
.boardDetailContainer{
    margin: 0 auto;
    width: 1000px;
    /* border 그라데이션 */
    border: 2px solid transparent;
    border-radius: 15px;
    background-image: linear-gradient(#fff, #fff), linear-gradient(to right top, #12c2e975, #c471ed7a, #f64f5a8c);
    background-origin: border-box;
    background-clip: content-box, border-box;
}
.boardDetailContainer>span{
    padding: 20px 20px 10px 5px;
}
.boardDetailContainer>div{
    margin: 20px 0 20px 0;
}
.bold{
    font-size: 15px;
    font-weight: bold;
}

/* BoardRegister */
.content{
    width: 1024px;
    margin: 0 auto;
    /* border 그라데이션 */
    border: 2px solid transparent;
    border-radius: 15px;
    background-image: linear-gradient(#fff, #fff), linear-gradient(to right top, #12c2e975, #c471ed7a, #f64f5a8c);
    background-origin: border-box;
    background-clip: content-box, border-box;
}
.content>.content-box{
    width: 100px;
    height: 45px;
    padding: 10px;
    border: none;
    outline: none;
    font-size: 15px;
    font-weight: 500;
    margin: 0 20px 0 33px;
}
.content>.content-box:first-child{
    margin-left: 34px;
}
.content>.content-box:nth-child(2){
    width: 250px;
}
.content>div>.content-box{
    width: 800px;
    height: 300px;
    padding: 10px;
    border: 1px solid pink;
    outline: none;
    font-size: 15px;
    font-weight: 500;
    margin: 10px;
}
.contentContainer{
    background-color: rgba(255, 192, 203, 0.322)
}
.boardRegister button{
    width: 150px;
    height: 60px;
    color: white;
    margin-top: 20px;
    font-size: 20px;
    font-weight: 700;
    /* border 그라데이션 - button */
    border: 1px solid transparent;
    border-radius: 20px;
    background-image: linear-gradient(to right top, #12c2e975, #c471ed7a, #f64f5a8c);
    background-origin: border-box;
    background-clip: content-box, border-box;
}
.boardRegister button:hover{
    cursor: pointer;
    transform: scale(1.05);
}
.boardRegister button:active {
    transform: scale(0.95);
}

 

- package.json

{
  "name": "board-app",
  "version": "0.1.0",
  "private": true,
  "proxy" : "http://localhost:5000/",
  "dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^1.7.7",
    "cors": "^2.8.5",
    "express": "^4.21.0",
    "json": "^11.0.0",
    "mysql": "^2.18.1",
    "nodemon": "^3.1.5",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-router-dom": "^6.26.2",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

 

cmd (1)

 

cmd (2)

 

▷ 출력

 

Register

 

Modify

 

Remove

 

2. 느낀 점

저번 프로젝트에서 게시판을 만들어보고 싶었는데 이번 기회에 만들어볼 수 있어서 좋았다.