백엔드 심화 4-4 ~ 4-11 (DB 모듈화, 미니프로젝트에 DB 연동)

Develop Kim·2024년 9월 23일

programmers

목록 보기
16/40
post-thumbnail

4 Express-데이터베이스 연동하기

4-4 db 모듈화

1️⃣ connection 모듈 만들기

  • 코드가 복잡하니 모듈화를 해보자, 여기서 모듈화를 할 수 있는 것으로 connection을 골라볼 수 있다.
const mysql = require('mysql2/promise');

// Create the connection to database
async function connectToDatabase() {
  const connection = await mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: '1234',
    database: 'Youtube', // 데이터베이스 이름을 'Youtube'로 설정
    dateStrings: true,
  });

  // A simple SELECT query
  try {
    const [results, fields] = await connection.query( // await를 사용하여 쿼리 결과가 올 때까지 기다립니다. 쿼리의 결과는 results와 fields라는 두 가지 값으로 반환됩니다.
      'SELECT * FROM `users`' // results: 데이터베이스에서 가져온 행들의 배열, fields: 쿼리 결과에 대한 메타데이터
    );

    let {id, email, name, created_at} = results[0]

    console.log(id);
    console.log(email);
    console.log(name);
    console.log(created_at);

  } catch (err) {
    console.log(err);
  }
}

connectToDatabase();
// 이 함수는 데이터베이스에 연결하고, 쿼리를 실행한 후 결과를 처리하는 비동기 함수입니다.
// connectToDatabase()를 호출하여 실제로 이 함수가 실행되도록 합니다.

2️⃣ 에러가 발생한다 왜 그럴까? 코드를 고쳐서 사용해보자

  • 강의에 맞춰 mariadb.js를 수정하고
const mysql = require('mysql2/promise');

// MySQL에 비동기적으로 연결
const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '1234',
  database: 'Youtube', // 데이터베이스 이름을 'Youtube'로 설정
  dateStrings: true, // 날짜를 문자열로 반환
});

// 연결을 Promise로 내보냄
module.exports = connection;
  • users.js에는 해당 코드를 붙여 db의 모듈화가 잘 되었는지 확인해본다.
const connPromise = require('../mariadb'); // mariadb 모듈에서 비동기적으로 반환되는 connection 객체를 Promise로 불러옴

async function runQuery() {
  // 비동기 함수로, 데이터베이스 작업이 완료될 때까지 기다릴 수 있음

  const conn = await connPromise; // 'connPromise'는 Promise를 반환하므로, await로 연결 객체(conn)가 반환될 때까지 기다림

  const [results, fields] = await conn.query( 
    'SELECT * FROM `users`'
  ); 
  // 데이터베이스에서 'users' 테이블의 모든 데이터를 가져오는 쿼리 실행
  // 쿼리의 결과는 'results'에 저장되고, 필드 메타데이터는 'fields'에 저장됨
  // 쿼리가 완료될 때까지 await로 기다림

  let { id, email, name, created_at } = results[0]; 
  // 'results' 배열에서 첫 번째 레코드를 구조 분해 할당하여 id, email, name, created_at 값을 변수에 저장
  
  console.log(id); // id 값 출력
  console.log(email); // email 값 출력
  console.log(name); // name 값 출력
  console.log(created_at); // created_at 값 출력
}

runQuery(); 
// runQuery 함수를 호출하여 실행, 데이터베이스에 연결하고 쿼리 결과를 콘솔에 출력함
  • 잘 출력되는 것을 확인할 수 있음

  • 위에서 비동기 함수를 계속 사용한 이유는

    • 데이터베이스 연결이 비동기적이기 때문
    • MySQL의 mysql2/promise 모듈이나 다른 데이터베이스 드라이버는 Promise 기반으로 동작하기 때문
      • Promise는 비동기 작업의 완료 또는 실패를 나타내며, 이 Promise를 처리하기 위해 비동기 함수(async) 또는 .then() 구문을 사용해야 함. 데이터베이스 모듈을 모듈화할 때, 비동기 함수를 사용해야 이러한 비동기적 데이터베이스 작업을 처리할 수 있음



4-5 get -users db 연동

1️⃣ 유튜브 프로젝트의 users를 db에 연동해보자

  • API설계를 확인하고 만든 데이터베이스 테이블에 맞게 수정해보자(id를 이메일로 변경)
  • 마이페이지도 마찬가지로 id를 이메일로 변경

2️⃣ 코드를 맞게 고쳐보자

  • 데이터베이스에서 값을 가져오는 Query를 회원 개별조회에 대입해본다.
const connPomise = require('../mariadb'); // Promise 기반의 mariadb 연결 객체 가져옴

async function runQuery() {
    const conn = await connPromise;
    const [results, fields] = await conn.query( 
      `SELECT * FROM users Where email = ${email}`,
      () => { res.json(results) }
    );
}

// 회원 개별 조회
  .get((req, res) => {
    let {email} = req.body // {}는 비구조화, id 값을 따로 빼서 사용
    runQuery();
  })
  • 특정 이메일 조회 SELECT * FROM Youtube.users WHERE email = 'kim@mail.com';를 하는 것으로 회원정보 개별 조회를 해보자

  • 값을 잘 가져오는지 확인해보기 위해 body값에 이메일을 넣어 잘 반환하는지 확인해 보니 에러가 발생한다....




4-6 오류 해결, select sql 쿼리 형식

1️⃣ 에러를 찾아보자

  • 코드에서 바꾼 것은 SELECT * FROM users Where email = ${email}로 이메일을 템플릿 문자열로 바꾼 것인데 이것이 원인일 수도 있을 거 같다.
  • 데이터베이스에서 값을 가져올 때는 아래와 같은 방식으로 해줘야 정석적인 방법이라고 한다.

2️⃣ 에러를 해결하기 전 코드 수정

  • 강의와는 다르게 conn을 함수로 사용하지 못하는 에러가 계속 발생하였는데 mysql2/promise모듈을 사용하였기 때문이다.
  • mysql2/promise모듈을 사용하면 conn.query는 Promise를 반환하므로, await 또는 .then()을 사용해야 했었다.
const mysql = require('mysql2'); // ('mysql2/promise')모듈에서 변경

// MySQL에 비동기적으로 연결
const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '1234',
  database: 'Youtube', // 데이터베이스 이름을 'Youtube'로 설정
  dateStrings: true, // 날짜를 문자열로 반환
});

// 연결을 Promise로 내보냄
module.exports = connection;
  • 아래와 같이 코드를 변경하게 되니 값을 잘 반환해 오는 것을 확인할 수 있었다.
const conn = require('../mariadb'); // Promise 기반의 mariadb 연결 객체 가져옴

    conn.query(
      'SELECT * FROM users WHERE email = ?', email, // email 값을 바인딩
      function(err, results, fields) {
        res.json(results); // 쿼리 결과를 클라이언트에 응답
      }
    );

3️⃣ 반환되는 값의 특이점

  • 아래와 같이 배열에 감싸져서 반환되는 것을 확인할 수 있음
[
    {
        "id": 3,
        "email": "chim@mail.com",
        "name": "chimchak",
        "password": "1234",
        "contect": "010-3333-3333",
        "created_at": "2024-09-24 18:40:12"
    }
]



4-7회원가입 INSERT

1️⃣ 회원 API 설계를 수정해보자

  • 이전 미니프로젝트에서 설계한 API를 데이터베이스 연동에 맞게 수정해보자

  • 1) 로그인 : POST/login (SQL문: SELECT)

    • req : body(userId, pwd)
    • res : ${name}님 환영합니다 [메인 페이지를 출력]
  • 2) 회원 가입 : POST/join (SQL문: INSERT)

    • req : body(userId, pwd, name) 👉 body(email, name, password, contect)
    • res :
  • 3) 회원 개별 정보 조회 : GET/users (SQL문: SELECT)

    • req : body(userId) 👉 body(email)
    • res : userId, name 👉 회원 객체
  • 4) 회원 개별 탈퇴 : DELETE/users (SQL문: DELETE)

    • req : body(userId)
    • res : ${name}님 다음에 또 뵙겠습니다. OR 메인페이지 출력

2️⃣ 회원가입도 연결해보자

  • 위 회원조회에서 해봤던대로 해보자
// 회원가입
router.post('/join', (req, res) => {
  console.log(req.body)

  if (req.body == {}) {
    res.status(400).json({
      message : `입력 값을 확인해주세여`
    })
  } else {
    const {email, name, password, contect} = req.body // 바디에 해당 값이 들어 있다면

    conn.query(
      `INSERT INTO users (email, name, password, contect)
      VALUES (?, ?, ?, ?)`, [email, name, password, contect], // SQL문으로 테이블에 등록을 해준다.
      function(err, results, fields) {
        res.json(results); // 쿼리 결과를 클라이언트에 응답
      }
    );

  }
})
  • 코드를 실행하고 body에 값을 입력하니 정상적으로 작동하는 것을 볼 수 있다. body에 값을 넣을 때는 컬럼의 순서는 상관이 없나보다.




4-8 delete, 로그인

1️⃣ DELETE와 로그인을 수정해보자

  • 1) 로그인 : POST/login (SQL문: SELECT)
    • req : body(userId, pwd) 👉 body(email, password)
    • res : ${name}님 환영합니다 [메인 페이지를 출력]
  • 2) 회원 가입 : POST/join (SQL문: INSERT)
    • req : body(userId, pwd, name) 👉 body(email, name, password, contect)
    • res : ${name}님 환영합니다 [로그인 페이지를 출력]
  • 3) 회원 개별 정보 조회 : GET/users (SQL문: SELECT)
    • req : body(userId) 👉 body(email)
    • res : userId, name 👉 회원 객체
  • 4) 회원 개별 탈퇴 : DELETE/users (SQL문: DELETE)
    • req : body(userId) 👉 body(email)
    • res : ${name}님 다음에 또 뵙겠습니다. OR 메인페이지 출력

2️⃣ 회원 삭제 코드를 작성해보자

  • 개별 삭제도 어렵지 않다.
  // 회원 개별 탈퇴
  .delete((req, res) => {
    let {email} = req.body // {}는 비구조화, id 값을 따로 빼서 사용
   
    conn.query(
      'DELETE FROM users WHERE email = ?', email, // email 값을 바인딩
      function(err, results, fields) {
        res.json(results); // 쿼리 결과를 클라이언트에 응답
      }
    );
  })
  • results 반환값이 특이함. 이 내용은 영향받은 행위, 정보 등을 표현해준 데이터임

  • 데이터베이스를 확인해보니 잘 삭제가 되었음.

3️⃣ 로그인 코드도 작성해보자

// 로그인
router.post('/login', (req, res) => { // 먼저 email이 디비에 저장된 회원인지 확인해야
  const {email, password} = req.body
  let loginUser = {} 

  conn.query(
    'SELECT * FROM users WHERE email = ?', email, // email 값을 바인딩
    function(err, results, fields) {
      
      if (results.length) { // results에 값이 있다면
        loginUser = results[0] // 로그인 유저 객체에 results의 0번째 객체를 담아준다. 이전보다 코드가 쉬워짐
        if (loginUser.password == password) { // 로그인 유저 객체의 비번과 같다면
          res.status(200).json({
            message : `${loginUser.name}님 로그인 되었습니다.`
          })
        } else {
          res.status(400).json({
            message : `비밀번호가 틀렸습니다.`
          })
        }
      } else {
        res.status(404).json({
          message : `회원정보가 없습니다.`
        })
      }
    }
  )
})
  • 로그인이 잘 되는 것을 확인할 수 있다.


  • 로그인 코드에 if else가 너무 많으니 깔끔하게 정리해보자

router.post('/login', (req, res) => { // 먼저 email이 디비에 저장된 회원인지 확인해야
  const {email, password} = req.body

  conn.query(
    'SELECT * FROM users WHERE email = ?', email, // email 값을 바인딩
    function(err, results, fields) {

      let loginUser = results[0]; // 로그인 유저 객체에 results의 0번째 객체를 담아준다. 이전보다 코드가 쉬워짐

      if (loginUser && loginUser.password == password) { // results에 값이 있다면을 로그인유저가 있고 패스워드가 같으면으로 변경해줌
        res.status(200).json({
          message : `${loginUser.name}님 로그인 되었습니다.`
        })
      } else if (loginUser && loginUser.password != password) { // 로그인 유저는 있지만, 패스워드가 아니면
        res.status(400).json({
          message : `비밀번호가 틀렸습니다.`
        })
      } else {
        res.status(404).json({
          message : `회원정보가 없습니다.`
        })
      }
    }
  )
})
  • 더 깔끔하게 바꾼다면 아래와 같이 만들어 벌 수 있다.
// 로그인
router.post('/login', (req, res) => { // 먼저 email이 디비에 저장된 회원인지 확인해야
  const {email, password} = req.body

  conn.query(
    'SELECT * FROM users WHERE email = ?', email, // email 값을 바인딩
    function(err, results, fields) {

      let loginUser = results[0]; // 로그인 유저 객체에 results의 0번째 객체를 담아준다. 이전보다 코드가 쉬워짐

      if (loginUser && loginUser.password == password) { // results에 값이 있다면을 로그인유저가 있고 패스워드가 같으면으로 변경해줌
        res.status(200).json({
          message : `${loginUser.name}님 로그인 되었습니다.`
        })
      } else {
        res.status(404).json({
          message : `이메일 또는 비밀번호가 틀렸습니다.`
        })
      }
    }
  )
})



4-9 users.js 코드 정리 -리팩토링

1️⃣ 위에서 만든 코드를 리펙토링 해보자

  • 이전에 작성한 주석도 모두 정리하고 코드를 보기 쉽게 정리해봤다.
const express = require('express')
const router = express.Router()
const conn = require('../mariadb') // MariaDB 연결 객체 가져옴

router.use(express.json()) // JSON 파싱 미들웨어 사용

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

  let sql = 'SELECT * FROM users WHERE email = ?' // SQL 쿼리를 변수로 저장

  conn.query(sql, email, (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) => {
  let sql = `INSERT INTO users (email, name, password, contect) VALUES (?, ?, ?, ?)`

  if (req.body == {}) { // 입력 값이 없을 경우 처리
    res.status(400).json({
      message: `입력 값을 확인해주세여`
    })
  } else {
    const { email, name, password, contect } = req.body

    conn.query(sql, [email, name, password, contect], (err, results) => {
      res.json(results) // 회원가입 성공 시 결과 응답
    })
  }
})

router
  .route('/users')

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

    let sql = 'SELECT * FROM users WHERE email = ?'

    conn.query(sql, email, (err, results) => {
      if (err) {
        return res.status(500).send('Database query failed') // 쿼리 에러 처리
      }
      res.json(results) // 회원 정보 조회 결과 응답
    })
  })

  // 회원 개별 탈퇴
  .delete((req, res) => {
    const { email } = req.body

    let sql = 'DELETE FROM users WHERE email = ?'

    conn.query(sql, email, (err, results) => {
      res.json(results) // 탈퇴 후 결과 응답
    })
  })

module.exports = router;



4-10 channels.js

1️⃣ 채널 API 설계를 수정해보자

  • 이전 미니프로젝트에서 설계한 API를 데이터베이스 연동에 맞게 수정해보자

  • 1) 채널 개별 "생성" : POST/channels (SQL문: INSERT)

    • req : body(name, user_id)
    • res :(201) ${channelTitle}님 채널을 응원합니다
  • 2) 채널 개별 "수정" : PUT/channels/:id (SQL문: INSERT)

    • req : URL(id), body(name)
    • res :(200) 채널명이 성공적으로 수정되었습니다. 기존: ${}-> 수정: ${}
  • 3) 채널 개별 "삭제": DELETE/channels/:id (SQL문: DELETE)

    • req : URL(id)
    • res :(200) 삭제되었습니다.
  • 4) 채널 전체 "조회": GET/channels (SQL문: SELECT)

    • req : body(user_id)
    • res :(200) 채널 전체 데이터 list, json array
  • 5) 채널 개별 "조회": GET/channels/:id (SQL문: SELECT)

    • req : URL(id)
    • res :(200) 채널 개별 데이터

2️⃣ 채널 개별 조회부터 코드를 만들어보자

router
  .route('/:id')

.get((req, res) => {
  let {id} = req.params // req.params 객체에서 URL 경로에 포함된 id 값을 추출하여 id 변수에 저장
  id = parseInt(id) // URL에서 문자열로 추출된 id를 정수로 변환

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

  conn.query(sql, id, // email 값을 바인딩
    function(err, results) {
      if (results.length) {
        res.json(results); // 쿼리 결과를 클라이언트에 응답
      } else {
        notFoundChannel()
      }
    }
  )
})
  • 잘 작동하는 것을 확인할 수 있다.

3️⃣ 채널 전체 조회도 해보자

.get((req, res) => { 
  let {userId} = req.body 
  let sql = 'SELECT * FROM channels WHERE user_id = ?'
  conn.query(sql, userId,
    function(err, results) {
      if (results.length) {
        res.status(200).json(results);
      } else {
        notFoundChannel(res)
      }
    }
  )
})
  • 데이터를 보내거나 틀리게 보내면 정상적으로 작동하는 것을 확인할 수 있다. 다만, 아무런 값도 안보내면 에러가 발생함


  • 자바스크립트의 단축평가를 이용해서 이 문제를 해결해보자, 빈 데이터를 보내도 배드리퀘가 뜨지만, 데이터를 보내도 배드리퀘가 뜬다.

.get((req, res) => { 
  let {userId} = req.body 
  let sql = 'SELECT * FROM channels WHERE user_id = ?'
  userId &&/*단축평가*/ conn.query(sql, userId, // 단축평가로 조건을 추가해주면 뒤 코드를 실행하지 않음(res를 보내지 못하고 무한로딩)
    function(err, results) {
      if (results.length) {
        res.status(200).json(results); // 쿼리 결과를 클라이언트에 응답
      } else {
        notFoundChannel(res)
      }
    }
  )
  res.status(400).end() // 값을 보내지 않고 무한로딩을 끝내줌
})

  • 그냥 if else문을 사용해서 고치자. 너무 코드를 줄이는데 집중하지 말자
// 채널 전체 조회
.get((req, res) => { 
  let {userId} = req.body 
  
  let 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()
  }
})



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

1️⃣ 채널 생성을 해보자

  .post((req, res) => {
    
    if (req.body.name){
      const {name, userId} = req.body
      let sql = `INSERT INTO channels (name, user_id) VALUES (?, ?)`
      let values = [name, userId]

      conn.query(sql, values,
        function(err, results) {
          res.json(results); // 쿼리 결과를 클라이언트에 응답
        }
      )
    } else {
      res.status(400).json({
        message : '요청 값을 제대로 보내주세요'
      })
    }
  })
  • 이름과 아이디를 등록하니 안됨, 아이디를 숫자로 적어주니 작동함


2️⃣ 값을 제대로 보내지 않았을 경우를 예외처리 해보자

  • if문에서 이름과 id가 있으면으로 묶어주면 된다. 근데 이래도 똑같네...? 숫자만 들어오도록 유효성 검사를 해줘야 할 거 같다
  .post((req, res) => {
    
    const {name, userId} = req.body

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

      conn.query(sql, values,
        function(err, results) {
          res.json(results); // 쿼리 결과를 클라이언트에 응답
        }
      )
    } else {
      res.status(400).json({
        message : '요청 값을 제대로 보내주세요'
      })
    }
  })

👉 유효성 검사는 5강의로




profile
김개발의 개발여정

0개의 댓글