본문 바로가기
TIL with Programmers

[TIL] 9/23 node.js, db 연결, db 모듈화, SQL 쿼리문, affectedRows, 단축평가(short-circuit evaluation)

by 보먀 2024. 9. 23.
728x90
반응형

1. 미니미니 프로젝트  (실습)

이전 내용

 

[TIL] 9/6 핸들러, 예외 처리, HTTP 상태코드

1. 핸들러 (Handler)HTTP request 가 날아오면 자동으로 호출되는 메소드 노드에서는 콜백함수로, 콜백함수를 핸들러라고 생각하면 된다. (cf. 스프링에서는 컨트롤러라고 불린다)즉, HTTPMETHOD 를 사용

everydayc0ding.tistory.com

 

 

[TIL] 9/9 API 설계 - 회원 API, 채널 API

1. 미니미니 프로젝트 (실습) 지난번에 했던 미니미니 프로젝트를 이어서 해보려고 한다. 지난번에는 회원 API 의 회원가입 / 회원 개별 조회 / 회원 개별 탈퇴까지 했으니, 로그인 API 를 만들어보

everydayc0ding.tistory.com

 

 

[TIL] 9/23 node.js, db 연결, db 모듈화, SQL 쿼리문, affectedRows, 단축평가(short-circuit evaluation)

1. 미니미니 프로젝트  (실습) 1.1. db 모듈화 db 연결이 필요한 곳마다 연결 코드를 다 쓰는 것은 효율적이지 않기 때문에 module.exports 를 통해 모듈화 시킨다.// mariadb.jsconst mysql = require('mysql2');cons

everydayc0ding.tistory.com

 

 

1.1. db 모듈화

 

db 연결이 필요한 곳마다 연결 코드를 다 쓰는 것은 효율적이지 않기 때문에 module.exports 를 통해 모듈화 시킨다.

// mariadb.js

const mysql = require('mysql2');

const connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'root',
    database: 'Youtube',
    dateStrings: true
});

module.exports = connection

 

이제 필요한 곳에 require 해서 사용하면 된다.

추가로 모듈화된 db 의 변수명은 connection 에서 따온 conn 이라는 이름을 많이 사용한다. 

// user.js

const conn = require('../mariadb')

 

 

1.2. sql 쿼리

 

connection.query() 는 MYSQL 데이터베이스와 연결하여 쿼리를 실행할 때 사용하는 Node.js 의 MYSQL 모듈에서 제공하는 메소드이다. 이 메소드를 사용하면 SQL 쿼리를 실행하고, 그 결과를 콜백함수로 처리할 수 있다. 

// connection.query 의 기본 형태

connection.query(
    'sql쿼리문',
    [values],
    function(err, results, fields) { // 쿼리가 실행된 후 호출되는 콜백함수
        // 에러처리
        // 성공시 결과처리
    }
)

 

 

만약 특정한 값을 뽑아와야 하는 경우 WHERE  ? 를 사용하여 뽑아오면 된다. ? 자리에 들어갈 값을 [values] 자리, 즉 두번째 매개변수로 넣어주면 된다. 

// 예시 코드

connection.query(
    `SELECT * FROM users WHERE id = ?`,
    [id],
    function(err, results, fields) {
        // 에러처리
        // 성공시 결과처리
    }
)

connection.query(
    `INSERT INTO users (id, name, age) VALUES (?, ?, ?)`,
    [id, name, age],
    function(err, results, fields) {
        // 에러처리
        // 성공시 결과처리
    }
)

 

 

1.3. 회원 API

 

- SELECT

회원 개별 조회: GET /users

  • req: body(email)
  • res200: 회원 객체를 통으로 전달
// 회원 개별 조회
router
    .route('/users')
    .get((req, res) => {
        let {email} = req.body

        let sql = `SELECT * FROM users WHERE email = ?`
        let values = [email]

        conn.query(
            sql,
            values,
            function(err, results) {
                if (results.length) {
                    res.status(200).json(...results)
                    console.log(results)
                } else {
                    res.status(404).json({ message: '회원 정보가 없습니다.'})
                }
            }
        )
    })

 

 

- INSERT

회원가입: POST /join

  • req: body(email, name, password, contact)
  • res201: `${name}님 환영합니다`
// 회원 가입
router
    .route('/join')
    .post((req, res) => {
        const {email, name, password, contact } = req.body

        let sql = `INSERT INTO users (email, name, password, contact) VALUES (?, ?, ?, ?)`
        let values = [email, name, password, contact]
        
        if (email && name && password) {
            conn.query(
                sql,
                values,
                function(err, results) {
                    res.status(201).json({ message: `${name}님 환영합니다. `})
                }
            )
        } else {
            res.status(400).json({ message: '빠진 입력값이 있습니다.' })
        }
    })

 

 

- SELECT

로그인: POST /login

  • req: body(email, password)
  • res200: `${name}님 환영합니다.`
// 로그인
router
    .route('/login')
    .post((req, res) => {
        const {email, password} = req.body

        let sql = `SELECT * FROM users WHERE email = ?`
        let values = [email]

        conn.query(
            sql,
            values,
            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: '이메일 또는 비밀번호가 틀렸습니다.' })
                }
            }
        )
    })

 

 

- DELETE

회원 개별 탈퇴: DELETE /users

  • req: body(email)
  • res200: `${name}님 탈퇴가 완료되었습니다`
// 회원 개별 탈퇴
router
    .route('/users')
    .delete((req, res) => {
        let {email} = req.body

        let sql = `DELETE FROM users WHERE email = ?`
        let values = [email]

        conn.query(
            sql,
            values,
            function(err, results) {
                if (results.affectedRows) {
                    res.status(200).json({ message: '탈퇴가 완료되었습니다.' })
                } else {
                    res.status(404).json({ message: '회원 정보가 없습니다.'})
                    console.log(results)
                }  
            }
        )
    })

 

 

affectedRows  SQL 쿼리가 데이터베이스에서 영향을 미친 행의 수를 나타내는 값으로, INSERT, UPDATE, DELETE 와 같이 데이터 변경 작업을 할 때 반환된다. results 를 출력해보면 확인할 수 있다. 

 

삭제 성공 시 -> effectedRows = 1

 

삭제 실패 시 -> effectedRows = 0

 

 

1.4. 채널 API

 

- INSERT

채널 생성: POST /channels

  • req: body(name, user_id)
  • res201: `${name}님 채널을 응원합니다.`
// 채널 개별 생성
router
    .route('/')
    .post((req, res) => {
        let {name, user_id} = req.body

        if (name && Number(user_id)) {
            
            let sql = `INSERT INTO channels (name, user_id) VALUES (?, ?)`
            let values = [name, user_id]

            conn.query(
                sql,
                values,
                function(err, results) {
                    res.status(201).json({ message: `${name}님 채널을 응원합니다.` })
                }
            )
        } else {
            res.status(400).json({ message: '요청 값을 제대로 보내주세요. '})
        }
    })

 

 

- SELECT

회원의 채널 전체 조회: GET /channels

  • req: body(user_id)
  • res200: 채널 데이터 list
// 회원 채널 전체 조회
router
    .route('/')
    .get((req, res) => {
        let {user_id} = req.body

        let sql = `SELECT * FROM channels WHERE user_id = ?`
        
        // 단축 평가
        user_id && conn.query(
            sql,
            user_id,
            function(err, results) {
                if (results.length) {
                    res.status(200).json(...results)
                } else {
                    notFoundChannel(res)
                }
            }
        )

        res.status(400).end()
    })

 

처음에는 user_id 가 없는 경우의 예외처리를 위해 if else 문을 사용하지 않고 단축 평가를 통해 코드를 간단하게 만들었다. 

 

user_id 가 존재 + SQL 쿼리가 성공적으로 실행되면 결과를 반환하고,

user_id 가 존재하지 않는다면 밑으로 내려가서 res.status(400).end() 를 만나 상태코드 400 을 뱉도록 했는데 이렇게 하면 user_id 가 존재하는 경우에도 res.status(400).end() 를 만나 상태코드 400 을 뱉고 아래의 에러를 만나게 된다. 

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

 

이 에러는 서버에서 이미 클라이언트의 요청에 응답을 보낸 후 같은 요청에 대해 또 보내려고 할 때 발생한다. 

즉, user_id 가 존재하는 경우에는 위에서 상태코드 200 을 뱉었는데, 밑으로 내려가서 또 400 을 뱉으려고 하니 덮어씌워져서 발생하는 에러인 것이다. 그래서 if else 를 사용하는 코드로 고쳐주었다. 

 

// 회원 채널 전체 조회
router
    .route('/')
    .get((req, res) => {
        let {user_id} = req.body

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

 

 

※ 추가 - 단축 평가 (short-circuit evaluation)

 

자바스크립트 단축 평가는 논리 연산에서 불필요한 연산을 생략하고, 가능한 빨리 결과를 도출하는 방식으로 &&(AND), ||(OR) 에서 주로 사용된다. 

// || (OR)
const a = null || "default";
console.log(a); // "default" 

// 좌측이 거짓이므로 우측 값 반환, 우측이 참이면 참이 되고 거짓이면 거짓이 됨, 우측 값에 따라 결정되니 우측 값 반환

const b = "Hello" || "World";
console.log(b); // "Hello" 

// 좌측이 참이므로 우측은 평가 X, 둘 중 하나만 참이면 참이기 때문에 우측을 평가할 필요 X
// && (AND)
const a = 1 && 0;
console.log(a); // 0 

// 첫 번째 거짓 값인 0이 반환됨, 둘 중 하나만 거짓이어도 거짓이기 때문에 우측 값을 볼 필요 X

const b = true && "Hello";
console.log(b); // "Hello" 

// 좌측이 참이므로 우측 값이 반환됨, 우측이 참이면 참 거짓이면 거짓이 됨, 우측 값에 따라 결정되기 때문에 우측 값 반환

 

 

- SELECT

채널 개별 조회: GET /channels/:id

  • req: URL(id)
  • res200: 채널 개별 데이터
// 채널 개별 조회
router
    .route('/:id')
    .get((req, res) => {
        let {id} = req.params
        id = parseInt(id)

        let sql = `SELECT * FROM channels WHERE id = ?`

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

 

728x90
반응형