[데이터베이스] Sequalize(프로미스지원. 비동기 !!!) : part2 MVC 패턴을 적용하여 코드작성
위에서 기초작업들이 모두 끝났으니 구현을 위한 파트를 진행해보자.
App.js 분석
const express = require('express');
const logger = require('morgan');
const indexRouter = require('./routes/index');
const linksRouter = require('./routes/links');
const app = express();
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use('/', indexRouter);
app.use('/links', linksRouter);
module.exports = app;
1. node.js에서 익스프레스 프레임워크사용
2. morgan 이전에도배웠지만 console.log식으로 찍혀나오는것
3. indexRouter,linksRouter로 연결을 하기위한 기본장치를 만듬.
4. app은 이제 express를 사용한다고해서 express환경? 을 사용하기 시작.
5. 가지고온것을 읽을때 json()화 해서 푸는작업도 사용한다고 명시.
6. app.use(express.urlencoded({ extended: false })); 에 대해서 알아보자면
자바스크립트에서 데이터를 주고받고 읽을 땐 객체 형태를 띄고, 실제로 데이터를 주고 받을 때도 객체 형태를 선호한다.
(따라서 여러 과정을 거쳐 파싱을 거쳐야 하는 것이다. JSON을 사용하는 것처럼) <- 5번을 작업하는 이유기도하다.
extended 옵션의 경우, true일 경우, 객체 형태로 전달된 데이터내에서 또다른 중첩된 객체를 허용한다는 말이며,
false인 경우에는 허용하지 않는 다는 의미다.
우리는 ✔️false를 사용중 : false 값일 시 node.js에 기본으로 내장된 queryString, true 값일 시 따로 설치가 필요한 npm qs 라이브러리를 사용한다.
중첩 객체? <- 데이터 숨기기에 좋을듯? yes! ---->>> qs는 추가적인 보안기능이 있는 모듈
지금은 queryString만 사용할거니 true로 하고 따로 라이브러리를 설치할 필요가없다.
7. app.js에서 하고있는 라우팅작업은
'/'이거 사용시 indexRouter를 호출. '/links' 사용시 linksRouter호출
그럼 해당 파일로 들어가보자.
app.use ('/',indexRouter);
app.use('/links', linksRouter)
Routes 폴더 분석 : 분기작업
const indexRouter = require('./routes/index');
const linksRouter = require('./routes/links');
1. app.js에 있던 index.js폴더는 보이나 links.js가 안보인다.
폴더를 하나 생성해준다.
2. links.js를 어떻게 채울것인가.
아래 예시는 이전 스프린트에서의 라우터 작업예시다. 아래와같은 형식으로 구현해준다.
const router = require('express').Router();
const controller = require('./../controllers');
// GET /items Router와 Controller를 연결합니다.
router.get('/', controller.items.get);
module.exports = router;
=> get, post 메소드별로 라우터 작업을 해준다.
=> controller 객체를 쓰기 위해 controllers 폴더안에 links 폴더를 만들고 그 안에 index.js 파일을 만들었다.
=> 이후에 router작업.
const express = require('express');
const router = express.Router();
const linkController = require('../controllers/links')
/* GET links listing. */
router.get('/', linkController.get)
router.post('/', linkController.post)
router.get('/:id', linkController.redirect)
module.exports = router;
Redirect ? : 읽으셈
리다이렉트 방법으로 데이터를 전달하는 방법이 있다. GET의 특징을 사용하는 것이다.
리다이렉트는 HTTP GET 메소드 방식이라고 앞서 말했다. GET방식은 아래 그림처럼 header에 ? 뒤에 파라미터를 붙여서 전달한다. 때문에 URL에 노출되는 단점이 있다.
Controller 구현하기
GET구현
GET부터 해보자. 얻을 수 있던 정보.
URL모델의 목록!
모델에는 유저도있었꼬 유알엘도 있었꼬~~ URL만 가져오려면?
const model = require('../../models').url
//구조분해 할당으로 아래와같이도 사용할수있다.
const { url: model } = require('../../models');
res.status(200).json(result) <- 1,2번째는 이런식으로 보내줘야하겠는건 알겠는데..
URL모델의 목록
그리고 뭘 어쩌란거야?? 당연히 모른다. 왜? 우리는 이거 처음 배우는 뉴비들이자네
공식문서보고 남들이 쓴 블로그 보고 따라가야지.. ABCD가 뭔지모르는데 APPLE 읽어보세요 이런거 시키는거랑 다름없다고요
✔️클라이언트가 get요청을 보내면, 에러가아니면 URL모델의 목록을 답변으로 준다. (쿼리구문으로 생각하면 SELECT * FROM url) 이랑 같은것. 그래서 아래와 같이 구현이 가능하다.
➡️ 클라이언트와 서버는 http 프로토콜을 이용해 소통한다. http는 req, res가 필요하다.
➡️ async가 튀어나오는 이유는? sequelize Promise 패턴 기반의 Node.js ORM입니다. 라고 쓰여있다.
비동기적으로 실행되는 함수를 동기적으로 흐름을 제어하는 행위에는 3가지가있다 : 콜백,프로미스,async 와 await
프로미스 패턴기반 ORM이란 말은 async와 await를 이용해서 간편하게 비동기 제어도 가능하다는말이다.
//const model = require('../../models').url
//모델에 있는 url테이블을 사용할것
const { url: model } = require('../../models');
module.exports = {
get : async(req,res) => {
const data = await model.findAll(); //전체테이블을 읽을수있다.
res.status(200).json(data)
},
}
https://sequelize.org/master/manual/model-querying-finders.html
Manual | Sequelize
Model Querying - Finders Finder methods are the ones that generate SELECT queries. By default, the results of all finder methods are instances of the model class (as opposed to being just plain JavaScript objects). This means that after the database return
sequelize.org
POST구현
제목을 가져오기위해 modules/utils.js를 이용하라고 되어있다.
const request = require('request');
const rValidUrl = /^(?!mailto:)(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))|localhost)(?::\d{2,5})?(?:\/[^\s]*)?$/i;
exports.getUrlTitle = (url, cb) => {
request(url, function (err, res, html) {
if (err) {
console.log('Error reading url heading: ', err);
return cb(err);
} else {
const tag = /<title>(.*)<\/title>/;
const match = html.match(tag);
console.log(match.length)
const title = match ? match[1] : url;
return cb(err, title);
}
});
};
exports.isValidUrl = url => {
return url.match(rValidUrl);
};
isValidUrl과 getUrlTitle 함수를 사용해야함을 알것같다.
url객체를 받아오는 변수가 하나 필요하다.
=> 왜? 클라이언트는 post요청을 보낼때 body에 정보를 보낸다.
=> 클라이언트가 보낸 요청의 body 객체 안에 url 이 들어 있게 된다.
const url = req.body.url
공식문서에서 찾은 위와같은 메소드를 사용해야한다.
주석을 달아놨다.
1. 위와같이 클라이언트에게 http규약으로응답을 줘야하니 req,res를 인자로 받는 함수를 만들어야한다.
2. 위에 만들어놧던 url = req.body.url을 적어준다.
3. isValidUrl <- url 유효성검사. 정상적인 url이 아니면 실패메시지.
4. url은 정상적이야 첫번째 통과! 유효하니 title을 가져올 작업을해준다.
//const model = require('../../models').url
//모델에 있는 url테이블을 사용할것
const { url: model } = require('../../models');
module.exports = {
get : async(req,res) => {
const data = await model.findAll(); //전체테이블을 읽을수있다.
res.status(200).json(data)
},
post: async(req, res) => {
//1. request에서 url객체를받는다. (클라이언트에게 들어온 요청.)
let url = req.body.url;
//2. url이 정상적으로 없을경우 =>, 실패메시지
if(!isValidUrl(url)){
return res.status(400);
}
//3. 유효한경우 title을 가져온다.
getUrlTitle(url, async (err,title)=>{
if(err){ //3.1 유효한경우에서도 두가지로 조건을 나눈다. 에러일경우, 아닐경우
return res.status(400)
}
//에러가 아닐경우? 생성해준다.
//있는지없는지확ㅇ니
try{
//try문 안에 내용은 공식문서에서 따오면된다.
const [result, created] = await model.findOrCreate({
where: { url },
defaults: {
title
}
});
if(created){
res.status(201).json(result); //create 없어서 만들어서 리턴해준거
}
res.status(201).json(result); //find 원래있던정보
}catch(error){ // 여기서 에러는 우리측 에러. 서버에러같은거
res.status(500)
}
})
},
}
💡 where은 항목을 찾을때 사용하는 것이고, defaults 은 찾는 항목이 없을때, (초기) 새로 인스턴스를 생성 하기위해 반드시 넣어야 할 요소
Redirect 구현
✔️ 리디렉션이 성공하여 기존 우리의 로컬호스트 주소를 입력하면 로컬호스트가 아니라 깃허브의 홈페이지가 뜨게 하는걸 목적
/:id 에서
/: === req.params
/:id === req.params.id
이원리로 id를 가져와야한다.
url 모델에서 id를 어떻게 찾나? 공식문서에서..^^ㅎㅎ..휴..
Manual | Sequelize
Model Querying - Basics Sequelize provides various methods to assist querying your database for data. Important notice: to perform production-ready queries with Sequelize, make sure you have read the Transactions guide as well. Transactions are important t
sequelize.org
// const model = require('../../models').url
const { url: model } = require('../../models');
const {isValidUrl,getUrlTitle} = require('../../modules/utils');
module.exports = {
get: async (req, res) => {
const data = await model.findAll();
res.status(200).json(data);
},
post: async(req, res) => {
//1. request에서 url객체를받는다. (클라이언트에게 들어온 요청.)
let url = req.body.url;
//2. url이 정상적으로 없을경우 =>, 실패메시지
if(!isValidUrl(url)){
return res.status(400);
}
//3. 유효한경우 title을 가져온다.
getUrlTitle(url, async (err,title)=>{
if(err){ //3.1 유효한경우에서도 두가지로 조건을 나눈다. 에러일경우, 아닐경우
return res.status(400)
}
//에러가 아닐경우? 생성해준다.
//있는지없는지확ㅇ니
try{
//공식문서에서 따오면된다.
const [result, created] = await model.findOrCreate({
where: { url },
defaults: {
title
}
});
if(created){
res.status(201).json(result); //create 없어서 만들어서 리턴해준거
}
res.status(201).json(result); //find 원래있던정보
}catch(error){ // 여기서 에러는 우리측 에러. 서버에러같은거
res.status(500)
}
})
},
redirect: async (req, res) => {
/*
1.params 가져오기
2.id로 url 조회
3. visits 필드의 값 1 추가
4. 해당 url로 리다이렉트
*/
const urlId = req.params.id;
const result = await model.findOne({
where: {
id: urlId,
},
});
if (!result) {
return res.sendStatus(204);
}
try {
await result.update({
visits: result.visits + 1,
});
const redirectUrl = result.url;
res.redirect(redirectUrl);
} catch (err) {
res.sendStatus(500);
}
},
};
아래는 레퍼런스 프로미스 코드.
이걸로도 공부를 해봐야겠다.
const utils = require('../../modules/utils');
const { url: URLModel } = require('../../models');
module.exports = {
get: async (req, res) => {
const result = await URLModel.findAll();
res.status(200).json(result);
},
post: (req, res) => {
const { url } = req.body;
if (!utils.isValidUrl(url)) {
return res.sendStatus(400);
}
utils.getUrlTitle(url, (err, title) => {
if (err) {
console.log(err);
return res.sendStatus(400);
}
URLModel
.findOrCreate({
where: {
url: url
},
defaults: {
title: title
}
})
.then(([result, created]) => {
if (!created) {
return res.status(201).json(result); // find
}
res.status(201).json(result); // Created
})
.catch(error => {
console.log(error);
res.sendStatus(500); // Server error
});
});
},
redirect: (req, res) => {
URLModel
.findOne({
where: {
id: req.params.id
}
})
.then(result => {
if (result) {
return result.update({
visits: result.visits + 1
});
} else {
res.sendStatus(204);
}
})
.then(result => {
res.redirect(result.url);
})
.catch(error => {
console.log(error);
res.sendStatus(500);
});
}
}