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}님 로그인되셨습니다.` 👉 메인 페이지로 이동



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로 유효성검사를 통해 알아보도록 하겠다.
참고
'Node.js' 카테고리의 다른 글
| 12/25 로그인(인증) 세션 만료 (0) | 2023.12.25 |
|---|---|
| 12/22 express.js 유효성 검사 (0) | 2023.12.25 |
| 12/14~15 (0) | 2023.12.18 |
| 12/13 express.js ==과 ===의 차이, 예외처리 고도화 (1) | 2023.12.18 |
| 12/11 express.js postman, express.js에서 사용되는 메소드들, 속성들 (0) | 2023.12.14 |