본문 바로가기
Node.js

12/21 DB 모듈화, DB연동 후 회원 API/코드, 채널 API/코드 수정

by 케이리케리 2023. 12. 24.

DB 모듈화

mariadb.js

// get the client
const mysql = require("mysql2");

// create the connection to database
const connection = mysql.createConnection({
    host: "localhost",
    user: "root",
    password: "root",
    database: "Youtube",
    dateStrings: true,
});

module.exports = connection; //외부에서 사용할 수 있도록 모듈화

 

users.js

//db 모듈 가져오기
const conn = require("../mariadb");

// db에서 데이터 가져오기
conn.query("SELECT * FROM `users`", function (err, results, fields) {
    let { id, email, name, created_at } = results[0];
    console.log(id);
    console.log(email);
    console.log(name);
    console.log(created_at);
});

 

 

sql 쿼리 형식

기본 형식 👉 .query(sqlString, callback)

  • 첫번째 인자에 SQL문, 두번째 인자에 콜백함수 
connection.query(
  'SELECT * FROM `table` WHERE `name` = "Page" AND `age` > 45',
  function(err, results, fields) {
  	console.log(err); // 쿼리 중 오류가 발생하면 err가 된다.
    console.log(results); // results는 쿼리 결과가 포함된다.
    console.log(fields); // fields는 results(있는 경우)에 대한 정보가 포함된다.
  }
);

 

placeholder를 사용한 형식 👉 .query(sqlString, values, callback)

(* placeholder : SQL안에 사용된 ?(물음표))

  • SQL문에 ?로 설정한 위치에는 두번째 인자인 values 값이 들어간다.
  • 두번째 인자에 들어갈 값이 여러 개인 경우에는 대괄호([])로 감싸서 전달해줘야한다.
connection.query(
  'SELECT * FROM `table` WHERE `name` = ? AND `age` > ?',
  ['Page', 45],
  function(err, results) {
    console.log(results);
  }
);

 

 

 

DB연동 후 회원(users)  API, 코드 수정

회원 개별 조회  => SELECT

(실제 현업에서는 회원의 유니크한 id 값으로 회원 조회를 잘 하지않는다고 한다.)

GET /users/:id  ▶️ SELECT
- req : URL (id)  -> body (userId)  -> body(email)
- res : userId, name -> 회원 객체를 통으로 전달
router
    .route("/users")
    // 회원 개별 조회
    .get((req, res) => {
        let { email } = req.body;

        conn.query(`SELECT * FROM users WHERE email = ?`, email, function (err, results, fields) {
                res.status(200).json(results);
            
        });
    })

 

GET 메서드로 회원 개별 조회

 

 

성공적으로 results가 json array 형태로 출력된다.

 

 

회원가입 => INSERT
회원 가입 : POST /join ▶️ INSERT
- req : body (email, userId, pwd, name, contact)
- res : `회원가입되셨습니다.` 👉 로그인 페이지로 이동
// 회원가입
router.post("/join", (req, res) => {
    // console.log(req.body);
    if (req.body == {}) {
        res.status(400).json({
            message: "입력 값을 다시 확인해주세요.",
        });
    } else {
        const { email, name, password, contact } = req.body;

        conn.query(
            `INSERT INTO users (email, name, password, contact) VALUES (?, ?, ?, ?)`,
            [email, name, password, contact],
            function (err, results, fields) {
                res.status(201).json({
                	message : "회원가입되셨습니다."
                });
            }
        );
    }
});

 

 

회원 탈퇴 => DELETE
회원 "개별" “탈퇴” : DELETE /users/:id ▶️ DELETE
- req : URL (id)  -> body (email)
- res : `아쉽지만 다음에 만나요` or 메인 페이지로 이동
 router
    .route("/users")
	// 회원 개별 탈퇴
    .delete((req, res) => {
        const { email } = req.body;

        conn.query(`DELETE FROM users WHERE email = ?`, email, function (err, results, fields) {
            res.status(200).json(results);
        });
    });

성공적으로 삭제되었기때문에 회원 조회했을 때 빈 객체로 값이 반환되었다.

 

 

로그인 => SELECT
로그인 : POST /login ▶️ SELECT
- req : body (userId, pwd) -> body(email, password)
- res : `${name}님 로그인되셨습니다.` 👉 메인 페이지로 이동

1. DB 연동 전

 

2. DB 연동 후 3. 2번에서 최신 트렌드 반영

 

users.js 리팩토링

1. 필요없는 코드, 변수, 매개변수 삭제

2. 꼭 필요한, 유의미한 주석만 남기기(주석 정리)

3. 개행은 코드 가독성에 도움될 정도로만 1~2줄 한다.

4. 문자열 확인 : 한 줄에 모든 내용, 문자열을 담기보다는 의도를 명확하게 알 수 있도록 정리한다.

   ex) conn.query(`SELECT * FROM users WHERE email = ?`, email, function(err, results){~~}); 

         ⬇️

       const sql = SELECT * FROM users WHERE email = ?`;

       conn.query(sql, email, function(err, results){~~}); 

const express = require("express");
const router = express.Router();
const conn = require("../mariadb");

router.use(express.json());

// 로그인
router.post("/login", function (req, res) {
    const { email, password } = req.body;

    const sql = `SELECT * FROM users WHERE email = ?`;

    conn.query(sql, email, function (err, results) {
        let loginUser = results[0];
        if (loginUser && loginUser.password == password) {
            res.status(200).json({
                message: `${loginUser.name}님 로그인 되었습니다.`,
            });
        } else {
            res.status(404).json({
                message: "이메일 또는 비밀번호가 틀렸습니다.",
            });
        }
    });
});

// 회원 가입
router.post("/join", (req, res) => {
    if (req.body == {}) {
        res.status(400).json({
            message: "입력 값을 다시 확인해주세요.",
        });
    } else {
        const { email, name, password, contact } = req.body;

        const sql = `INSERT INTO users (email, name, password, contact) VALUES (?, ?, ?, ?)`;
        const values = [email, name, password, contact];

        conn.query(sql, values, function (err, results) {
            res.status(201).json({
                message: "회원가입되셨습니다.",
            });
        });
    }
});

router
    .route("/users")
    // 회원 개별 조회
    .get((req, res) => {
        const { email } = req.body;

        const sql = `SELECT * FROM users WHERE email = ?`;

        conn.query(sql, email, function (err, results, fields) {
            res.status(200).json(results);
        });
    })
    // 회원 탈퇴
    .delete((req, res) => {
        const { email } = req.body;

        const sql = `DELETE FROM users WHERE email = ?`;
        conn.query(sql, email, function (err, results, fields) {
            res.status(200).json(results);
        });
    });

module.exports = router;

 

 

DB 연동 후 채널(channels) API, 코드 수정

채널 개별 조회 => SELECT
채널 개별 “조회” : GET /channels/:id ▶️ SELECT
- req : URL (id)
- res 200 : 채널 개별 데이터  
router
    .route("/:id")
    .get((req, res) => {
            let { id } = req.params;
            id = parseInt(id);

            const sql = `SELECT * FROM channels WHERE id = ?`;
            conn.query(sql, id, function (err, results, fields) {
                if (results.length) {
                    res.status(200).json(results);
                } else {
                    notFoundChannel(res);
                }
            });
        });
        
function notFoundChannel(res) {
    res.status(404).json({
        message: "채널 정보를 찾을 수 없습니다.",
    });
}

 

 

로그인한 사용자의 채널 전체 조회 => SELECT
로그인한 회원의 채널 전체 “조회” : GET /channels ▶️ SELECT
- req : body (userId) -> body(user_id)
- req 200 : 채널 전체 데이터를 list/json array로 보내준다
router
    .route("/")
    // 채널 전체 조회
    .get((req, res) => {
        const { userId } = req.body;

        const sql = `SELECT * FROM channels WHERE user_id = ?`;
        if (userId) {
            conn.query(sql, userId, function (err, results) {
                if (results.length) {
                    res.status(200).json(results);
                } else {
                    notFoundChannel(res);
                }
            });
        } else {
            res.status(400).end();
        }
    })

 

 

논리연산자를 이용한 단축평가

(단축평가를 추천하지 않는다. if문의 중첩은 줄여줄 수 있지만 오히려 가독성이 안좋기때문이다.)

 

&& 연산자로 코드 단축

- && 연산자는 두 개의 피연산자가 모두 true로 평가될 때 true를 반환

A && B 연산자를 사용하게 될 때에는

A가 Truthy 한 값 -> B도 Truthy한 값이면 -> 결과값은 B

반면, A가 Falsy 한 값 -> 평가가 끝나서 결과는 A 

console.log(true && 'hello'); // hello
console.log(false && 'hello'); // false
console.log('hello' && false); //false
console.log('hello' && 'bye'); // bye
console.log(null && 'hello'); // null
console.log(undefined && 'hello'); // undefined
console.log('' && 'hello'); // '', ''은 false
console.log(0 && 'hello'); // 0
console.log(1 && 'hello'); // hello
console.log(1 && 1); // 1

 

 

|| 연산자로 코드 단축

- ||연산자는 두 개의 피연산자 중 하나만 true로 평가되어도 true를 반환

- A || B 는

만약 A 가 Truthy라면 -> 결과는 A.

반면, A 가 Falsy -> 결과는 B

console.log(true || 0); // true
console.log(0 || true); // true
console.log(true || 'hello'); //true
console.log(false || 0); // 0

 

 

 (*** Boolean으로 형변환 했을 때 false가 되는 것 => 값이 없거나  False, ""(빈 문자열), 0, NaN, undefined, null, document.all)

 

 

로그인한 사용자의 채널 전체 조회 코드에서

if문이 연달아 여러개 작성돼 깔끔해보이지 않아 && 연산자로 단축평가를 시도해봤다.

if (userId) {
    conn.query(sql, userId, function (err, results) {
        if (results.length) {
            res.status(200).json(results);
        } else {
            notFoundChannel(res);
        }
    });
} else {
    res.status(400).end();
}

 

if문을 &&연산자로 수정

router
    .route("/")
    // 채널 전체 조회
    .get((req, res) => {
        const { userId } = req.body;

        const sql = `SELECT * FROM channels WHERE user_id = ?`;
        userId && conn.query(sql, userId, 
        	function (err, results) {
                if (results.length) {
                    res.status(200).json(results);
                } else {
                    notFoundChannel(res);
                }
            }
        );
        res.status(400).end();
    })

 

오류 발생!

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (node:_http_outgoing:652:11)
    at ServerResponse.header (/Users/yunjiwon/Desktop/youtube-demo/node_modules/express/lib/response.js:794:10)
    at ServerResponse.json (/Users/yunjiwon/Desktop/youtube-demo/node_modules/express/lib/response.js:275:10)
    at Query.onResult (/Users/yunjiwon/Desktop/youtube-demo/routes/channels.js:19:37)
    at /Users/yunjiwon/Desktop/youtube-demo/node_modules/mysql2/lib/commands/query.js:86:16
    at process.processTicksAndRejections (node:internal/process/task_queues:77:11) {
  code: 'ERR_HTTP_HEADERS_SENT'
}

 

else문 안에 있던 res.status(400).end()가

단축 평가로 인해 else문이 없어졌기때문에 userId가 있든 말든 모든 상황에서 실행되어 문제가 발생했다.

userId가 있다면 ->  query문이 실행 ->  res.status(400).end()까지 실행된다.

userId가 없다 -> res.status(400).end() 실행

// 원래 코드
if (userId) {
	conn.query(~~);
} else {
	res.status(400).end()
}

// && 연산자로 단축평가
userId && conn.query(~~);
res.status(400).end();

 

 

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

서버가 클라이언트에게 2개 이상의 응답을 보내려고 할 때" 발생하는 오류이다.

(이미 응답을 한번 보냈는데 두 번째 응답을 보내려 시도하기 때문에 서버가 충돌해서 오류 메세지가 떴다.)

 

userId 있으면 -> 응답 보낼 때 함수 실행을 종료할 수 있도록 응답을 보내는 코드 앞에 return을 작성해봤지만 오류가 해결되지 않음.

👉 res.status(400).end(); 가 밖에 있기때문에 영향을 받지 않기때문이다.

userId && conn.query(sql, userId, 
    function (err, results) {
        if (results.length) {
            return res.status(200).json(results);
        } else {
            return notFoundChannel(res);
        }
    }
);
res.status(400).end();

 

 

 

단축평가를 포기하고 원래 코드로 원상복구했다. (나중에 배운 express-validator로 해결할 수 있었다.)

const sql = `SELECT * FROM channels WHERE user_id = ?`;
if (userId) {
    conn.query(sql, userId, function (err, results) {
        if (results.length) {
            res.status(200).json(results);
        } else {
            notFoundChannel(res);
        }
    });
} else {
    res.status(400).end();
}

 

 

 

채널 생성 => INSERT
채널 “생성” : POST /channels ▶️ INSERT
- req : body(channelTitle, userId) -> body(name, user_id)
cf. userId는 body가 아니라 header에 숨겨서 받아와야함..Token
- res 201 : `${channelTitle}님의 유튜브 시작을 응원합니다.` 👉 다른 페이지 띄워주고 싶음 ex. 채널 관리 페이지
// 채널 개별 생성
router
    .route("/")
    .post((req, res) => {
        if (req.body.name) {
            const { name, userId } = req.body;

            const sql = `INSERT INTO channels (name, user_id) VALUES (?, ?)`;
            const values = [name, userId];
            conn.query(sql, values, function (err, results) {
                res.status(201).json(results);
            });
        } else {
            res.status(400).json({
                message: `채널명을 입력해주세요.`,
            });
        }
    });

 

 

❓name(채널 이름)은 "한라봉", userId(사용자의 유니크한 id)은 "abc"(문자열)로 입력하고 POST 요청을 보내고 DB에 insert하면 어떻게 될까❓

➔ 데이터베이스에는 데이터가 저장❌

➔ 심지어 postman에서 insert 결과로 상태코드는 201(생성)으로 나옴

 

👉 데이터베이스에서 user_id 컬럼의 타입은 INT이다. userId의 값을 문자열로 입력하고 insert했기때문에 생긴 문제이다.

 

 

name값과 userId의 값이 없으면 생성되도록 코드를 수정했다.

.post((req, res) => {
        const { name, userId } = req.body;

        if (name && userId) {
            const sql = `INSERT INTO channels (name, user_id) VALUES (?, ?)`;
            const values = [name, userId];
            conn.query(sql, values, function (err, results) {
                res.status(201).json(results);
            });
        } else {
            res.status(400).json({
                message: `요청 값을 올바르게 입력해주세요.`,
            });
        }
    });

 

그러나 여전히 문제는 해결되지 않았다.

name값과 userId 값의 존재 유무의 문제가 아니라 타입의 문제이기때문이다.

 

발생한 문제들 해결은 다음에 express-validator로 유효성검사를 통해 알아보도록 하겠다.

 

 

참고

단축평가 참고 블로그

 

단축평가 참고 자료