React / Node.js / MySQL 연동

윤태현·2023년 7월 13일
1

REACT

목록 보기
14/19
post-thumbnail

React와 Node.js 연동을 해봤고 이제 MySQL까지 연동해보려고 한다.
이전글 : React와 Node.js 연동

  • node.js 버전 : 18.16.1 LTS
  • MySQL workbench 사용 (버전 8.0.33)

순서

  1. MySQL workbench 설치
  2. 폴더 구조
  3. MySQL 연결
  4. 간단한 회원가입 및 로그인 만들기

1. MySQL workbench 설치

MySQL workbench 설치 참고한 사이트
https://shinysblog.tistory.com/20


2. 폴더 구조

각각의 폴더 설명 (클라이언트가 요청을 했을 때, 처리 순서? 로 작성)

routes/

  • 애플리케이션의 각 라우트를 정의
  • 클라이언트의 요청은 Express 라우터에 의해 처리
  • 요청이 들어온 경로와 HTTP 메소드에 따라 적절한 라우트 핸들러에 요청을 전달

controllers/

  • HTTP 요청을 처리하는 라우트 핸들러들을 넣음
  • 요청의 유효성을 검사하고 필요한 데이터를 가져오고 적절한 응답을 보내는 등의 작업을 수행

database/

  • 데이터베이스 연결 설정이 있는 파일을 넣음

models/

  • 데이터베이스와 관련된 코드를 저장
  • controllers 와 상호작용하여 데이터베이스 작업을 수행
  • 데이터를 가져오고, 생성하고, 업데이트하고, 삭제하는 기능을 포함

3. MySQL 연결

  • 간단하게 server.js에서 db 연결
npm install mysql
// backend/database/db.js

const mysql = require('mysql');

const conn = mysql.createConnection({
    host: 'localhost',
    port: '포트번호',
    user: 'user',
    password: '비밀번호',
    database: 'my_db'
});

conn.connect((err) => {
    if (err) console.log(err);
    else console.log('Connected to the database');
});

module.exports = conn;
// backend/server.js

const express = require('express');
const db = require('./database/db');

const app = express();
const port = 3001;	// React의 포트 번호와 다르게 하기 위해

app.get('/', (req, res) => {
    db.query('SELECT * FROM table_name', function (err, results, fields) {
        if (err) throw err;
        res.send(results);
    });
});

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

4. 간단한 회원가입 및 로그인 만들기

  1. 폴더 구조 생성
  2. MySQL workbench connection 생성
  3. React에서 회원가입 및 로그인 시 서버 요청
  4. signup db에서 값을 받은 후 데이터베이스에 저장
  5. login db에서 클라이언트가 입력한 값과 일치 여부 확인 후

4-1. 폴더 구조 생성

Your first imageYour second image

4-2. MySQL workbench connection 생성

테이블 생성

  • 회원가입 시 user 테이블에 추가
  • Primary Key를 idx로 설정 후 Auto increase를 추가하여 row가 생길 때마다 자동으로 1씩 증가
  • userID를 string으로 생성
  • userPW는 보안상 BLOB으로 생성

4-3. 기본 설정

hash를 사용하기 위해 bcrypt 설치

npm install bcrypt

클라이언트

// App.js

import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Login from "./components/Login";
import Signup from './components/Signup';
import Main from './pages/Main';


function App() {
  return (
    <Router>
      <Routes>
        <Route path='/' element={<Login />}></Route>
        <Route path='/signup' element={<Signup />}></Route>
        <Route path='/main' element={<Main />}></Route>
      </Routes>
    </Router>
  )
}

export default App;

서버

// server.js

const express = require('express');
const cors = require("cors");
const userRoutes = require('./routes/userRoutes');

const app = express();
app.use(express.json());
app.use(cors({
    origin: 'http://localhost:3000',
    credentials: true,
}));

app.use('/api', userRoutes);        // user 라우트 연결

app.listen(3001, () => {
    console.log("서버 실행")
});
// userRoutes.js

const express = require('express');
const router = express.Router();
const userController = require('../controllers/user');	// 유저 컨트롤러 가져오기

router.post('/signup', userController.signup);			// 회원가입 부분
router.post('/loginCheck', userController.loginCheck);	// 로그인 부분

module.exports = router;
// db.js
const mysql = require('mysql');

const conn = mysql.createConnection({
    host: 'localhost',
    port: '포트번호',
    user: 'user',
    password: '비밀번호',
    database: 'my_db'
});

conn.connect((err) => {
    if (err) console.log(err);
    else console.log('Connected to the database');
});

module.exports = conn;

4-4. 회원가입 및 데이터베이스에 저장

클라이언트

// Signup.jsx

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

const Signup = () => {
    const [id, setId] = useState('');
    const [pw, setPw] = useState('');
    const navigate = useNavigate();

    const submitBtn = async () => {
        if (id === '' || pw === '') {
            alert("아이디 또는 비밀번호를 입력해주시기 바랍니다");
            return;
        } else {
            try {
                const res = await fetch('/api/signup', {
                    method: 'POST',
                    body: JSON.stringify({userID: id, userPW: pw}),
                    credentials: 'include',
                    headers: { 'Content-Type': 'application/json' },
                });
                const data = await res.json();

                alert(data);
                if (res.status === 200) {
                    navigate('/');
                } else {
                    setId('');
                    setPw('');
                    return;
                }
            } catch(err) {
                console.log(err);
            }
        }
    }

    return (
        <div>
            <h1>회원가입</h1>
            <div>
                <input type="text" value={id} onChange={(e) => setId(e.target.value)}/>
                <input type="password" value={pw} onChange={(e) => setPw(e.target.value)}/>
                <button type='submit' onClick={submitBtn}>가입</button>
            </div>
        </div>
    );
};

export default Signup;

서버

// user.js

const bcrypt = require('bcrypt');
const userDB = require('../models/userDB');

const textToHash = async (text) => {		// 텍스트 값을 hash로 변환
    const saltRounds = 10;

    try {
        const hash = await bcrypt.hash(text, saltRounds);
        return hash
    } catch (err) {
        console.error(err);
        return err;
    }
}

exports.signup = async (req, res) => {
    const { userID, userPW } = req.body;

    try {
        const getUser = await userDB.getUser(userID);
        if (getUser.length) {
            res.status(401).json('이미 존재하는 아이디입니다.');
            return;
        }

        const hash = await textToHash(userPW);
        const signUp = await userDB.signUp([userID, hash]);
        res.status(200).json('가입 성공');
    } catch (err) {
        console.error(err);
        res.status(500).json(err);
    }
};
// userDB.js

const db = require('../database/db'); // 데이터베이스 연결 설정

exports.signUp = (data) => {
    return new Promise((resolve, reject) => {
        db.query(`INSERT INTO user (userID, userPW) VALUES (?, ?) `, [data[0], data[1]], (err, result) => {
            if (err) reject(err);
            else resolve(result);
        });
    });
};

4-5. 데이터베이스 조회 및 로그인

클라이언트

// Login.jsx

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

const Login = () => {
    const [id, setId] = useState('');
    const [pw, setPw] = useState('');
    const navigate = useNavigate();

    const loginSubmit = async () => {
        if (id === '' || pw === '') {
            alert('아이디 또는 비밀번호를 입력해주시기 바랍니다');
            return
        } else {
            try {
                const res = await fetch('/api/loginCheck', {
                    method: 'POST',
                    body: JSON.stringify({userID: id, userPW: pw}),
                    credentials: 'include',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                });
                const data = await res.json();
                
                alert(data);
                if (res.status === 200) {
                    navigate('/main');
                } else {
                    setId('');
                    setPw('');
                    return;
                }
            } catch(err) {
                console.log(err);
            }
        }
    }

    const moveSignUP = () => {
        navigate('/signup');
    }

    return (
        <>
            <h1>로그인</h1>
            <div>
                <input type="text" value={id} onChange={(e) => setId(e.target.value)} required/>
                <input type="password" value={pw} onChange={(e) => setPw(e.target.value)} required/>
                <button type='submit' onClick={loginSubmit}>로그인</button>
            </div>
            <button onClick={moveSignUP}>회원가입</button>
        </>
    );
};

export default Login;

서버

// user.js

const bcrypt = require('bcrypt');
const userDB = require('../models/userDB');

const hashCompare = async (inputValue, hash) => {
    try {
        const isMatch = await bcrypt.compare(inputValue, hash);
        if (isMatch) return true;
        else return false;
    } catch(err) {
        console.error(err);
        return err;
    }
}

exports.loginCheck = async (req, res) => {
    const { userID, userPW } = req.body;

    try {
        const getUser = await userDB.getUser(userID);
        if (!getUser.length) {
            res.status(401).json('존재하지 않는 아이디입니다.');
            return;
        }

        const blobToStr = Buffer.from(getUser[0].userPW).toString();
        const isMatch = await hashCompare(userPW, blobToStr);

        if (!isMatch) {
            res.status(401).json('비밀번호가 일치하지 않습니다.');
            return;
        }
        res.status(200).json('로그인 성공');
    } catch (err) {
        console.error(err);
        res.status(500).json(err);
    }
}
// userDB.js

const db = require('../database/db'); // 데이터베이스 연결 설정

exports.getUser = (userID) => {
    return new Promise((resolve, reject) => {
        db.query(`SELECT * FROM user where userID = ?`, userID, (err, result) => {
            if (err) reject(err);
            else resolve(result);
        });
    });
};

2개의 댓글

comment-user-thumbnail
2024년 3월 22일

Main.jsx 파일 코드가 없는거 아닌가요?

1개의 답글