DB GUI 로 만져보기, 기존 코드 DB 쓰는 버전으로 리팩토링

Younha Lee·2026년 2월 2일

TIL

목록 보기
24/61

GUI 익숙해지기


이번에는 DDL이 아니라 직접 GUI에서 테이블을 만들어봤어요.


GUI 상에서 직접 row를 수정할 수도 있고, 생성할 수도 있어요.

이렇게 sub_num과 video_count는 null 시 default로 0을 지정할 수 있어요.

FK 참조 무결성 제약조건?

참조 무결성이란 부모 테이블에 존재하지 않는 값을 자식 테이블이 가질 수 없도록 강제하는 것을 말해요.
예를 들어, users 테이블에 없는 유저 ID로는 channels를 생성할 수 없게 막는 것이 있어요.

이렇게 user_id가 users 테이블에 없는 id (여기서는 10)을 했더니 다음과 같은 참조 무결성 제약조건이 터졌어요.

Auto Increament 좀 더 알아보기


기존 1,2,3 id 중에 2을 지우고, 새로 row를 만들면 2이 아니라 4로 만들어져요!
가장 마지막 값을 기준으로 1을 더하는 것을 알 수 있어요.

백엔드와 DB 연결하기

ORM 없이 mysql2 라이브러리로 연결해보기로 했어요.
이렇게 Array<Object> 형식으로 응답이 오는 걸 알 수 있었어요.

즉슨, result에서 키 값으로 조회할 수 있다는 말이에요

그리고 created_at 칼럼도 추가했어요.


created_at 도 이렇게 나온 것을 볼 수 있었어요.

timezone 설정하기

db 조회시 서울시간으로 나오지 않는 것이 어색해서 조정하려고 해요.

먼저 db 연결시 타임존을 아시아/서울로 설정하면 우리가 예상한대로 되지 않아요.

// 기존 시간 세팅 확인하기
SELECT @@global.time_zone, @@session.time_zone;
// 기존 세팅은 둘의 값이 모두 SYSTEM임.

SET GLOBAL time_zone = 'Asia/Seoul';
SET time_zone = 'Asia/Seoul';

설정을 변경하면 조회한 테이블에서도 자연스럽게 값이 바뀐 것을 확인할 수 있어요

하지만 노드에서 보이는 시간은 그대로 일 텐데 이를 바꾸기 위한 코드가 createConnection() 내부의 'dateStrings : true'timezone: '+09:00 이에요.

기존에는 시간이 '2024-05-09T08:47:47.000Z' 으로 나오는 불편함이 있었지만,
설정을 해주게 되면 '2024-05-09 17:47:47' 로 원하는 문자열로 잘 나오는 것을 확인할 수 있어요.

const connection = db.createConnection({
    host: 'localhost',
    port: 3307,
    user: 'user',
    password: 'root',
    database: 'mydb',
    dateStrings: true,
    timezone: '+09:00'
})

DB 모듈화

모듈화는 지난 시간에 연동했던 connection을 export default 를 활용해 내보내고, import 를 통해서 불러서 사용해 보겠습니다.

// db.js
export default connection;

// librarians.js, books.js
import mariadb from '../db''

실제 DB에서 데이터 가져오기

이제는 전역 변수로 db를 쓰는 게 아니라 실제로 db를 조회하는 방식으로 리팩토링해야해요.

router.get('/users/:email', (req,res) => {
    const email = req.params.email;
    conn.query(
        `SELECT * FROM users where email= ${email}`,
        (err, results, fields) => {
            if(results.length === 0) return res.status(404).json(
                { "message": "No User Found" }
            )
            res.json(results);
        }
    )
});

이 코드에서 socket hang up 에러가 발생했어요.
이 에러는 error 발생시 아무런 로직이 없으면 생기는 에러라고 해요.
또한 ${email} 처럼 받으려면 요청을 받을 때 '' 를 붙여서 파라미터에 줘야해요.

해결결과 잘 요청이 온 것을 볼 수 있어요.

 const email = req.params.email;
    conn.query(
        `SELECT * FROM users where email= ?`, email,
        (err, results, fields) => {
            if (err) return res.status(500).json(err);
            res.json(results);
        }

또한 정석적으로는 템플릿 문자열 대신 ? 자리에 매개변수를 넣는 방식으로 구현해요.


이렇게 바꾸니 요청을 보낼 때 '' 도 필요없게 됐어요.

회원가입 수정

// 회원가입
router.post('/register', (req,res) => {
    const { email, password, nickname, contact } = req.body;
    // validation
    if (!email || !password || !nickname || !contact) return res.status(400).send("모든 필드를 입력해주세요.");

    conn.query(
        `INSERT INTO users (email, password, name, contact) VALUES (?, ?, ?, ?)`, [email, password, nickname, contact],
        (err, results, fields) => {
            if (err) return res.status(500).json(err);
            res.json(results);
        }
    )
});

매개변수를 여러 개 주려면 []를 사용해서 한 번에 전달할 수 있어요.

삭제 리팩토링

router.delete('/users/:email', (req,res) => {
    const email = req.params.email;
    conn.query(
        `DELETE FROM users WHERE email = ?`, email,
        (err, results, fields) => {
            if (err) return res.status(500).json(err);
            res.json(results);
        }
    )
});

삭제도 query를 통해 db에서 삭제하도록 구현했어요.

응답값을 보면 affectedRows 가 0이에요.

{
    "fieldCount": 0,
    "affectedRows": 1,
    "insertId": 0,
    "info": "",
    "serverStatus": 2,
    "warningStatus": 0,
    "changedRows": 0
}

만약 있는 이메일로 삭제를 했다면 영향받은 row가 1로 나와요.

로그인 비동기 이슈

router.post('/login', (req,res) => {
    const { email, password } = req.body;
    let user = {};
    conn.query(
        `SELECT * FROM users where email= ? and password = ?`, [email, password],
        (err, results, fields) => {
            if (err) return res.status(401).json({
                "message": "로그인에 실패했습니다."
            });
            user = results[0];
        }
    )
    res.json(user);
});

이렇게 구현했을 때 응답이 잘 나올 줄 알았는데, 로그인을 성공해도 빈 객체가 나왔어요.
이유는 conn.query 가 비동기이기 때문이었어요.
user 객체에 담겨지기 전에 응답으로 내보내니까 빈 객체로 보내지더라고요.
해결하기 위해 res.json(user) 를 콜백함수 안으로 넣어 해결했어요.
저는 로그인에서는 유저에게 고객 정보를 최소한으로 줘야한다고 생각했어요.
그래서 아이디와 비밀번호 중 어느 것이 틀려도 무조건 로그인에 실패했다고 전달하는 게 맞다고 생각했어요. (아이디만 맞다는 게 힌트가 된다고 생각)
혹은 다음과 같이 async/await으로 바꾸는 방법도 있어요.

router.post('/login', async (req, res) => {
    try {
        const { email, password } = req.body;
        const [results] = await conn.query(
            `SELECT * FROM users WHERE email = ? AND password = ?`,
            [email, password]
        );

        if (results.length > 0) {
            // 응답으로 보낼 유저 객체
            const user = results[0];
            res.json(user);
        } else {
            res.status(400).json({ "message": "아이디 또는 비밀번호가 틀렸습니다." });
        }

    } catch (err) {
        console.error(err);
        res.status(500).json({ "message": "서버 오류가 발생했습니다." });
    }
});

기존 코드 리팩토링

db를 전역 변수에서 실제 db로 바꾸면서 기존 변수를 사용하던 코드를 지우기로 했어요.
우리 코드에서 가장 위험한 건 sql을 string으로 작성하고 있는 부분이에요.

router.get('/users/:email', (req,res) => {
    const email = req.params.email;
    let sql =`SELECT * FROM users where email= ?`
        conn.query(sql,
        email,
        (err, results, fields) => {
            if (err) return res.status(500).json(err);
            res.json(results);
        }
    )
});

우선 다음과 같이 sql을 따로 변수화해서 가독성 좋게 만들었어요.
또한 같은 맥락으로 /channels 또한 sql 사용하는 법으로 리팩토링했어요.

router
    .route('/:id')
    .get((req, res) => {
        const id = parseInt(req.params.id);
        let sql =`SELECT * FROM channels where id= ?`
        conn.query(sql,
            id,
            (err, results, fields) => {
                if (err) return res.status(500).json(err);
                res.json(results);
            }
        )
    })

&& 연산자로 유효성 검증

router
    .route('/')
    .get((req, res) => {
        let { userId } = req.body;
        let sql = 'SELECT * FROM channels WHERE user_id = ?'
        
        // && 연산자로 userId 유효성 검사
        userId && conn.query(sql, userId,
            (err, results) => {
            if(results.length) res.json(results);
            else res.status(404).json({"message": "No Channel Found"});
            })
    })

여기서 강의는 && 연산자를 사용해 userId가 있어야 sql이 실행되도록 만들었어요.
개인적으로는 프론트엔드에서는 이런 방식이 좋다고 생각하는데, 백엔드에서는 예외처리가 꼼꼼해야한다고 생각해서 선호하지 않는 방식이에요.
라고 했는데, 바로 다음 장에서 백엔드에서 short ciruit을 하지 않는 걸 권장한다고 했고, if문으로 다시 돌려놨어요.

router
    .route('/')
    .get((req, res) => {
        let { userId } = req.body;
        let sql = 'SELECT * FROM channels WHERE user_id = ?'

        // && 연산자로 userId 유효성 검사
        userId && conn.query(sql, userId,
            (err, results) => {
            if(results.length) res.json(results);
            else res.status(404).json({"message": "No Channel Found"});
            })
    })


/channels 를 사용하는 코드들도 모두 리팩토링하고 정상 작동되는 것을 확인했어요.

profile
할 땐 하고 놀 땐 노는 일일놀놀입니다.

0개의 댓글