server.js
// 설치한 라이브러리를 가져온다
const express = require('express');
const app = express(); // 기본적으로 express를 사용할 수 있는 변수를 생성
// express포트 설정
const PORT = process.env.PORT || 5000;
const bodyParser = require('body-parser');
const cors = require('cors');
// config body-parser
app.use(bodyParser.json());
app.use(cors());
// mysql 데이터베이스 사용
const mysql = require('mysql');
// db 접속 정보
const db = mysql.createConnection({
host: "127.0.0.1",
user: "user_icia",
password: "1111",
port: "3306",
database: "db_icia"
});
//db접속
db.connect((err)=>{
if(!err){
console.log('db접속 성공!');
} else {
console.log('db접속 실패!');
}
});
// 서버 접속
app.listen(PORT, () => {
console.log(`Server On : http://localhost:${PORT}`);
});
// 처음 express에 접속 했을 경우
app.get('/', (req, res) => {
console.log('root!');
});
// 게시글 목록 불러오기
app.get('/list', (req, res) => {
console.log(`app.get('/list')`);
const sql = "select * from board order by id desc";
db.query(sql, (err, data) => {
if(!err){
// console.log(data);
res.send(data);
} else {
console.log(err);
}
});
});
// 게시글 상세보기
app.get('/view/:id', (req, res) => {
const id = req.params.id;
console.log(`app.get('/view/${id}')`);
const sql = "select * from board where id=?";
db.query(sql, [id], function(err, data) {
if(!err){
console.log(data);
res.send(data);
} else {
console.log(err);
}
});
});
//게시글 등록
app.post('/insert', function(req, res) {
const title=req.body.title;
const contents=req.body.contents;
const writer=req.body.writer;
console.log(title, contents, writer);
const sql='insert into board(title, contents, writer) values(?,?,?)';
db.query(sql, [title, contents, writer], function(err, data){
if(!err){
console.log(data);
res.sendStatus(200);
} else {
console.log(err);
}
});
});
//게시글 수정
app.post("/update/:id", function(req, res) {
console.log(`수정 확인!`);
const id = req.params.id;
const title=req.body.title;
const contents=req.body.contents;
const writer=req.body.writer;
console.log(id, title, contents, writer);
const sql='update board set title=?, contents=?, writer=? where id=?';
db.query(sql, [title, contents, writer, id], function(err, data){
if(!err){
console.log(data);
res.sendStatus(200);
} else {
console.log(err);
}
});
});
// 게시글 삭제
app.post('/delete/:id', (req, res) => {
});
App.js
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import BoardMain from './component/BoardMain';
function App() {
return (
<div className="App">
<BoardMain />
</div>
);
}
export default App;
BoardMain.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import BoardMain from './BoardMain';
import BoardWrite from './BoardWrite';
import BoardView from './BoardView';
import BoardList from './BoardList';
import BoardModify from './BoardModify';
const BoardMain = () => {
return (
<div id="board-main">
<BrowserRouter>
<Routes>
<Route path="/" element={<BoardList />} />
<Route path="/list" element={<BoardList />} />
<Route path="/write" element={<BoardWrite />} />
<Route path="/view/:id" element={<BoardView />} />
<Route path="/modify/:id" element={<BoardModify />} />
</Routes>
</BrowserRouter>
</div>
)
}
export default BoardMain;
BoardList.jsx
import { Table, Button} from 'react-bootstrap'
import { useEffect , useState } from 'react';
import axios from 'axios';
import {Link} from 'react-router-dom'
const BoardList = () => {
const [boardList, setBoardList] = useState({});
// hook의 일종
// 마운트와 업데이트 사이
// 컴포넌트가 랜더링 될 때 , 업데이트 될 때 실행
const getBoardData = async () => {
const boards = await axios('/list');
console.log(boards);
setBoardList(boards.data);
}
useEffect(() => {
getBoardData()
}, []); //
if(boardList.length>0){
return(
<div className='board-list'>
<h1>게시글 목록</h1>
<Table striped bordered hover>
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
{boardList.map(boards=>(
<tr >
<td>{boards.id}</td>
<td><Link to={`/view/${boards.id}`}>{boards.title}</Link></td>
<td>{boards.writer}</td>
<td>{boards.reg_date}</td>
</tr>)
)}
</tbody>
</Table>
<Link to={`/write`} >
<Button className='mx-2 btnWrite'>작성하기</Button>
</Link>
</div>
);
}
}
export default BoardList;
BoardWrite.jsx
import axios from 'axios';
import React, { useState } from 'react'
import { Row, Col, Form, Button } from 'react-bootstrap'
const BoardWrite = () => {
const [form, setForm] = useState({
title: '',
contents: '',
writer: ''
});
const { title, contents, writer } = form;
const onChange = (e) => {
setForm({
...form,
[e.target.name] : e.target.value
})
}
const onSubmit = async () => {
if (title === '') {
alert('제목을 입력하세요!');
} else if (contents === '') {
alert('내용을 입력하세요!');
} else {
if (window.confirm('게시글을 등록하시겠습니까?')) {
await axios.post('/insert', form);
window.location.href = "/list";
}
}
}
const onReset = () => {
setForm({
...form,
title: '',
contents: '',
writer: ''
});
}
return (
<Row className='my-5'>
<Col className='p-5'>
<h1 className='text-center my-5'>게시글 작성</h1>
<Form>
<h4>제목</h4><Form.Control placeholder='제목을 입력하세요.'
className='my-3' name='title' value={title} onChange={onChange} />
<h4>작성자</h4><Form.Control placeholder='작성자를 입력하세요.'
className='my-3' name='writer' value={writer} onChange={onChange} />
<h4>내용</h4><Form.Control as='textarea' rows={10} placeholder='내용을 입력하세요.'
className='my-3' name='contents' value={contents} onChange={onChange} />
<div className='text-center'>
<Button className='mx-2 px-3 btn-sm' onClick={onSubmit}>저장</Button>
<Button className='mx-2 px-3 btn-sm' onClick={onReset} variant='secondary'>초기화</Button>
</div>
</Form>
</Col>
</Row>
)
}
export default BoardWrite;
BoardView.jsx
import React, { useEffect, useState } from 'react';
import { useParams , Link } from 'react-router-dom';
import axios from 'axios';
import {Row, Col, Button, Card} from 'react-bootstrap'
const BoardView = () => {
const { id } = useParams(); // /board/:id와 동일한 변수명으로 데이터를 꺼낼 수 있습니다.
const [board, setBoard] = useState({});
const getBoard = async () => {
const board = await axios.get(`/view/${id}`);
console.log(`/view/${id}`);
setBoard(board.data[0]);
}
useEffect(() => {
getBoard();
},[]);
const onDelete = async() => {
if(window.confirm(id + '번 게시글을 삭제하시겠습니까?')) {
}
}
return (
<div className="board-view">
<Row className='my-5'>
<Col className='px-5'>
<h1 className='my-5 text-center'>{board.id}번 게시글 정보</h1>
<div className='text-end my-2'>
<Link to={`/modify/${id}`}>
<Button className='btn-sm mx-2'>수정</Button>
</Link>
<Button className='btn-sm' variant='danger'
onClick={onDelete}>삭제</Button>
</div>
<Card>
<Card.Body>
<h5>[{board.id}] {board.title}</h5>
<hr/>
<div className='cArea'>{board.contents}</div>
</Card.Body>
<Card.Footer>
Created on {board.reg_date} by {board.writer}
</Card.Footer>
</Card>
</Col>
</Row>
</div>
);
};
export default BoardView;
BoardModify.jsx
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';
import { Row, Col, Form, Button } from 'react-bootstrap'
const BoardModify = () => {
const { id } = useParams();
const [board, setBoard] = useState({});
const getBoard = async () => {
const board = await (await axios.get(`/view/${id}`));
setBoard(board.data[0]);
};
useEffect(() => {
getBoard();
},[]);
const [form, setForm] = useState({
title: '',
contents: '',
writer: ''
});
const { title, contents, writer } = form;
const onChange = (e) => {
setForm({
...form,
[e.target.name]: e.target.value
})
}
const onReset = () => {
setForm({
...form,
title: '',
contents: '',
writer: ''
});
}
const onSubmit = async () => {
if (title === '') {
alert('제목을 입력하세요!');
} else if (contents === '') {
alert('내용을 입력하세요!');
} else {
if (window.confirm('게시글을 수정하시겠습니까?')) {
await axios.post(`/update/${id}`, form);
window.location.href = "/list";
}
}
}
return (
<Row className='my-5'>
<Col className='p-5'>
<h1 className='text-center my-5'>게시글 수정</h1>
<Form>
<h4>제목</h4> <Form.Control placeholder={board.title}
className='my-3' name='title' value={title} onChange={onChange} />
<h4>작성자</h4><Form.Control placeholder={board.writer}
className='my-3' name='writer' value={writer} onChange={onChange} />
<h4>내용</h4><Form.Control as='textarea' rows={10} placeholder={board.contents}
className='my-3' name='contents' value={contents} onChange={onChange} />
<div className='text-center'>
<Button className='mx-2 px-3 btn-sm'
onClick={onSubmit}>저장</Button>
<Button className='mx-2 px-3 btn-sm'
onClick={onReset} variant='secondary'>초기화</Button>
</div>
</Form>
</Col>
</Row>
);
};
export default BoardModify;
package.json
{
"name": "board-test",
"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.5.1",
"body-parser": "^1.20.2",
"bootstrap": "^5.3.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"json": "^11.0.0",
"mysql": "^2.18.1",
"nodemon": "^3.0.1",
"react": "^18.2.0",
"react-bootstrap": "^2.9.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.16.0",
"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"
]
}
}