이번 과제에서는 쇼핑몰 애플리케이션의 데이터베이스를 구축해보았다.
클라이언트의 경우 이미 구축이 되어져 있고, MVC패턴에서 Model부분만 구현해보았다.
일단 시작하기 전, 환경변수를 먼저 세팅해주었다.
node.js 기반 프로젝트 개발을 하면서 외부에 알려지면 안될 민감한 정보들은 일반적으로 소스코드에 하드코딩하거나 특정 설정파일을 만들어서 가져오는 경우가 있다. 하드코딩으로 소스코드에 정보를 넣어두면 깃허브 등 오픈소스 공개시 관련 정보 유출의 위험이 있으며, 관련 정보 수정시 서비스를 재배포해야하는 문제가 있다.
이러한 정보들은 환경변수에 저장해놓고 사용하는 것이 일반적이다. 다음과 같이 환경변수명을 설정하고 불러올 수 있다. Node.js는 환경변수에 접근할 때 process.env라는 자바스크립트 객체를 사용한다. 전역객체이므로 별도로 모듈을 임포트하지 않아도 된다.
// 1. 환경변수 설정
export 환경변수명 = "설정값"
// 또는
process.env.환경변수명 = "설정값"
// 2. 환경변수 불러오기
process.env.환경변수명
.env는 환경변수를 관리하는 파일이다. node.js의 dotenv 모듈이 필요하다. 프로젝트 최상위 경로에 .env파일을 생성하고, 환경변수 값을 넣어주면 된다. 그리고 오픈소스에 업로드하지 않도록 .gitignore 파일에 .env를 등록해야 한다.
// .env
DATABASE_USERNAME="root"
DATABASE_PASSWORD="1234"
DATABASE_NAME='learnMySQL'
// mysql.js에서 다음과 같이 환경파일 내부의 설정값을 불러올 수 있다.
const dotenv = require("dotenv");
class Database {
constructor(){
this.config = {
host: "localhost",
user: process.env.DATABASE_USERNAME || "root",
password: process.env.DATABASE_PASSWORD || "",
database: process.env.DATABASE_NAME || "mySQL";
}
}
}
//.gitignore
.env
CREATE DATABASE cmarket;
schema.sql
은 관계형 데이터베이스의 기초 뼈대의 역할을 한다. schema.sql
을 활용하여 내부 테이블을 생성하였다. 커맨드 창에 명령어를 하나하나 입력할 필요 없이 명령문들을 정리한 파일을 작성한 다음 배치모드로 실행중인 MySQL 서버에 한번에 적용할 수 있다. 파일 seed.sql
에는 데이터를 추가하는 명령어가 작성되어 있다.
-> 미리 파일을 만들어두고 명령어를 실행하는 방식으로 데이터베이스에 테이블을 생성하고 데이터를 추가할 수 있는 것 같다.
$ mysql -u root -p < server/schema.sql -Dcmarket
명령어를 이용하여 cmarket 데이터베이스에 테이블을 생성.
$ mysql -u root -p < server/seed.sql -Dcmarket
명령어를 이용하여 생성한 테이블에 기반이 되는 데이터를 저장.
package.json을 확인하면 dependencies에 mysql이 있다.
이 모듈은 이 모듈은 Cmarket Server가 MySQL server와 상호작용 할 수 있게 합니다.
보안상/편의상 이유로 비밀번호는 환경 변수로 분리해놓았다. (.env파일 내 아래와 같은 코드로..) github 같이 공개된 환경에 비밀번호가 노출되면 안되기 때문에, 환경 변수를 관리하는 파일은 반드시 .gitignore 파일에 등록해두었다.(node_modules와 env파일이 있다)
DATABASE_SPRINT_PASSWORD=
config/config.js파일에서 아래와 같이 mySQL과의 연결을 위한 환경 변수 DATABASE_SPRINT_PASSWORD 를 사용하고 있다. npm 모듈 dotenv 로 환경 변수를 사용하는 방법을 확인해보았다(공식문서)
app.js 파일에는 express로 서버를 만드는 코드가 이미 작성되어있다. 클라이언트와 연결된 서버이며, express 는 Node.js 웹 서버의 진입점이자 자바스크립트의 서버 프레임워크 중 하나이다.
controllers/index.js 파일(아래 코드)은 get, post 요청을 처리하는 코드가 이미 작성되어 있다. 라우터 분기별로 요청에 대해 수행하는 함수이다. 클라이언트로 요청을 받은 것을 토대로 데이터베이스에 요청한다. API문서를 참고하여 controller가 어떻게 서버에 요청을 보내고 응답을 전송하는지 알 수 있다.
// controllers/index.js
const models = require("../models")
module.exports = {
// 주문과 아이템에 관한 Action을 분리한다.
// 1. Orders
orders: {
get: (req, res) => {
// 다음과 같이 URL의 :userId를 사용한 것을 확인할 수 있다.
const userId = req.params.userId;
if (!userId) return res.status(401).send("Unauthorized user.");
else {
models.orders.get(Number(userId), (error, result) => {
// query함수의 callback을 controller.js에서 규정할 수 있도록 하였다. 훨씬 명확하다.
if (error) res.status(404).send("No orders found.")
else res.status(200).json(result);
})
}
}
},
post: (req, res) => {
const userId = req.params.userId;
const { orders, totalPrice } = req.body;
if (orders.length === 0) return res.status(400).send("Bad request.");
else {
models.orders.post(Number(userId), orders, totalPrice, (error, result) => {
if (error) res.status(404).send("Not found.");
else res.status(201).send("Order has been successfully placed.");
})
}
},
// 2. Items
items: {
get: (req, res) => {
models.items.get((error, result) => {
if (error) res.status(404).send("Not found");
else res.status(200).json(result);
})
}
}
}
db/index.js 파일에서는 아래와 같이 mysql 모듈을 사용해 데이터베이스와 서버를 연결한다.
models/index.js 파일은 controller에서 사용할 orders, items 모델을 정의해야 한다. 기본적인 틀은 짜여져 있고,db/index.js의 함수를 불러와 SQL을 사용하여 쿼리문을 통해 DB의 정보를 처리한다. 데이터베이스 쿼리는 반드시 비동기 요청인점을 고려해야 한다.
데이터베이스에 존재하는 테이블의 개수는 총 4개이다. DESC
명령문으로 각각 테이블의 구조를 파악해보았다. 가장 왼쪽 필드값으로 1:N, N:M 관계를 아이패드에 그려보고 연결고리를 이어주니 전체적으로 스키마 확인이 수월했다.
db
객체는 /db/index.js
파일에서 가져온 것이다. 추가내용은 공식문서를 잠시 참고하였다.items: {
get: (callback) => {
let sql = ` `; //쿼리문 작성
db.query(sql, (err, result) => {
callback(err, result);
})
}
}
sql
이라는 변수에 items
테이블의 모든 데이터를 가져오는 쿼리문을 문자열로 저장하였더니 아래와 같이 모든 상품들이 잘 보였다.
orders에는 (한 요청당 하나의 주문이 추가되므로) 하나의 데이터만 insert되지만, 하나의 주문에 여러 상품이 있을 수 있다. 따라서 bulk insert
하는 방법을 아래와 같이 params라는 변수를 이용하여 여러개의 레코드를 한 번에 저장하였다.
post: (userId, orders, totalPrice, callback) => {
const sql1 = ` `//첫번째 쿼리문 작성
db.query(sql1, (err, result) => {
if (result) {
const sql2 = ` `//두번째 쿼리문 작성
const params = //[[], [], ...[]]
db.query(sql2, [params], (err, result) => {
callback(err, result)
})
}
else { //result가 undefined일 경우
callback(err, result);
}
})
}
또한 첫번째 쿼리문의 result
값 내에서 자동생성된 PK값을 받아와서 두번째 쿼리문 처리 시 사용한다. result.insertId
를 이용하면 된다.
if (result) {console.log(result)}
를 찍어보면 아래 내용을 확인할 수 있다.
OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 1,
serverStatus: 2,
warningCount: 0,
message: '',
protocol41: true,
changedRows: 0
}
SELECT 보여줄 데이터의 필드명
FROM 데이터를 추출할 테이블명
//여기서 만약 추출하고자 하는 데이터가 테이블에 없을 때 아래와 같이 연결하기.
JOIN ON
JOIN ON
JOIN ON
//아래와 같이 필터를 줄 수 있다. 필터가 일어나는 테이블과 데이터추출이 일어나는 테이블이 다를 때에도 JOIN으로 연결해야 한다.
WHERE
3-tier 아키텍처를 모두 경험해본 첫 스프린트였다. 부분적으로만 코드를 작성했었기 때문에, 0에서부터 혼자 스스로 코드구현을 할 수 있을 때까지 우선은 여태 배운것을 꼼꼼히 복습해야겠다.
나중에는 혼자서 client, server, database까지 모두 만들어보고 싶다!
좋은 글이네요 잘 봤습니다 공부하는데 도움이 될것 같아요.