웹개발 부트캠프 2022 3주차(노드)

백민철·2022년 5월 12일
0
post-thumbnail

(5/6 ~5/12) The Web Developer 부트캠프 2022 강의 내용 정리
범위 : 우리의 첫번째 도구 Node ~ 하나로 묶기 Express Mongoose(섹션 31 ~38)

122. Node.js

Javascript 런타임이다. 즉 브라우저 밖에서 작동되는 JavaScript의 실행 환경이다.

덕분에 클라이언트 측 Javascript로는 브라우저에서 할 수 없었던 많은 일(웹 서비스를 만들고 파일 시스템과 상호작용 등의)들을 할 수 있게 됐다.

123. Node는 어디에 사용하나

  • 웹 서버 구축
  • 명령줄 도구
  • 네이티브 앱 (대표적 앱: vscode)
  • 비디오 게임
  • 드론 소프트웨어

이처럼 브라우저를 벗어나 Javascript로 다양한 곳에 활용될 수 있다.

124. Node REPL

사용자가 특정 코드를 입력하면 그 코드를 평가하고 코드의 실행결과를 출력해주는 것을 반복해주는 환경

터미널 또는 cmd창에서 node를 입력하면
REPL환경으로 접속이 가능하다.

REPL환경에서 간단한 연산, 함수 사용, 변수 활용등 테스트가 가능하고 바로 결과가 확인이 가능하다.

125. Node 파일 실행

node /경로/파일명

126. process와 argv

process

process는 Node에서 사용하는 객체이다.

RELP환경에서 process를 치면 메서드와 특성이 매우 많다.

예를 들면 아래같은 것들이 있다.

  • process.version node버전
  • process.cwd() 현재 작업 디렉토리
  • process.argv node로 스크립트 실행 시 넘겨준 인수들에 대한 정보

process.argv

스크립트 실행 시 넘겨준 인수들에 대한 정보

node test.js hi
console.log(process.argv[0]); //node 실행파일 경로
console.log(process.argv[1]); //스크립트 파일 경로
console.log(process.argv[2]); //hi 출력

//인수를 만든다면 argv 처음 요소 2개는 무시

const args = process.argv.slice(2);
for (let arg of args) {
    console.log(arg);
}

127. require

  • CommonJS를 사용
  • 모듈을 로드하는 키워드
//파일 시스템 모듈 사용 예제
const fs = require('fs');
const folderName = process.argv[2] || 'Project';

try {
    fs.mkdirSync(folderName);
    fs.writeFileSync(`${folderName}/index.html`, '');
    fs.writeFileSync(`${folderName}/app.js`, '');
    fs.writeFileSync(`${folderName}/style.css`, '');
} catch (e) {
    console.log('SOMETHING WRONG!');
    console.log(e);
}

128. import

  • ES6에서만 사용
  • 모듈 로드하는 키워드
import fs from 'fs';

129. 모듈 내보내기

서로 다른 파일간에 Javascript 코드를 공유하는데 사용

module.exports

module.exports객체에 설정을 하면 다른 파일에서 require키워드를 통해 module.exports에 설정된 값에 액세스할 수 있다.

//math.js

const add = (x, y) => x + y;

module.exports.add = add;
//app.js
const math = require('./math');
//구조분해도 가능하다
const { add, PI } require('./math');

math.add(2, 3);

exports

module.exports 단축 구문
exports는 module.exports객체를 참조한다.
exports 자체에 새로운 값을 넣으면 module.exports객체를 참조하지 않게 되므로 주의

//math.js
exports.add = function (x, y) {
    x + y;
};

//app.js
const math = require('./math');
//구조분해도 가능하다
const { add } = require('./math');

math.add(2, 3);

ES6에서는 import키워드를 통해 모듈을 load하고 export 키워드를 통해 객체를 내보낸다.

named export

//math.js

export const add = (x, y) => {
    x + y;
};

exports.print = (x) => {
    console.log(x);
};

//app.js
import { add, print } from './math.js';
print(add(1, 2)); // 3 출력

default export

내보낼 하나의 고정된 값만 내보낼 때 사용

//math.js
const test = 'test';
export default test;
//app.js
import test from './test';

index.js 활용

디렉토리 전체를 불러오라고 하면 Node가 index 파일을 찾게 되고 해당 파일이 내보내는 게 바로 그 디렉토리가 내보내는 항목이 됨

  • 일종의 진입점으로 활용

  • 디렉토리에서의 메인 파일

  • 라이브러리를 만들거나 라이브러리로 작업을 할 때 중요한 역할을 함

/*
shelter라는 디렉토리 안에 blue.js janet.js sadie.js
3개의 파일에서 각각 객체를 내보냄
이를 index.js에서 배열로 저장하여
내보내고 app.js에서 불러오는 예제임
*/
//blue.js
exports default {
    name:'blue',
    color:'grey'
}

//janet.js
exports default {
    name:'janet',
    color:'gray'
}
//sadie.js
exports default {
    name:'sadie',
    color:'silver'
}
//index.js
import blue from "./blue.js"
import janet from "./janet.js"
import sadie from "./sadie.js"

exports const allCats=[blue,janet,sadie]

//app.js
import cats from "./index.js"

130. NPM

노드 패키지 매니저로 패키지(모듈)들을 손쉽게 설치하고 관리할 수 있게 해주는 명령줄 도구임

npm install 패키지명
npm i 패키지명

이렇게 설치되는 패키지는 node_modules 디렉토리에 위치하게된다.

package.json

특정 프로젝트, 패키지, 앱에 대한 정보를 갖고 있음

패키지를 설치하게 되면 패키지에 대한 디펜던시(설치할 모듈 정보)가 추가됨

npm_modules 디렉토리는 파일의 양이 많으므로 이 폴더를 공유하는 것은 바람직하지 않다. 때문에 디펜던시 정보를 토대로 프로젝트의 모듈들을 한 번에 다운로드하는 방식을 사용한다.

script라는 것도 있는데 이것은 run 명령어를 통해서 실행할 것들을 적어두는 것이다.

npm init //package.json파일 생성
npm i // 디펜던시에 기록된 모듈들 install

package-lock.json

node_modules 디렉토리의 콘텐츠에 대한 기록을 관리

의존성 트리에 대한 정보를 가지고 있으며 package-lock.json 파일이 작성된 시점의 의존성 트리가 다시 생성될 수 있도록 보장한다.

이 파일이 없다면 package.json에
A라는 모듈이 업데이트된 버전이 있다면
그 버전을 설치하기 때문에 프로젝트를 공유한 사람끼리 다른 버전의 모듈을 사용할 수 있게됨. 이 때문에 똑같은 버전의 모듈을 사용하도록 보장하는 것이 필요하다.

패키지 지역 설치

npm install 패키지명

지역 설치를 하는 이유는 버전마다 호환되는 환경이 다르거나 프로젝트마다 사용되는 패키지가 다를 수 있기 때문이다.

패키지 전역 설치

npm install -g 패키지명

131. Express

웹 개발 프레임워크로써
서버 역할을 하는 웹 어플리케이션을 만드는데 사용한다.

프레임워크란 다양한 메서드, 애드온과 플러그인을 제공하여 Application개발을 위한 기본적인 뼈대를 제공해준다. 또한 제어의 역전으로 사용자로 하여금 코드를 어디서, 언제 호출할지를 강제한다.

  • 코드의 유연성과 자유를 어느정도 감수하고 빠른 개발 속도와 다양한 기능을 사용하기 위해 프레임워크를 사용함
npm i express

간단한 서버 만들기

import express from 'express';
//app 객체에는 route, param path, get, post, setting등의 메소드나 정보가 들어있다.
const app = express();

//8080포트로 요청을 받도록 함
app.listen(8080, () => {
    console.log('LISTENING ON PORT 8080!');
});

//모든 요청타입에 대해 요청이 들어오면 콜백이 실행됨
app.use(() => {
    console.log('WE GOT A NEW REQUEST');
});

요청 타입으론 get, post, put, delete, patch가 있다.

HTTP 요청은 JAVASCRIPT 객체가 아닌 텍스트 정보이다. express에서는 이를 파싱하여 객체로 변환하여 콜백의 첫번 째 인수로 넘긴다.

//req- 요청 객체
//res- 응답 객체
app.use('요청 url', (req, res) => {
    //요청에 대한 동작
    console.log('WE GOT A NEW REQUEST');
    //응답
    res.send('Hello, We got Your Request!');
});

132. 라우팅

요청과 요청된 경로를 가져와서
응답을 갖는 어떠한 코드에 맞추는 것을 의미한다.

//dogs로 요청할 경우
app.get('/dogs', (req, res) => {
    res.send('WOOF!');
});

//cats로 요청할 경우
app.get('/cats', (req, res) => {
    res.send('MEOW!');
});

/*요청 URL을 제네릭 패턴으로 하면
일치하는 요청경로 및 요청타입이 없을 경우 콜백이 실행된다.
이 경우 맨 밑에 둬야 다른 요청들에 대해 콜백을 수행하게 된다*/
app.get('*', (req, res) => {
    res.send("I don't know that path!");
});

133. express 경로 매개 변수

//요청 url 뒤에 /:매개 변수로 작성하면 params를 통해 매개변수를 받을 수 있다.
app.get('/r/:subreddit/:postId', (req, res) => {
    const { subreddit, postId } = req.params;
    res.send('subreddit:', subreddit, 'postId:', postId);
});

134. 쿼리 스트링

URL의 뒤에 입력 데이터를 함께 제공하는 데이터 전달 방법
req객체의 query라는 특성에 key-value값으로 들어간다.
GET방식으로 데이터를 요청할 때 쓰이는 방법

//요청 url /search?q="test"&n="test"
app.get('/search', (req, res) => {
    const { q, t } = req.query;
    res.send(`q:${q}t:{t}`);
});

135. Nodemon

서버를 재시작하지 않고 변경된 부분을 자동으로 업데이트 해주는 도구

136. 템플레이팅

특정 로직과 HTML 응답 생성을 결합하는 것

항상 같은, 정적인 HTML 코드를 쓰는 대신에 정보와 로직을 넣음으로써 동적인 템플릿을 만듬

템플릿 : HTML 조각

137. EJS

Embedded JavaScriptd의 약자로 템플릿 엔진(뷰 엔진) 중의 하나이며 Javascripts 로직을 템플릿에서 동작하게 해준다.

view engine 설정

app.set('view', 'ejs'); // view engine 설정

view 디렉토리 설정

view 디렉토리 default값은 현재 작업하고 있는 디렉토리의 views이다.
따라서 view 디렉토리 설정을 하지 않으면 views가 없는 디렉토리에서 서버를 동작시키면 views 디렉토리를 찾지 못한다.

app.set('views', path.join(__dirname, '/views')); //views폴더의 절대 경로

138. EJS 문법

<% %>

EJS 태그로 태그 사이에 자바스크립트를 넣는다.

<%= %>

EJS 태그로 태그 사이에 받을 데이터의 변수명 또는 반환되는 값이 있는 자바스크립트문을(Math.rand()같은 것) 넣으면 된다.

res.render("","") 첫 번째 인수에는 렌더링할 파일명, 두 번째 인수에는 템플릿에 같은 이름으로해서 값을 주면 템플릿에서 해당 값을 읽을 수 있다.

<%- %>

HTML 코드를 날것(Raw)로 보여준다.
템플릿을 include할 때 사용

//app.js

app.get('/', (req, res) => {
    const num = Math.floor(Math.random() * 5);
    res.render('random', { rand: num }); // 렌더링할 ejs파일명, 넘길 값들
});
<body>
    <h1><%="Math.floor(Math.random()*5)%></h1>
    <p><%=rand%></p>
    <%-
    <h1></h1>
    %>
</body>
//0~5이하 자연수

EJS 조건문

<body>
    <%if(rand>0){ %>console.log("good") <% } else{ %> console.log("Not bad") <%}%>
</body>

EJS 반복문

<body>
    <%for(i=0;i<5;i++){%> console.log(<%=i%>) <%}%>
</body>

139. 정적 Assets 사용하기

정적 Assets

CSS,JAVASCRIPT,사진 같은 파일

//public 디렉토리를 정적 Assets의 base로 설정
app.use(express.static('public'));

//public의 절대경로로 설정
app.use(express.static(path.join(__dirname), 'public'));
//app.css가 public 에 있을 경우 public경로 없이 사용 가능 <link rel="stylesheet" href="/app.css" />

140. EJS와 파일 분할

<%-include %> 태그를 활용하여
head,navbar,footer 등등 템플릿을 포함시킬 수 있다.

//head.ejs
<body>
    <h1>head</h1>
</body>
//home.ejs <%- include('partials/head')>

<body>
    <h1>home</h1>
</body>

141. Get 요청과 Post 요청

Get요청

  • 주로 정보를 가져올 때 (불러오기)
  • 같이 따라오는 데이터는 쿼리스트링에 담는다.
  • URL의 최대길이가 있어 보낼 데이터가 한정된다.

Post 요청

  • 주로 정보를 올리거나 보낼 때(생성)
  • 보낼 데이터는 Body에 포함된다.
  • 길이가 제한된 Get방식보다 더 많은 데이터 보내기 가능
  • Body에 보내지는 데이터의 타입은 JSON,application/x-www-form-urlencoded, multipart/form-data등이 있다.

요청 구문 분석

//body

{
    "name":"test",
    "color":"brown",
}
//form request body를 해석하기 위해 사용
app.use(express.urlencoded({ extended: true }));

//json 타입의 body를 해석하기 위해 사용
app.use(express.json())

app.post("/",(req,res){
    const {name,color} = req.body
})

extended 값을 false로 하면 querystring모듈을 사용
true로 하면 qs모듈을 사용

142. REST

REST(Representational State Transfer)의 약자로 자원을 이름으로 구분하여 해당 자원의 상태를 주고받는 모든 것을 의미

즉 요청 URI에 자원을 명시하고 HTTP Method를 통해 해당 자원에 대해 CURD를 적용하는 것을 의미

143. RESTful API

URI + HTTP METHOD + 필요시 id나 고유 식별자를 사용하여 정보 교환을 하는 것

HTTP METHOD(GET,POST,PATCH,DELETE,PUT)
GET /comments - list all comments
POST /comments - Create a new comment
GET /comments/:id -Get one comment(using ID)
PATCH /comments/:id -Update one comment
DELETE /comments/:id -Destroy one comment

144. Express redirect

초기 응답에서 전송된 위치를 기반으로 두 번째 요청을 이어서 하게 한다.
res.redirect("요청 url")

145. 디테일 라우트 (쇼 라우트)

고유식별자를 활용하여 특정한 하나의 리소스에 대해 보여주는 것

경로 끝에 고유 식별자값을 넣으면 req.params에 값을 들어간다.

app.get('/comments/:id', (req, res) => {
    const { id } = req.params;
});

146. UUID

범용 고유 식별자이다.

  • 데이터들이 나중에 단일 DB로 통합되더라도
    식별자가 중복될 확률이 매우 낮다.
  • 128비트이며, 32자리의 16진수로 표현
  • 8자리-4자리-4자리-4자리-12자리 패턴으로 5개의 그룹으로 구분된다.
  • UUID를 만드는 버전에 따라 생성 기준이 다르다.(보통 4버전인 랜덤 생성을 사용한다.)
//구조분해시 새로운 이름 명명
import { v4 as uuidv4 } from 'uuid';
const id = uuidv4();
console.log(id); //1944cc80-c239-4394-9f5f-9341f8f9945f

147. PATCH와 PUT

PUT

전체 내용을 업데이트하는 용도

보내지지 않은 페이로드에 대해선 NULL값으로 업데이트

PATCH

부분적으로 업데이트하는 용도

보내지지 않은 페이로드에 대해선 기존 값을 유지

148. express method override

브라우저의 HTML 폼은 get이나 post 요청만
전송할 수 있어서 Put 요청이나 Patch 요청 Delete 요청은 보낼 수 없다. 이 경우 method-override라는 패키지를 활용하여 우회하는 형식으로 가능하게 할 수 있다.

import methodOverride from 'method-override';
app.use(methodOverride('_method'));

app.patch('/comments/:id', (req, res) => {});
<body>
    //?_method부분으로 인해 patch요청으로 express가 인식함
    <form method="post" action="/comments/<%=comment.id%>/?_method=PATCH"></form>
</body>

149. 데이터베이스 개요

  • 데이터 지속성
  • 데이터를 효율적으로 저장, 사용, 관리하기 쉽게 해준다.
  • 데이터베이스 관리시스템으로 보안 기능이나 관리자로서의 접근을 누구에게 허용할지에 대한 제어가 가능하다.

150. SQL, NOSQL

SQL

  • 관계형 데이터베이스
  • 모든 작업이 테이블에서 이루어진다.
  • 테이블과 테이블을 연결하여 작업한다.

NoSQL

  • 특정한 패턴을 따르거나 X, Y를 따지느냐를 보아 그렇지 않은 건 전부 다 NoSQL

  • 키 값 쌍도 있고 칼럼 데이터베이스도 있고
    그래프 데이터베이스, 문서 데이터베이스와 튜플 저장소가 있다.

  • SQL에 비해 유연하고 자유롭다.

151. Mongo

  • NoSQL
  • 배우기 쉽다.
  • express와 같이 많이 쓰인다.
  • Javascript 구문을 쓴다.

152. Mongo Shell

Javascript Shell이라 Javascript 코드 입력 가능

db 명령어

디폴트로 사용하게 될 데이터베이스

show dbs(databases)

데이터베이스 목록

use 데이터베이스명

없으면 데이터베이스 생성 있으면 db 전환

Mongo db는 데이터베이스를 생성해도 실제 저장할 데이터가 있을 때까지 대기한다. 따라서
show dbs를 했을 때 생성한 db가 출력되지 않는다.

153. BSON이란

이진법 JSON이다. JSON보다 데이터를 훨씬 더 압축하여 저장할 수 있다.

JSON의 문제는 홈페이지나 문서에 들어갈 때
상당히 느려서 Mongo에선 JSON을 이진법으로 인코딩한 BSON을 사용한다.

154. Mongo 데이터베이스에 삽입

insertOne(하나의 객체만을 전달), insertMany(여러 객체 전달), insert(하나 또는 여러개 객체 전달) 세 가지 메소드가 있다.

db.dogs.insertOne({
    name: 'Charlie',
    age: 3,

    breed: 'corgi',
    catFriendly: true,
});

//아래처럼 뜨면 삽입되었음을  의미
{
"acknowledged" : true,
"insertedId" : ObjectId("627bf7a4491e7e80fb01fa6f")
}

//2개 객체 삽입
db.dogs.insert([{name: "Wyatt",

breed: "Golden",age: 14, catFriendly: false},{name: "Tonya",

breed: "Chihuahua",age:17, catFriendly: true}])

//아래처럼 뜨면 2개 모두 삽입되었음을 의미
BulkWriteResult({
        "writeErrors" : [ ],
        "writeConcernErrors" : [ ],
        "nInserted" : 2,
        "nUpserted" : 0,
        "nMatched" : 0,
        "nModified" : 0,
        "nRemoved" : 0,
        "upserted" : [ ]
})

155. Mongo 데이터베이스에서 찾기

find, findOne


db.dogs.find() //해당 집합에 있는 객체 모두 출력

{ "_id" : ObjectId("627bf7a4491e7e80fb01fa6f"), "name" : "Charlie", "age" : 3, "breed" : "corgi", "catFriendly" : true }
{ "_id" : ObjectId("627bf938491e7e80fb01fa70"), "name" : "Wyatt", "breed" : "Golden", "age" : 14, "catFriendly" : false }
{ "_id" : ObjectId("627bf938491e7e80fb01fa71"), "name" : "Tonya", "breed" : "Chihuahua", "age" : 17, "catFriendly" : true }


db.dogs.find({"name":"Tonya"})

{ "_id" : ObjectId("627bf938491e7e80fb01fa71"), "name" : "Tonya", "breed" : "Chihuahua", "age" : 17, "catFriendly" : true }

156. Mongo 데이터베이스에서 업데이트

updateOne(매치되는 첫 항목만), updateMany(모두), replaceOne(document의 특정 내용 완전히 대체)

//첫 번째 인자는 선택자, 두 번째 인자는 업데이트할 값
//$set는 필드의 값을 새로운 값으로 대체할 때 사용
db.dogs.updateOne({ name: 'charlie' }, { $set: { age: 4 } });

//업데이트 성공
{ "acknowledged" : true, "matchedCount" : 0, "modifiedCount" : 0 }

//없는 특성을 추가하면 객체에 추가됨
db.dogs.updateOne({ name: 'charlie' }, { $set: { color: "brown" } })

db.dogs.updateMany({catFriendly:true},{$set:{isAvailable:false}})
//2건 모두 성공
{ "acknowledged" : true, "matchedCount" : 2, "modifiedCount" : 2 }

//$currentDate는 날짜 업데이트에 사용 true값이면 현재 날짜로 설정
db.dogs.updateOne({ name: 'charlie' }, { $currentDate: { lastChanged: true } })

{ "_id" : ObjectId("627bf7a4491e7e80fb01fa6f"), "name" : "Charlie", "age" : 3, "breed" : "corgi", "catFriendly" : true, "isAvailable" : false, "lastChanged" : ISODate("2022-05-11T18:16:47.596Z") }

157. Mongo 데이터베이스에서 삭제

deleteMany, deleteOne 사용

db.dogs.deleteMany({ isAvailable: false });

//삭제됨
{ "acknowledged" : true, "deletedCount" : 2 }

//다 삭제시킴
db.dogs.deleteMany({})

//$or, $lt  연산자를 사용하여 조건을 추가로 줄 수 있음

158. 기타 연산자

중첩된 특성을 찾으려면 따옴표를 쓰고 dot 구문을 써야한다.

db.dogs.find({ 'personality.catFriendly': true });

//gt 초과인 값
db.dogs.find({ age: { $gt: 8 } });
//gte 이상인 값
db.dogs.find({ age: { $gte: 8 } });
//lt 미만인 값
db.dogs.find({ age: { $lt: 8 } });
//lte 이하인 값
db.dogs.find({ age: { $lte: 8 } });
//in 배열 검색 (하나라도 충족하면 검색)
db.dogs.find({ breed: { $in: ['Mutt', 'Corgi'] } });
//ne 일치하지 않는 값
db.dogs.find({ breed: { $ne: 'Mutt' } });
//nin 배열에 없는 객체 검색
db.dogs.find({ breed: { $in: ['Mutt', 'Corgi'] } });

//$and,$not,$nor,$or 연산자도 있음

159. Mongoose란

Express 앱이나 Node 앱을 Mongo와 연결하는데 사용되는 드라이버이다.

ODM(Object Data Mapper) 즉, 객체 데이터 매퍼로 데이터나 문서를 JavaScript 객체로 매핑한다.

사전에 프리셋 스키마 정의하여 데이터가 레이아웃된 스키마를 따르도록 강제할 수 있음

복잡한 쿼리를 만들 수 있도록 도와줌

node-mongo연결

import mongoose from 'mongoose';
main()
    .then(() => {
        console.log('CONNECTED!');
    })
    .catch((err) => console.log(err));

async function main() {
    await mongoose.connect('mongodb://localhost:27017/movieApp');
}

// node RELP에서 .load할 경우엔 이렇게 연결해주어야 한다.
let { mongoose } = await import('mongoose');
main()
    .then(() => {
        console.log('CONNECTED!');
    })
    .catch((err) => console.log(err));

async function main() {
    await mongoose.connect('mongodb://localhost:27017/movieApp');
}

160. 스키마

JavaScript 파일에서 데이터를 사용하거나 접근하려면 각 데이터를 정의하는 모델을 만들어야한다. 이 정의서가 스키마이다.

//스키마 생성
const movieSchema = new Mongoose.Schema({
    title: String,
    year: Number,
    score: Number,
    rating: String,
});

//movie 클래스 만듬
const Movie = mongoose.model('Movie', movieSchema);

//db에는 저장되지 않은 상태 
const amadeus = new Movie({ title: 'Amadeus', year: 1986, score: 9.2, rating: 'R' });

//db에 저장
amadeus.save();
/*
mongo shell에서 저장됨을 확인
{ "_id" : ObjectId("627c5b2c569309a9c73b5602"), "title" : "Amadeus", "year" : 1986, "score" : 9.2, "rating" : "R", "__v" : 0 }
 */

161. 대량 삽입하기

const amadeus = new Movie({
  title: "Amadeus",
  year: 1986,
  score: 9.2,
  rating: "R",
});

//db에 저장
amadeus.save();
/*
mongo shell에서 저장됨을 확인
{ "_id" : ObjectId("627c5b2c569309a9c73b5602"), "title" : "Amadeus", "year" : 1986, "score" : 9.2, "rating" : "R", "__v" : 0 }
 */

//insertMany메소드는 save하지 않아도 데이터베이스에 저장된다.
Movie.insertMany([
  { title: "Amelie", year: 2001, score: 8.3, rating: "R" },
  { title: "Alien", year: 1979, score: 7.5, rating: "R" },
  { title: "The Iron Giant", year: 1986, score: 8.6, rating: "R" },
  { title: "Moonrise Kingdom", year: 2012, score: 7.3, rating: "PG-13" },
]);

/* 
mongo shell에서 저장된 것을 확인할 수 있음
> db.movies.find()
{ "_id" : ObjectId("627c57e59a049e1b03b869e4"), "title" : "Amelie", "year" : 2001, "score" : 8.3, "rating" : "R", "__v" : 0 }
{ "_id" : ObjectId("627c57e59a049e1b03b869e5"), "title" : "Alien", "year" : 1979, "score" : 7.5, "rating" : "R", "__v" : 0 }
{ "_id" : ObjectId("627c57e59a049e1b03b869e6"), "title" : "The Iron Giant", "year" : 1986, "score" : 8.6, "rating" : "R", "__v" : 0 }
{ "_id" : ObjectId("627c57e59a049e1b03b869e7"), "title" : "Moonrise Kingdom", "year" : 2012, "score" : 7.3, "rating" : "PG-13", "__v" : 0 }
{ "_id" : ObjectId("627c593d5b6ad45e3981881b"), "title" : "Amelie", "year" : 2001, "score" : 8.3, "rating" : "R", "__v" : 0 }
{ "_id" : ObjectId("627c593d5b6ad45e3981881c"), "title" : "Alien", "year" : 1979, "score" : 7.5, "rating" : "R", "__v" : 0 }
{ "_id" : ObjectId("627c593d5b6ad45e3981881d"), "title" : "The Iron Giant", "year" : 1986, "score" : 8.6, "rating" : "R", "__v" : 0 }
{ "_id" : ObjectId("627c593d5b6ad45e3981881e"), "title" : "Moonrise Kingdom", "year" : 2012, "score" : 7.3, "rating" : "PG-13", "__v" : 0 } 
*/

162. Mongoose로 찾기

const Movie = mongoose.model("Movie", movieSchema);

//node REPL에서
Movie.find({ rating: "R" }).then((data) => {
  console.log(data);
});

Movie.findById({ id: "59ws9i2o030" }).thein((data) => {
  console.log(data);
});

쿼리를 실행하는 방법은 2가지가 있다.

  • 1.콜백 함수를 넘기기
  • 2 .exec()으로 Promise같이 쓰기

exec 옵션

쿼리 인스턴스의 함수 중 콜백을 넘기지 않았을 때 Promise를 반환하는(정확히는 Promise-Like) 함수는 create(), save(), remove(), exec() 등 일부고 그 외엔 모두 자기 자신(쿼리 인스턴스)를 다시 반환하기 때문에 exec를 사용하여야 쿼리를 최종 실행시킬 수 있다.

const movieR = await Movie.findOne({ rating: "R" }).exec();

163. Mongoose로 업데이트하기

Movie.updateOne({ title: "Amadeus" }, { year: 1984 }).then((res) => {
  console.log(res);
});

/*  갱신된 데이터가 반환되지 않음 update 메서드의 방식이 이러함.
{
  acknowledged: true,
  modifiedCount: 1,
  upsertedId: null,
  upsertedCount: 0,
  matchedCount: 1
} */

//여러개를 업데이트할 경우
Movie.updateMany(
  { title: { $in: ["Amadeus", "Stand By Me"] } },
  { score: 10 }
).then((res) => {
  console.log(res);
});

findByIdUpdate, findOneAndUpdate

갱신을 실행한 뒤 갱신된 객체를 돌려줌

Movie.findOneAndUpdate(
  { title: "The Iron Giant" },
  { score: 8 },
  { new: true } //이 옵션을 줘야 갱신된 객체를 반환함
).then((data) => {
  console.log(data);
});

Movie.findByIdUpdate(
  { id: "5931l30oepp" },
  { score: 8 },
  { new: true } //이 옵션을 줘야 갱신된 객체를 반환함
).then((data) => {
  console.log(data);
});

164. Mongoose로 삭제하기

Movie.remove({ title: "Alien" }).then((res) => {
  console.log(res);
});
/* 
삭제된 객체를 반환하지 않음
{ acknowledged: true, deletedCount: 1 }
 */

Movie.deleteOne({ title: "Alien" }).then((res) => {
  console.log(res);
});

Movie.deleteMany({ year: { $gte: 2000 } }).then((res) => {
  console.log(res);
});

findOneAndDelte, findByIdAndDelete

삭제를 실행한 뒤 삭제된 객체 반환함

Movie.findOneAndUpdate({ title: "The Iron Giant" }).then((data) => {
  console.log(data);
});

165. 스키마 유효성 검사

const productSchema = new mongoose.Schema({
  //required:true를 넣어주면 특성의 디폴트가 설정됨
  name: { type: String, required: true }, //필수 특성
  price: { type: Number, required: true }, //필수 특성
});

const Product = mongoose.model("Product", productSchema);
const bike = new Product({ name: "Moutain Bike", price: 599 });
const keyboard = new Product({
  name: "developer Keyboadr",
  price: 599,
  color: "brown", //스키마에 없는 특성을 추가하였을 경우 무시한다.
});
bike
  .save()
  .then((data) => {
    console.log(data);
  })
  .catch((err) => {
    console.log(err); //type이 다르거나 required인 특성에 값이 없을 경우 에러로 빠짐
  });

});

165. 스키마 유효성 검사

const productSchema = new mongoose.Schema({
  //required:true를 넣어주면 특성의 디폴트가 설정됨
  name: { type: String, required: true }, //필수 특성
  price: { type: Number, required: true }, //필수 특성
});

const Product = mongoose.model("Product", productSchema);
const keyboard = new Product({
  name: "developer Keyboadr",
  price: 599,
  color: "brown", //스키마에 없는 특성을 추가하면 무시한다.
});

keyboard.save();
/* {
  name: 'developer Keyboadr',
  price: 599,
  _id: new ObjectId("627c6f687bc64c5aa7e5fc75"),
  __v: 0
} */

166. 스키마 추가 제약조건

default 기본값 설정
maxlength 최대 길이 제한
minlength 최소 길이 제한
max 최대 값 제한
min 최소 값 제한
[type] type 배열
enum 배열을 제공 후 해당 값이 배열에 있는지 검사
등등

const productSchema = new mongoose.Schema({
  //required:true를 넣어주면 특성의 디폴트가 설정됨
  name: { type: String, required: true, minlength: 0, maxlength: 10 }, //필수 특성
  price: { type: Number, required: true, min:0}, //필수 특성
  onSale: { type: boolean, default: false },
  categories:{[String]},
  size:{enum:[S,M,L,XL]},
  qty:{
      online:{
          tpye:Number,
          default :0
      },
      instore:{
          tpye:Number,
          default :0
      }
  }
});

169. 업데이트 유효성 검사

runValidators:true를 안주면 해당 객체가 처음에만 유효성 검사를 하고 다음부턴 하지 않기 때문에 계속 유효성 검사를 하라고 명시해야함

Product.findOneAndUpdate(
  { name: "Moutain Bike" },
  { price: -1 },
  { new: true, runValidators: true } // runValidators:true로 주면 유효성 검사 적용
)
  .then((data) => {
    console.log(data);
  })
  .catch((err) => {
    console.log(err); //price가 min:0으로 되어있기 때문에 에러남
  });

170. 유효성 검사 오류

유효성 오류시 커스텀 메시지를 설정할 수 있다.


const productSchema = new mongoose.Schema({
  name: { type: String, required: true, minlength: [0,too short], maxlength: 10 },
  price: { type: Number, required: true, min:[0,Price must be positive]},
  });

171. 인스터스 메서드

스키마에 .을 찍고 methods. 다음에 메서드명과 메서드 정의
이 메서드는 해당 스키마로 만들어진 클래스의 인스턴스에서 다 사용 가능

productSchema.methods.greet = function () {
  console.log("Hello!");
  console.log(${this.name})
};
productSchema.methods.toggleOnSale=function(){
    this.onSale=!this.OnSale;
    this.save();
}

productSchema.methods.addCategory = function(newCategory){
    this.Category.push(newCategory);
    this.save();
}

const Product = mongoose.model("Product", productSchema);

const keyboard = new Product({
  name: "developer Keyboadr",
  price: 599,
});

const findProduct = async ()=>{
    const foundProduct = await Product.findOne({name:"Moutain Bike"});
    await foundProduct.toggleOnSale();
    await foundProduct.addCategory("Outdoors");

}

172. 정적 메서드 추가

스키마명.statics.메서드명 = 정의


productSchema.statics.fireSale = function()=>{
    return this.updateMany({},{onSale:true,price:0})
}
find.fireSale().then(()=>{
    console.log("fireSale")
})

173. 가상 Mongoose

실제 데이터베이스 자체에는 존재하지 않는 스키마에 특성을 추가할 수 있게 해준다.

첫 번째 예시로 사용할 것은 first name과 last name이 있는
사람 모델 first name과 last name이 있는 사용자 모델이다.

fullName을 데이터베이스에 저장할 필요가 없으나 마치 fullName이 데이터베이스에 있는 것처럼 접근할 수 있는 특성이 있다면 좋을 것이다.

get, set으로 값을 가져오거나 설정할 수 있다.

const personSchema = new mongoose.Schema({
  first: String,
  last: String,
});
personSchema
  .virtual("fullName")
  .get(function () {
    return `${this.first} ${this.last}`;
  })
  .set(function (v) {
    this.first = v.substr(0, v.indexOf(" "));
    this.last = v.substr(v.indexOf(" ") + 1);
  });

const Person = mongoose.model("Person", personSchema);
const tammy = new Person({ first: "Tammy", last: "Chow" });
tammy.fullName; // Tammy Chow
tammy.fullName = Tammy Xios // tammy.first = Tammy tammy.last = Xios로 바뀐다.

174. Mongoose를 미들웨어로 정의

어떤 것이 삭제되거나 저장되기 직전이나 updateMany나 find 등 어떤 함수를 호출할 때도 코드를 실행할 수 있습니다

방법은 .pre와 .post를 추가하는 것이다.

personSchema.pre("save", asyncc function () {
    console.log("ABOUT TO SAVE")
});
personSchema.post("save", asyncc function () {
    console.log("JUST SAVED")
});

0개의 댓글