스터디/KAKAOCLOUDSCHOOL
[Node + JavaScript] 개발자 지망생 스터디 - 19일차
shineIT
2022. 11. 28. 13:38
SPA(Single Page Application) 구현 방법
- 하나의 HTML 파일에 스크립트를 이용해서 여러 개의 콘텐츠를 출력하는 방식 - 비추천
- 각 콘텐츠에 해당하는 별도의 html파일을 만들고 이를 불러들이는 방식
- 대부분의 SPA 프레임워크(React, Vue 등)들이 이 방식으로 구현(js 파일의 형태로 구현)
- 이렇게 만들어진 콘텐츠에 해당하는 파일들을 컴포넌트라고 함.
하나의 HTML에서 다른 HTML 파일을 가져오는 것은 ajax로 가능
<a href="파일 경로"> <a href="서버에 요청하는 경로">
클라이언트가 서버에 요청 -> 서버 -> 클라이언트에게 응답
💡 클라이언트가 어떤 행위를 할 떄 서버는 알고 있어야 한다.
12. 서버의 데이터가 마지막으로 업데이트 된 시간을 전송하는 처리를 App.js 파일에 작성
app.get('/item/updatedate', (req, res) => {
fs.readFile('./update.txt', (error, data)=>{
res.json({'result': data.toString()});
})
});
더보기
클라이언트와 서버간의 데이터 교환 시 고려할 내용
- 클라이언트가 접속할 때 마다 서버에서 데이터를 받아오는 경우
실시간으로 데이터가 많이 변하는 경우는 이렇게 구현하는 것이 좋음
거의 데이터가 변화가 없는 경우나 오프라인 상태에서도 애플리케이션을 사용하고자 하는 경우에는 이방법은 좋은 방법이 아님 - 서버의 데이터를 로컬에 저장을 하고 로컬에 데이터가 없으면 다운로드 받고 로컬에 데이터가 있는 경우는 서버의 데이터에 변화가 있다면 변화가 생긴 데이터만 가져오는 방식으로 구현
게임은 거의 대부분 이렇게 구현함
캘린더 앱의 경우도 이렇게 구현하는 것이 좋음. - 데이터가 변경된 경우에는 기본키 값들을 비교해서 서버에 존재하는데 클라이언트에 없으면 데이터를 가져오고 서버에 없는 클라이언트에 존재하면 클라이언트에서 삭제함
프로그래밍 언어에서 관계형 데이터 베이스를 사용하는 방법
1. 데이터 베이스 드라이버만 이용해서 작업
- 소스 코드 안에 SQL을 삽입해서 작업하는 방식
- 소스 코드 안에 SQL이 삽입되어 있어 유지보수가 어려움
2. SQL Mapper 방식
- 소스 코드와 SQL을 분리해서 작성하는 방식
- 사용이 쉽기 때문에 SI와 같은 여러 명이 공동으로 작업하는 프로젝트에서 많이 사용
- 성능은 떨어짐
- Java 나 ASP.net 에서 사용하는 MyBatis 가 가장 대표적인 프레임워크임
3. ORM
- 관계형 데이터베이스의 테이블을 클래스와 그리고 테이블의 행을 인스턴스와 매핑해서 사용하는 방식으로 SQL을 사용할 수도 있고 사용하지 않을 수도 있음
- 일반적으로 성능이 SQL Mapper 보다 좋기 때문에 솔루션 개발에 많이 이용함
- 하지만 학습이 어려워 SI 업무에는 적합하지 않음
- JAVA의 JPA(Hibernate로 구현하는 경우가 많음) 나 node의 sequelize 그리고 Python의 Django 등이 대표적인 프레임워크임
- 대부분의 경우는 데이터베이스를 변경할 때 설정만 변경하면 됨
Node_ORM
1. ORM(Object Relational Mapping)
- 객체 지향 패러다임을 관계형 데이터베이스에 적용하는 기술
- 관계형 데이터베이스의 TABLE은 객체 지향 프로그래밍의 클래스와 유사한데 TABLE에서는 여러개의 컬럼을 만들지만 CLASS에서는 속성을 만들어서 저장하는 것이 유사
- 이런 이유 때문에 Instance 와 Row가 유사
- 이를 적절히 이용해서 Instance를 가지고 관계형 데이터베이스 작업을 할 수 있도록 만든 프레임워크가 ORM
- Instance를 가지고 작업을 수행하면 프레임워크가 SQL로 변경을 해서 데이터베이스에 작업을 수행하는 형태로 동작
- Node에서는 sequelize 모듈이 이러한 작업을 수행할 수 있음
2. Sequlize를 이용한 하나의 테이블 연동
1) 패키지 설치
- npm install sequelize sequelize-cli mysql2
2) sequelize 초기화
- npx sequelize init 명령을 수행
- 초기화를 수행하면 config, migration, models, seeders 디렉토리가 생성됨
config : 데이터베이스 접속 정보 설정
models : 각 테이블과 매핑 되는 클래스를 설정
migration : 데이터베이스 스키마(구조, 테이블)가 변경되는 경우를 위한 설정
seeders : 테스트 데이터 사용을 위한 설정
3. 데이터베이스 접속 설정
1) config 디렉토리의 config.json 파일을 수정
{
"development": {
"username": "root",
"password": "qlxkals",
"database": "itoriginal",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": "qlxkals",
"database": "itoriginal",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": "qlxkals",
"database": "itoriginal",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
2) models 디렉토리의 index.js 파일을 수정
// 모듈 import
const Sequelize = require('sequelize');
// 환경 설정
const env = process.env.NODE_ENV || 'development';
// 환경 설정 내용 가져오기
const config = require('../config/config.json')[env];
// 내보낼 객체 생성
const db = {};
// ORM 설정
const sequelize = new Sequelize(
config.database, config.username, config.password, config);
db.sequelize = sequelize;
module.exports = db;
3) App.js 파일에 데이터베이스 연결 코드를 작성하고 실행
...
// sequelize를 이용한 데이터베이스 연결
// require를 할 때 디렉토리 이름을 기재하면
// 디렉토리 안에 index.js의 내용을 import
const {sequelize} = require('./models')
sequelize.sync({force:false})
.then(()=>{
console.log("데이터베이스 연결 성공")
})
.catch((err)=>{
console.log("데이터베이스 연결 실패")
});
//기본 요청을 처리
app.get ...
4. 하나의 테이블을 연동
- 테이블을 먼저 만들고 연결 시켜도 되고 모델을 만들고 처음 실행을 하면 테이블이 존재하지 않으면 테이블이 생성됨
- 테이블이 이미 존재하면 존재하는 테이블과 연결함
대부분의 실무 환경에서는 테이블을 먼저 만들고 모델을 만들고 학습을 할 때는 모델을 가지고 테이블을 생성함
더보기
MySQL이나 MariaDB는 Snake 표기법을 사용함
MemberBoard => member_board
1) 테이블 과 연결할 모델 생성
- Sequelize.Model을 상속받은 클래스를 생성
- 2개의 메서드 : 오버라이딩
static init 메서드 : 현재 테이블에 대한 설정
static associate 메서드 : 다른 테이블과의 관계를 위한 설정
static init 메서드
- 첫번째 인수 : 컬럼에 대한 설정
- 자료형 매핑
- VARCHAR ↔ STRING
- CHAR ↔ CHAR
- TEXT ↔ TEXT
- TYNYINT(1) ↔ BOOLEAN
- INT,INTERGER ↔ INTEGER
- FLOAT ↔ FLOAT
- DOUBLE ↔ DOBLE
- DATETIME ↔ DATE
- DATE ↔ DATEONLY
- TIME ↔ TIME
- BLOB ↔ BLOB
- JSON ↔ JSON
- 제약조건
- allow Null
- unique
- defaultValue
- validate
- 자료형 매핑
- 두번째 인수 : 테이블에 대한 설정
- timestamps : true로 설정하면 createdAt 과 updatedAt 컬럼이 자동으로 생성되서 데이터가 생성된 날짜와 수정된 날짜를 자동으로 삽입
- underscored : 시퀄라이즈는 이름을 기본적으로 Camel Case로 만드는데 이를 Snake Case로 변경하고자 할 때 사용
- modelName : 노드 프로젝트에서 사용할 모델 이름
- tableName : 데이터베이스의 테이블 이름
- paranoid : 데이터를 삭제할 때 삭제하지 않고 deletedAt 이라는 컬럼을 생성해서 이 컬럼의 값을 true로 만들고 조회하기 위한 옵션
- charset 과 collate : 캐릭터 셋으로 한글을 사용하고자 할 때는 utf8이나 utf8_general로 설정하고 이모티콘까지 사용하고자 하면 utf8mb4와 utfmb4_general_ci를 설정
- static associate 메서드
자신의모델이름.hasMany 나 belongsTo 를 호출하는데 hasMany는 참조되는 경우(부모 테이블로 외래키의 참조 대상)이고 belogsTo 는 참조하는 경우(외래키로 소유한 경우)에 호출
매개 변수로는 상재방모델이름, {foreignKey:'외래키이름', targetKey:'참조하는 속성'} 를 설정하면 됨
2) 모델을 이용한 쿼리 메서드
- 삽입 : create
- 조회 : findOne, findAll
- 수정 : update
- 삭제 : delete
3)연동할 테이블을 위해서 기존 테이블을 삭제
- drop table goods;
4) modes 디렉토리에 goods 테이블과 연동할 모델을 생성 - good.js
const Sequelize = require('sequelize');
module.exports = class Good extends Sequelize.Model{
static init(sequelize){
return super.init({
itemid:{
type:Sequelize.INTEGER.UNSIGNED,
allowNull:false,
unique:true
},
itemname:{
type:Sequelize.STRING(100),
allowNull:true
},
price:{
type:Sequelize.INTEGER.UNSIGNED,
allowNull:true
},
description:{
type:Sequelize.STRING(200),
allowNull:true
},
pictureurl:{
type:Sequelize.STRING(100),
allowNull:true
},
updatedate:{
type:Sequelize.STRING(20),
allowNull:true
}
}, {
sequelize,
timestamps:true,
underscored:false,
tableName:'goods',
modelName:'Good',
paranoid:true,
charset:'utf8',
collate:'utf8_general_ci'
})
}
}
5) models 디렉토리의 index.js 파일에 생성한 모델을 사용할 수 있도록 추가 설정
//모듈 import
const Sequelize = require('sequelize');
//모델 가져오기
const Good = require('./good');
//환경 설정
const env = process.env.NODE_ENV || 'development';
//환경 설정 내용 가져오기
const config = require('../config/config.json')[env];
//내보낼 객체 생성
const db = {};
//ORM 설정
const sequelize = new Sequelize(
config.database, config.username, config.password, config);
db.sequelize = sequelize;
db.Sequelize = Sequelize;
db.Good = Good;
Good.init(sequelize);
module.exports = db;
6) App.js 파일에 Good을 사용하기 위한 설정을 추가
const{Good} = require('./models');
7)App.js 파일의 내용을 수정해서 sequelize를 이용해서 데이터 CRUD 작업을 수행
- 데이터 삽입 부분 수정
//데이터 삽입을 처리해주는 함수
app.post('/item/insert', upload.single('pictureurl'),
async (req, res) => {
//파라미터 읽어오기
const itemname = req.body.itemname;
const description = req.body.description;
const price = req.body.price;
//파일 이름 - 업로드하는 파일이 없으면 default.png
let pictureurl;
if(req.file){
pictureurl = req.file.filename
}else{
pictureurl = 'default.jpg';
}
//가장 큰 itemid를 이용해서 itemid 생성
let itemid = 1;
try{
let x = await Good.max('itemid');
itemid = x + 1;
}catch(err){
console.log(err);
}
//데이터 삽입
Good.create({
itemid:itemid,
itemname:itemname,
price:price,
description:description,
pictureurl:pictureurl,
updatedate:getDate()
});
//현재 날짜 및 시간을 update.txt에 기록
const writeStream = fs.createWriteStream('./update.txt');
writeStream.write(getTime());
writeStream.end();
res.json({"result":true});
});
- 전체 데이터 가져오기
//데이터 전체 가져오기 처리
app.get('/item/all', async(req, res) => {
//전체 데이터 가져오기
try{
let list = await Good.findAll();
res.json({"result":true, "list":list});
}catch(error){
console.log(error);
res.json({"result":true});
}
});
- 데이터 페이지로 가져오기
app.get('/item/list', async (req, res) => {
//파라미터 읽어오기
let pageno = req.query.pageno;
if(pageno == undefined){
pageno = 1;
}
console.log(pageno);
//브라우저에서 테스트 - 콘솔 확인
//localhost:9000/item/list
//localhost:9000/item/list?pageno=3
//item 테이블에서 itemid 를 가지고 내림차순 정렬해서
//페이지 단위로 데이터 가져오기
//select * from item order by itemid desc limit 시작번호, 5
//시작번호=(pageno-1)*5
//파라미터는 무조건 문자열입니다.
//파라미터를 가지고 산술연산을 할 때는 숫자로 변환을 수행
try{
//테이블의 데이터 개수 가져오기
let cnt = await Good.count();
//페이지 단위로 데이터 목록 가져오기
let list = await Good.findAll({
offset:(parseInt(pageno)-1)*5,
limit:5
})
res.json({"result": true, "count":cnt, "list":list});
}catch(error){
console.log(error);
res.json({"result":false});
}
});
- 데이터 1개 가져오기 수정
//상세보기 처리를 위한 코드
app.get('/item/detail/:itemid', async (req, res) => {
//파라미터 읽기
let itemid = req.params.itemid;
try{
let item = await Good.findOne({
where:{
itemid:itemid
}
})
res.json({"result":true, "item":item});
}catch(error){
console.log(error);
res.json({"result":false});
}
});
- 데이터 수정 부분을 수정
app.post('/item/update', upload.single('pictureurl'),
async(req, res) => {
//파라미터 가져오기
const itemid = req.body.itemid;
const itemname = req.body.itemname;
const price = req.body.price;
const description = req.body.description;
//예전 파일 이름
const oldpictureurl = req.body.oldpictureurl;
//수정할 파일 이름 만들기
let pictureurl;
//새로 선택한 파일이 있다면
if(req.file){
pictureurl = req.file.filename;
}else{
pictureurl = oldpictureurl;
}
//데이터베이스 작업
try{
await Good.update({
itemname:itemname,
price:price,
description:description,
pictureurl:pictureurl,
updatedate:getDate()
}, {where:{itemid:itemid}})
res.json({"result":true});
}catch(error){
console.log(error);
res.json({"result":false});
}
});
- 데이터 삭제 부분을 수정
//데이터를 삭제하는 함수
app.post('/item/delete', async (req, res) => {
//post 방식으로 전송된 데이터 읽기
let itemid = req.body.itemid;
try{
await Good.destroy({
where:{
itemid:itemid
}
});
res.json({"result":true});
}catch(error){
console.error(error)
res.json({"result":false});
}
});
8) 메서드의 리턴
- 검색을 하는 경우에는 검색 결과가 리턴되지만 삽입, 삭제, 갱신의 경우는 삽입, 삭제, 갱신된 데이터가 리턴됨
5. 2개 테이블 연결
1) 프로젝트 생성 - relativetable
2) 패키지 설치
- express, sequelize, sequelize-cli, mysql2
- nodemon
3) sequelize 초기화
- npx sequelize init
- 4개의 디렉토리가 생성되는지 확인: config, migrations, models, seeders
4) config 디렉토리의 config.json 파일에 데이터베이스 정보를 설정
{
"development": {
"username": "root",
"password": "qlxkals",
"database": "itoriginal",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": "qlxkals",
"database": "itoriginal",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": "qlxkals",
"database": "itoriginal",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
5) models 디렉토리의 index.js를 수정
const Sequelize = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require('../config/config.json')[env];
const db = {}
const sequelize = new Sequelize(config.database, config.username,
config.password, config);
db.sequelize = sequelize;
module.exports = db;
6)프로젝트에 App.js를 생성하고 작성
const express = require('express');
const path = require('path');
const app = express();
app.set('port', process.env.PORT || 3000);
//sequelize 연결
const {sequelize} = require('./models');
sequelize.sync({force:false})
.then(()=>{
console.log("데이터베이스 연결 성공");
})
.catch((error)=>{
console.log(error);
});
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 서버 대기 중');
});
7) package.json 에서 npm start 명령을 내리면 App.js를 실행하고 수정하면 자동 실행되도록 설정
"main": "App.js",
"scripts": {
"start":"nodemon app",
"test": "echo \"Error: no test specified\" && exit 1"
},
8) 터미널에서 실행하고 콘솔을 확인
9)테이블 설계
- users 테이블
- id - 정수, 기본키
- name - 문자열로 20 이고 not null
- age - 정수이고 not null
- created_at - 날짜
- updated_at - 날짜
- comments 테이블
- id - 정수
- comment - 문자열 100 자이고 not null
- created_at - 날짜
- updated_at - 날짜
- commenter - 정수이고 users 테이블의 id를 참조하는 외래키
10) 테이블을 위한 model을 생성 - models 디렉토리에서 수행
- models 디렉토리에 users 테이블의 model 을 위한 users.js
const Sequelize = require('sequelize');
module.exports = class User extends Sequelize.Model{
static init(sequelize){
//테이블에 대한 설정
return super.init({
//컬럼에 대한 설정
name:{
type:Sequelize.STRING(20),
allowNull:false,
unique:true
},
age:{
type:Sequelize.INTEGER,
allowNull:false
}
}, {
//테이블에 대한 설정
sequelize,
timestamps:true,
modelName:'User',
tableName:'users',
paranoid:false,
charset:'utf8',
collate:'utf8_general_ci'
})
}
static associate(db){
//외래키에 대한 설정
db.User.hasMany(db.Comment, {foreignKey:'commenter',
sourceKey:'id'});
}
}
- models 디렉토리에 comments 테이블을 위한 모델을 생성 - comment.js
const Sequelize = require('sequelize');
module.exports = class Comment extends Sequelize.Model{
static init(sequelize){
//테이블에 대한 설정
return super.init({
comment:{
type:Sequelize.STRING(100),
allowNull:false
}
}, {
//테이블에 대한 설정
sequelize,
timestamps:true,
modelName:'Comment',
tableName:'comments',
paranoid:false,
charset:'utf8',
collate:'utf8_general_ci'
})
}
static associate(db){
db.Comment.belongsTo(db.User,
{foreignKey:'commenter', targetKey:'id'});
}
}
11) models 디렉토리의 index.js 파일에 모델들을 import 하고 외부로 내보낼 수 있도록 설정
const Sequelize = require('sequelize');
const User = require('./users');
const Comment = require('./comments');
const env = process.env.NODE_ENV || 'development';
const config = require('../config/config.json')[env];
const db = {}
const sequelize = new Sequelize(config.database, config.username,
config.password, config);
db.Sequelize = Sequelize;
db.User = User;
db.Comment = Comment;
User.init(sequelize);
Comment.init(sequelize);
User.associate(db);
Comment.associate(db);
db.sequelize = sequelize;
module.exports = db;
12) 애플리케이션 실행한 후 콘솔의 SQL 구문을 확인하고 데이터베이스에 테이블이 생성되었는지 확인
desc users;
desc comments;