프로그래머스 풀스택 23

  • programmers

posted on 23 Sep 2024 under category programmers in series programmers

프로그래머스 풀스택 23

백엔드 심화: 인증과 비동기처리(4)

🌊 db 모듈화

db-demo.js -> mariadb.js로 이름 변경
기존 query 출력문을 없애고 connection을 모듈화 시켜줌

module.exports = connection


users.js에 가서 mariadb.js의 connection을 불러오고 query문을 출력하기.
connection.query에서 conn.query로 시작해야함!

// express 모듈 셋팅
const express = require('express')
const router = express.Router();
const conn = require('../mariadb') //mariadb.js의 connection을 불러오기
// A simple SELECT query
conn.query(
    'SELECT * FROM `users`',
    function (err, results, fields) {
        var {id, email, name, created_at} = results[0];
        console.log(id);
        console.log(email);
        console.log(name);
        console.log(created_at);
    }
);


<app.js 실행>
alt text

  • 잘 출력되는 걸 확인!



🌊 get -users db 연동

users.js파일의 router.route(‘/users’) 부분!
userId -> 개인정보이므로 url에 안넘기고 body값에 숨김
email을 받기로 수정하기

alt text

  • WHERE 절 삽입을 위해 Workbench에서 실행함
router.route('/users')
    .get((req, res) => {
        let {email} = req.body //비구조화

        conn.query(
            `SELECT * FROM users Where email = ${email}`,
            function (err, results, fields) {
                if(results.length)
                    res.status(200).json(results)
                else {
                    res.status(404).json({
                        message : "죄송합니다. 찾으시는 회원 정보가 없습니다."
            
                    })
                }
            }
        );
        // const user = db.get(userId) //db에서 객체 꺼내기(map이라서 이제 필요없음)
    })

alt text

  • 하지만 에러가 나는 걸 알 수 있음!


<혼자 수정한 것>

router.route('/users')
    .get((req, res) => {
        let { email } = req.body; // 비구조화

        // SQL 쿼리에서 변수를 직접 넣지 말고 ?로 대체하여 prepared statement 사용
        conn.query(
            'SELECT * FROM users WHERE email = ?',
            [email],
            function (err, results, fields) {
                if (err) {
                    // 에러가 발생하면 에러 응답을 보냄
                    res.status(500).json({ message: '서버 에러가 발생했습니다.', error: err });
                } else if (results.length) {
                    // 조회된 데이터가 있으면 그 결과를 반환
                    res.status(200).json(results);
                } else {
                    // 조회된 데이터가 없을 경우
                    res.status(404).json({ message: '죄송합니다. 찾으시는 회원 정보가 없습니다.' });
                }
            }
        )
    })


<실행결과>
alt text

  • 잘 실행됨!

🌊 Prepared Statement

💫 Prepared Statement

  • SQL 문법의 일종으로, 쿼리 성능 최적화와 보안 강화를 위해 사용
  • 특히, SQL Injection(주입 공격)으로부터 애플리케이션을 보호하는 중요한 기술

Prepared Statement는 보통 두 단계로 동작 :

  1. 쿼리 준비 단계 (Preparation): SQL 쿼리의 구조가 고정된 상태에서 데이터베이스 서버에 전달, 이때 쿼리에서 가변적인 값들은 플레이스홀더(?)로 표현

  2. 쿼리 실행 단계 (Execution): 실제 값들은 실행 시점에 제공되며, 이 값들이 플레이스홀더에 대입되어 실행

💫 Prepared Statement의 주요 특징

  1. SQL Injection 방지
    SQL Injection은 사용자 입력을 통해 SQL 문을 조작하여 데이터베이스에 악의적인 쿼리를 실행하는 공격 방법임 -> Prepared Statement는 쿼리와 데이터를 분리하여 이러한 공격을 차단

  2. 성능 최적화
    데이터베이스는 Prepared Statement를 처음 전달받았을 때, 쿼리의 구조를 분석하고 미리 최적화된 실행 계획을 준비 -> 이후 같은 구조의 쿼리가 반복해서 실행될 경우, 쿼리 분석 및 최적화 단계를 생략하여 빠르게 처리

  3. 재사용 가능성
    Prepared Statement는 동일한 SQL 문을 반복적으로 실행할 때 재사용 가능


[email]은 Prepared Statement에서 사용하는 Placeholders(플레이스홀더)에 값을 바인딩하는 방식.
이를 통해 SQL Injection과 같은 보안 문제를 방지하고, SQL 쿼리에서 동적으로 변수를 안전하게 사용 가능.

동작 방식

  1. ?(플레이스홀더): SQL 쿼리에서 ?는 값을 대체할 위치를 나타냄. 즉, 쿼리문 안에서 직접 변수를 넣지 않고, ?를 사용하여 변수를 넣을 자리를 예약하는 역할.

  2. [email]: email은 req.query나 req.body에서 가져온 값. 이 값은 ? 위치에 대입될 값이 됨. 배열 형태로 넘기면 배열의 순서대로 ?에 대응.

<예시>

conn.query(
    'SELECT * FROM users WHERE email = ?',
    [email], // ? 자리에 email 변수가 들어감
    function (err, results, fields) {
        // 결과 처리
    }
);
  • 이 쿼리에서 SELECT * FROM users WHERE email = ?는 실행 시, ? 자리에 email 값이 들어가면서 최종적으로 실행되는 SQL 쿼리는 SELECT * FROM users WHERE email = ‘kim@mail.com’ 같은 형식.

🌊 오류 해결, select sql 쿼리 형식

💫 오류 해결

Error: read ECONNRESET : 네트워크 연결 오류로, 클라이언트와 서버 간의 TCP 연결이 갑작스럽게 끊겼을 때 발생하는 오류
이 오류는 주로 서버에서 클라이언트의 요청을 처리하던 중 연결이 강제로 리셋되었을 때 발생
ECONNRESET은 Connection Reset by Peer(상대방에 의해 연결이 재설정됨)이라는 의미

아까 사용한 코드는 템플릿 문자열로 인해 SQL 구문 오류로 서버에서 쿼리가 실패했을 가능성이 큼!

💫 select sql 쿼리 형식

🌊select sql 쿼리 형식 오류 해결

나는 저렇게 해결했는데 host: ‘localhost’ -> host: ‘127.0.0.1’로 바꿔주는 것도 해결된다고 함!

🌊 회원가입 INSERT

💫 회원 API 설계 수정

get과 post의 가장 큰 차이 : body의 유무!!(body 있으면 post!!)

  1. SELECT 로그인 : POST /login
    • req : body(email, password)
    • res : `${name}님 환영합니다!` //👉 메인 페이지 출력
  2. INSERT 회원 가입 : POST /join
    • req : body(email, name, password, contact)
    • res : `${name}님 회원 가입을 축하드립니다!` //👉 로그인 페이지
  3. SELECT 회원 개별 “조회” : GET /users
    • req : body(email)
    • res : 회원 객체를 통으로 전달
  4. DELETE 회원 개별 탈퇴 : DELETE /users
    • req : body(email)
    • res : `${name}님 아쉽지만, 다음에 또 뵙겠습니다!` or 메인 페이지 출력


<혼자 수정한 것>
alt text

<실행결과>
alt text

🌊 delete, 로그인

💫 delete

affetedRows(영향받은 행) : 0 or 1, 2 …

  • 날라오는 숫자에 따라 구별가능
  • 프론트엔드와의 협업에서 어떻게 할지 논의 가능
    (404를 날릴건지 or affetedRows 숫자로 구별할건지)


<혼자 수정한 것>
alt text

<실행결과>
alt text

💫 로그인

<혼자 수정한 것>
alt text

<실행결과>
alt text

<코드 고도화>
alt text

🌊 users.js 코드 정리(리팩토링)

function (err, results, fields) {} : 매개 변수 총 3개. err, result, field

<로그인 부분 고도화>
alt text

  • 안쓰는 코드는 지우고, 통일감을 위해 ;도 없애기
  • 주석도 최대한 지우기!
  • 3줄 이상 공백은 들어올 코드가 있다고 생각하기에 띄울 때 2줄 이하로만 띄우기
let sql = 'SELECT * FROM users WHERE email = ?'

이런 구조로 conn.query()를 간편하게 함.



🌊 channels.js

💫 채널 API 설계 수정

채널

  1. INSERT 채널 “생성” : POST /channels
    get과 post의 가장 큰 차이 : body의 유무!!(body 있으면 post!!)
    • req : body(name, user_id) cf. userId는 body X header 숨겨서 Token
    • res 201 : `${name}님 채널 생성을 축하드립니다!` 👉 다른 페이지 띄워주고 싶음.. ex 채널 관리 페이지
  2. UPDATE 채널 “수정” : PUT /channels/:id
    • req : URL(id), body(name)
    • res 200 : `채널명이 성공적으로 수정되었습니다! 기존 : ${channelTitle} -> 수정 : ${channelTitle}`
  3. DELETE 채널 개별 “삭제” : DELETE /channels/:id
    • req : URL(id)
    • res 200 : `${name}이 정상적으로 삭제되었습니다. 아쉽지만 다음에 뵙겠습니다!` 👉 메인 페이지
  4. SELECT 회원의 채널 전체 “조회” : GET /channels
    • req : body(user_id)
    • res 200 : 채널 전체 데이터 list, json array
  5. SELECT 채널 개별 “조회” : GET /channels/:id
    • req : URL(id)
    • res 200 : 채널 개별 데이터


<SELECT 채널 개별 조회 혼자 수정한 것>
alt text

<실행결과>
alt text

  • 메소드 사이사이에 매개변수 전달하는 걸 잊지말기!

<SELECT 회원 채널 전체 조회 혼자 수정한 것>
alt text

  • res.status(400).end()를 사용하면 그냥 끝남

<실행결과>
alt text



🌊 INSERT 할 때 userId 값이 이상해서 실패해도 201이라니?!

단축평가(Short-circuit Evaluation) : 논리 연산자 &&(AND)와 ||(OR)를 사용할 때, 불필요한 연산을 건너뛰는 동작을 의미
논리식의 결과가 이미 확정된 경우 나머지 연산을 수행하지 않고 평가를 “단축”하는 것

<INSERT 채널 개별 등록 혼자 수정한 것>
alt text

<실행결과>
alt text
alt text



🌊 느낀 점(YWT)

Y 일을 통해 명확히 알게 되었거나 이해한 부분(한 일)에 대해 정리 :
DB 모듈화, Prepared Statement, 단축평가

W 배운 점과 시사점 :
DB 모듈화 : 쿼리문을 통해 간편하게 할 수 있음!
Prepared Statement : 쿼리 성능 최적화와 보안 강화를 위해 사용!!
단축평가(Short-circuit Evaluation) : 논리 연산자 &&(AND)와 ||(OR)를 사용할 때, 불필요한 연산을 건너뛰는 동작을 의미.

T 응용하여 배운 것을 어디에 어떻게 적용할지:
쿼리문을 통해 DB를 모듈화가 가능함을 배움! 백엔드에서 DB와 연동하는 걸 더 잘 알게 되었음