<<nodeJS>>
크롬 V8 자바스크립트 엔진으로 빌드 된 자바스크립트 런타임
브라우저 밖에서 돌아가는 자바스크립트
Ryan은 자바스크립트를 브라우저에서 분리하여 nodeJS를 만듦
*런타임(runtime) : 프로그램이 실행되고 있을 때 존재하는 곳, 프로그래밍 언어가 구동되는 환경
(Javascipt는 web browser에서 작동하는 JavaScript가 있고 Node.js에서 구동되는 JavaScript가 있다)
<<npm>>
자바스크립트 언어를 위한 패키지 매니저
nodeJS와 상호작용을 할 수 있게 해줌 (yarn과 같은 기능)
대부분 npm 명령어 사용 아주 가끔 node 명령어
유명한 사람들이 만든 패키지들을 다운받고 사용할 수 있음.
ex) express 패키지 - 한달에 7000만명 다운로드
npm 덕분에 npm install express로 사용가능
<<json>>
프로그래머가 파일에 정보를 저장하기 위해 만든 방식 중 하나
파일에 정보를 입력하는 방식, { key : value }
nodeJS인 경우 파일이름 = package.json이어야만 함.
<<start>>
폴더생성 - git init - git repository - git remote add - npm init
<<package.json>>
<scripts>
"scripts": {
"start": "node index.js"
}
Tml > npm run start
-> node index.js가 실행됨
나중에는 서버를 시작하는 script, CSS를 압축하는 script, 웹사이트를 빌드하고 서버에 배포하는 script등을 만듦
*package.json이 있는 폴더 내에서 terminal명령어 실행시켜야 함.
*babel - "dev": babel-node index.js -> babelnode로 index.js 실행
ex) const express = require("express"); -> import express from "express";
Tml > npm run dev
-> 뭔가 바뀌면 항상 저장하고 계속 실행시켜줘야함 -> nodemon사용
*nodemon - 파일이 수정되는 걸 감시해주는 패키지, 수정되면 알아서 재시작
"nodemon --exec babel-node index.js"
index.js를 수정하고 저장하면 알아서 서버를 재시작
<<npm>>
<npm install> (npm 웹사이트에 있는 그대로 복사 붙여넣기!)
Tml > npm i(nstall) express
package-lock.json파일과 node_modules폴더 자동 생성됨
package-lock.json - 패키지들을 안전하게 관리
node_modules - npm으로 설치한 모든 패키지가 저장되어 있음
<express>
node_modules > express > lib > package.json
"dependencies" : {}- express가 작동되려면 필요한 패키지들
npm install express시 같이 다운받아짐
npm은 package.json의 dependencies를 보고 그 안에 있는 모듈들을 알아서 설치함
-> package.json이 중요한 이유!
<dependencies>
프로젝트를 실행하기 위해 필요(가솔린)
<devDependencies>
개발자에게 필요한 dependencies(운전을 잘하기 위한 음악)
<<babel>>
babel - JavaScript compiler, 작성한 최신 자바스크립트를 컴파일
nodeJS가 이해하지못하는 최신 JavaScript가 있음
선택지 1. nodeJS가 이해하는 자바스크립트만 사용
선택지 2. babel을 사용!
babel이 알아서 babel.config.json이라는 파일의 내용을 들여다 봄
<preset> - babel을 위한 거대한 플러그인
preset-env : smart preset, 최신 자바스크립트 구문 사용가능
<express>
정의 :웹 및 모바일 애플리케이션을 위한 일련의 강력한 기능을 제공하는 간결하고 유연한 Node.js 프레임워크
사용 :import express from "express"; node_modules의 express에서 가져옴
const app = express(); express로 application(server)생성
<server>
항상 켜져있는 컴퓨터 같은 것, 인터넷에 연결 돼 있음
request를 항상 listening함.
callback : 서버가 시작될 때 작동하는 함수
callback전에 어떤 port를 listening할지 얘기해 줘야 함
보통 서버를 시작했다면 localhost:portNum를 통해서 접속할 수 있음
<Cannot GET />
/ : 서버의 root, 혹은 첫 페이지(home)
GET : HTTP method, 페이지 갖다줘, request중 하나
*http: 우리 혹은 서버가 서버와 소통하는 방법
*http request : 웹사이트에 접속하고 서버에 정보를 보내는 방법
*request : 유저가 뭔가를 요청하거나, 보내거나, 네게 무슨 행동을 하는 것
app.get("/", callback) : 누군가 root page(/)로 get request를 보낸다면 서버는 callback함수를 작동
'cannot GET /' error 해결
*사용자가 /login page같은 page를 원할 때 브라우저가 get request를 보냄
<handleHome>
const handleHome = (req, res) => console.log("asdf");
이 req와 res는 express로 부터 받은 것 따라서 get request를 받으면 express는
handleHome({request받은 것}, {respond할 것})을 넣어줌
<request>
브라우저가 보내는 요청
request object는 파일, search query, username, password를 포함 할 때도 있다
<response>
request를 받으면 response return해야 함
res.end();로 response 종료
res.send("");로 페이지에 "" print ""안에는 html,json도 포함가능
<middleware>
request와 response사이에 존재, 모든 middleware는 handler
handler = controller
controller는 req, res말고 next라는 argument를 가지고 있다.
middleware는 next();를 포함하는 controller
<next();> - 다음 함수를 호출
<app.use(globalMiddleware);> : global middleware를 생성
모든 route에서 이 globalMiddleware를 가짐
* express는 위에서 아래로 순서대로 실행하기 때문에 순서 중요
ex) privateMiddleware
const privateMiddleware = (req, res, next) => {
const url = req.url;
if(url === "/protected"){
return res.send("<h1>Not Allowed</h1>");
}
console.log("Allowed, you may continue.");
next();
};
<Setup Recap>
package.json = node.js 관련정보를 넣은 text
npm이 어떤 행동을 할 수 있게 해주는 것들이 있음
"scripts": { "something": "" } - Tml : npm run something(aliasing)
"dependecies": { "express": "^4.17.1"} - node_modules에 자동으로 추가
"devDependencies": { } - node_modules로 가는 것 동일, 차이점 : 개발자가 개발할 때 필요한 것들
ex) nodemon - 파일을 보고 있다가 변화가 생기면 command를 재시작
ex) @babel - server.js를 node가 아닌 babel-node로 돌림 -> 최신 언어 사용가능
babel.config.json파일을 만들어 babel에 추가하고싶은 plugin(preset)을 추가
<Server Recap>
Server : 항상 켜져있고 인터넷에 연결 돼 있으면서 request를 listening하고 있는 컴퓨터
request : 우리가 서버에 요청하는 것(브라우저를 통해 웹사이트와 상호작용하는 것)
import express from "node_modules/express";
const app = express(); express application을 사용할 수 있게 변수로 설정
port : 서버의 창문, request를 보내는 곳
app.listen(4000, handleListening); : 창문(port)을 여는 곳
4000 port : backend 관습, 거의 비어있음
cannot get / : server가 브라우저의 request를 listening하고 있는데 대답해 줄 사람이 없음
http method : post, delete, get
ex) get : 사용자가 요청을 하면 서버가 보내줌 우리가 가는 것이 아님
express.js에서 하는 방법은 app.get을 사용
argument : req, res, handdler
<Controller Recap>
모든 controller는 req와 res가 있다. (express로부터 주어짐, arg 순서 중요)
respond는 여러 method가 있다. ex)res.send(), res.end(), res.cookies(), res.json()
res.end(); : finish connection
res.send(); : text,html,json을 보낼 수 있음(화면에 띄울 수 있음)
request도 여러 method ex)req.ip(), req.cookies(), req.secure()
req.method(); : get, post, put등의 http method 정보를 줌
req.path(); : url
<morgan> : middleware를 return함
기존 middleware보다 정교함, next()를 포함함
"dev" : method, path, status code, respond time
"combined" : time, method, http version, status code, browser, os..
"tiny"
사용 : import logger from "morgan";
app.use(logger("dev"));
// const loggermid = (req, res, next) => {
// console.log(`METHOD ${req.method} ${req.path}`);
// next();
// };
// app.use(loggermid);
// const home = (req, res) => {
// return res.send("hello");
// };
// app.get("/", home);
<<router>> - 컨트롤러와 URL의 관리를 쉽게 해줌(mini-application)
작업중인 주제를 기반으로 URL을 그룹화함
글로벌라우터, 유저라우터, 비디오라우터
*import 하기전엔 export해야함
default export로 변수를 export
import한 변수는 export한 것을 불러왔기 때문에 똑같은 이름으로 안써도 무방
반드시 하나만 default export할 수 있음
*라우터와 컨트롤러를 섞어서 쓰는것은 좋지않음
컨트롤러는 함수, 라우터는 그것을 이용하는 입장이기 때문
*default로 export하면 이름을 바꿀 수 있었지만
export const로 하면 실제 이름을 그대로 써야함 (오브젝트를 그대로 들고 옴)
<:id>
parameter - url안에 변수를 바꿔줄 수 있음
모든 영상마다 매번 라우터를 만드는 것을 방지
ex) /videos/123142/edit - express는 :id에 123142를 넣음
사용 : req.params
<정규표현식> - express에선 정규표현식 사용가능
/ab*cd - abasdfsfacd, abrandomcd, abcd
/ab+cd - abbbcd, abbbbbbbbcd
/ab?cd - b가 옵션
/ab(cd)?e - (cd)가 옵션
/(nico\w+)/g - nico로 시작하는 단어들
/(\d+)/g - 숫자만
*JavaScript는 \ 두번
*템플릿 : 뷰 엔진으로 표시하는 웹페이지
<<pug>> - 템플릿 엔진
굉장히 편리함, html에서 <>을 생략가능(깔끔한 html), html에 자바스크립트를 포함가능
<최대장점> : partials를 이용하여 반복할 필요가 없음
<설정방법> : npm install pug -> app.set("view engine", "pug")
:(Express에게 html helper(view engine)로 pug를 쓰겠다고 말함)
views폴더안에 something.pug로 파일생성(express는 views폴더 안의 view폴더를 봄)
<작성방법>
모든 건 소문자로 작성, 속성이 있으면 괄호안에 작성
모든 건 부모속성보다 안쪽에 있어야 함(tab)
#{Javascript code}으로 변수를 사용하거나 pug에서 자바스크립트를 사용 가능
<적용방법> : (pug에게 파일을 보내면 pug가 렌더링해서 normal html로 변환함)
: res.render("something") #something은 view파일
<partials>
footer example) view/partials/footer.pug 생성 -> footer내용 작성
-> include partials/footer.pug
<상속>
html의 base를 만들 수 있음
base.pug에 반복되는 html을 넣고 반복할 pug에 extends base.pug
override하려면 block을 사용
block : 내용을 넣을 수 있는 템플릿의 창, 부모 pug에 block st로 작성
content block에 무언가 넣으려면 자식 pug에 block st \n html(st는 서로 이름 같아야 함)
<변수>
#{variable}로 변수 줄 수 있음
변수 선언은 rendering 하는 곳에서 res.render("pugfile", {변수})
=variable으로도 변수를 줄 수 있음
class, href, id..등은 `${}`을 사용하여야 함
<conditional>
만약 fakeUser가 들어왔을 때 logout link만 보이도록 하려면?
if fakeUser.loggedIn, else if fakeUser.loggedIn, else
<iteration>
만약 array를 가져온다면 변수로 each variable in variables로 쓸 수 있음
else도 쓸 수 있음
*mixin : data를 받을 수 있는 partial
작성: mixin video(mixin가 받을 data)
반복될 문장들
적용: include pugfile
+video(each문의 element)
어떤 페이지에서도 reuse가능
<ternary operator>
#{video.views == 1 ? "view" : "views" }
*→ : 오른쪽 화살표
*cwd = current working directory, process.cwd() = return 프로세스 현재 작업 dir
*현재 server를 process하는 것은 package.json(node.js를 시동), 그렇기때문에 process.cwd는 package.json이 있는 dir
*app.set("views", process.cwd() + "/directory/views") 로 임의로 views폴더 경로 설정 가능
<<MVP style>>
html을 예쁘게 만들어 줌
사용 : (in head)link(rel="stylesheet" href="https://unpkg.com/mvp.css")
* ES6 문법
const id = req.params.id;
=> const { id } = req.params;
(array: array)
=> (array)
<<Post>> : backend로 보내는 방법
form(method="POST")
cf) get을 사용하는 form
form(action="/save-changes", method="GET")
구글에서 뭔가 검색할 때 검색어가 주소창에 포함되어 있음
*videoRouter.get("url", getController), videoRouter.post("url", postController)
=>videoRouter.route("url").get(getController).post(postController)
*express.urlencoded : post controller가 form의 body를 이해할 수 있도록 하는 middleware
app.use(express.urlencoded({ extended: true }));
express application이 form의 value들을 이해할 수 있도록 하고 쓸 수 있는
자바스크립트 형식으로 변형시켜 줌
<<DataBase>>
<mongoDB>
document-based(json-like document) : object로 생각 함
document를 검색할 수 있고, 만들 수 있고, 삭제할 수도 있고, 수정, 업데이트 가능
데이터베이스를 클라우드에 배치하고, 모니터링가능
<<mongoose>> 자바스크립트에서 mongodb상호작용, nodeJS와 mongoDB를 이어주는 다리역할
<install>
>>npm i mongoose
db.js에 import mongoose from "mongoose";
mongoose.connect("url/your database name");
Event) *mongoose.connection.on("error", function) : 여러번 발생 가능
*mongoose.connection.once("open", function2) : 단 한번만 실행
<schema>
mongoose에게 데이터가 어떻게 생겼는지 알려줘야 함(model을 만드는 이유)
shape = schema
const videoSchema = new mongoose.Schema({
title: {type: String, required: true},
createdAt: Date,
})
const Video = mongoose.model("Video", videoSchema);
export default Video; 그리고 server.js에서 import
<database loading>
Video.find({search terms},callback)
search terms가 비어있으면 모든 형식을 찾는다는 것을 뜻함
data가 loading된 후에 진행하여야 하므로 기다려야 함.
그 방법에는 callback과 promise가 있다.
callback : 무언가 발생하고 난 다음 호출되는 function, err와 docs라는 signature를 가짐
promise : const home = async(req,res){ const videos = await Video.find({});}
error-> try{}catch{}, 장점 : 보다 직관적임
*await : database를 기다림
<post>
const video = new Video({}) //models에서 Video schema를 import
or await Video.create({}) - save()안해도 됨
만약 schema에 반하는 형식으로 post된다면 mongoose가 형변환 시키거나 제외시켜버림
video.save()로 database에 저장, promise가 내장돼있기 때문에 async와 await로 기다려야함
try{}catch{}문으로 에러검출
<CRUD>
Create, Read, Update, Delete
<return & render>
함수에서 render가 여러번 있으면 안됨
return : 함수를 종료시켜주는 기능, return + render로 실수 방지 가능
<schema>
기능 : uppercase, lowercase, trim(whitespace제거), default, required, minLength
, maxLength(보안차원에서 form에도 설정해야 함)
unique: 중복 x
*db에 저장된 데이터를 업데이트 하는 방법
1. 각 데이터의 내용들을 하나하나 바꾸고 save()하는 방법
2. mongoose의 함수를 쓰는 방법(await Video.findByIdAndUpdate(id,{}))
*update 이전에 확인 할 것들 - middleware(hooks)
model이 생성되기 전에 만들어야 함
form : videoSchema.pre("save", async function (){})
<static>
schema에 대한 함수를 임의로 만들 수 있음
Video만 import하면 static함수는 import 없이 사용 가능
form : videoSchema.static("formatHashtags", function(){})
<mongoDB의 filter>
find().sort({ createdAt : "desc" }) - 내림차순, "asc" - 오름차순
query에 정규표현식을 포함시킬 수 있음
videos = await Video.find({
title: {
$regex: new RegExp(keyword, "i")
}
})
* i - 대문자, 소문자 구분x
* keyword - keyword를 포함하는 단어
* `^${keyword}` - keyword로 시작하는 단어
* `${keyword}$` - keyword로 끝나는 단어
<<User>>
*console창에 DeprecationWarning : 만든지 오래된 기능을 쓰고 있으니 업데이트 요망
<hash>
database에 password가 노출되면 안되기 때문에 hash기능을 사용함
*deterministic function(결정적 함수) : 입력이 같으면 같은 내용을 출력함(hashing)
*bcrypt : rainbow table공격을 막아줌
설치 : npm i bcrypt
사용 : bcrypt.hash(password, saltRounds, callback)
*saltRounds : hash를 반복하는 횟수
<$or operator>
{ $or: [ {조건1}, {조건2}, ... {조건n} ] }
각 조건이 true일 때 실행할 수 있도록 할 수 있음
ex)
const usernameExists = await User.exists({
$or: [{ username: username }, { email }],
}); - username 또는 email이 존재하면 usernameExists = true
*res.status(400).render("",) 으로 브라우저에 상태 코드를 보낼 수 있음
<login>
입력한 password를 해쉬한 값으로 비교 해야함(입력값이 같으면 결과가 같으므로)
*compare함수
bcrypt.compare(password, hash).then(function(result){
}) 혹은
async function checkUser(username, password){
const match = await bcrypt.compare(password, user.passwordHash);
if(match){ //login }
}
<유저를 기억하는 방법>
유저에게 쿠키를 보냄
*세션 : 백엔드가 자신과 브라우저 간에 어떤 활동을 했는지 기억하는 것(약 2주 뒤에 사라짐)
*express-session : express에서 세션을 처리하게 하는 미들웨어
사이트로 들어오는 모두를 기억하게 됨(로그인하지 않아도)
req.header에 cookie가 있음
백엔드가 누가 요청했는지 다 기억하고 있음
백엔드가 요청을 보내는 유저한테 id(세션)를 보냄
브라우저의 쿠키 안에 id가 있음
서버가 재시작될 때마다 메모리에 있는 세션이 지워짐(mongoDB로 기억할 수 있음)
<백엔드가 쿠키로 브라우저를 구분하는 방법>
백엔드의 메모리에 세션을 저장할 수 있는 DB가 생김
백엔드의 각 세션들은 id를 가지고 있음, 이 id를 브라우저가 요청 보낼 때마다 브라우저에게 보냄
=>어떤 세션이 각 브라우저와 일치하는지 알 수 있음
*
세션id를 가지고 있으면 세션 object에 정보 추가 가능
서버가 브라우저한테 세션 id를 주고 브라우저가 요청을 보낼때마다
쿠키에서 세션 id를 가져와 보내줌 그러면 서버가 그 세션 id를 읽고
우리가 누군지 알 수있음
브라우저가 서버의 모든 URL을 방문할 때마다 브라우저가 받았던 id를 요청에 담아 보냄
정리 :
내가 브라우저에서 웹사이트를 방문할 때마다 세션 미들웨어가 있으면
express가 알아서 그 브라우저를 위한 세션 id를 만들고 브라우저에게 보내줌
그러면 브라우저가 쿠키에 그 세션 id를 저장하고
express에서도 그 세션을 세션 DB에 저장함
그러면 브라우저한테 보내서 쿠키에 저장한 세션 id를
브라우저가 localhost:4000의 모든 url에 요청을 보낼 때마다
세션 id를 요청과 함께 보냄
그러면 백엔드에서 어떤 유저가 어떤 브라우저에서 요청을 보냈는지 알 수 있음
<login>
user controller의 req.session object에 정보 추가
이 정보를 template과 공유하려면 res.locals
res의 locals은 template가 접근할 수 있음 #{}
전역변수이기 때문에 모든 template가 사용 가능
localMiddleware를 session middleware보다 나중에 작성해야 작동
<recap login>
express-session : middleware, 브라우저와 backend가 상호작용할 때 마다
브라우저에게 쿠키를 전송
쿠키 : 백엔드가 브라우저에게 주는 정보, 브라우저가 요청을 보낼 때 쿠키를 덧붙여 보냄
쿠키에는 많은 정보를 넣을 수 있는데 session ID를 넣을 수 있음
session store : 우리가 session을 저장하는 곳, 서버를 재시작할 때마다 초기화
<connect-mongo>
session을 MongoDB에 저장
사용 : import MongoStore from "connect-mongo"
session({ stroe: MongoStore.create({mongoUrl: "mongodb://127.0.0.1:27017/wetube",}),})
메모리가 아닌 db에 저장되어있으므로 서버가 재시작 되어도 로그인 되어있음
mongoUrl은 배포시 공개하면 안됨
<Uninitialized Session>
*모든 사용자에 대해 세션을 만들고 쿠키를 저장하는 것은 좋은 생각이 아님
로그인한 사용자의 세션만 저장하는 게 좋음
saveUninitialized: false : 세션이 새로 만들어지고 수정된 적이 없을 때
Uninitialized
세션이 수정되는 곳 : userController의 req.session.~~
<cookie property>
secret : 쿠키에 sign할 때 사용하는 string
우리 backend가 쿠키를 줬다는 걸 보여주기 위해 sign
Domain : 이 쿠키를 만든 backend가 누구인지 알려줌, 브라우저는 Domain에 따라서
쿠키를 저장하도록 되어있음
Expires : session cookie 만료 날짜, 적지않으면 session cookie는 사용자가 닫으면 끝남
Max-Age : 언제 쿠키가 만료되는지 알려줌
<.env>
환경변수, env 파일에 추가하는 모든 건 대문자로
사용 : process.env.~~
<dotenv>
require("dotenv").config();
최대한 먼저 선언(가능한 빨리 env를 load해야하기 때문)
하지만 require하면 번거롭게 사용하는 모든 파일에 require 해야 함
import "dotenv/config"; 로 바꿔사용
<깃허브의 login flow>
1. 사용자를 깃허브로 보냄, 정보를 공유하는 것을 승인
2. 깃허브는 사용자를 token과 함께 우리의 웹페이지로 돌려보냄
3. 그러면 우리가 그 token으로 사용자의 정보를 받아옴, token은 빠르게 만료
<1. OAuth>
github.com/settings/applications 앱 만들기
href="https://github.com/login/oauth/authorize?client_id=앱의
client_id&scope=user:email" parameter를 보내줘야 함
github의 public data보다 많은 정보를 얻으려면
scope에 전송, 사용자에 대해 어디까지 알 수 있는지 적으면 됨
*scope : docs/github.com/en/developers/apps/scopes-for-auth-apps
우리가 필요한 것 : user와 user email
&scope=read:user user:email
allow_signup : github 계정이 있는 사람만 혹은 x
url을 각각 써주게 되면 번거로우므로 Controller에 url생성
*URLSearchParams : utility,
URLSearchParams(config).toString으로 모두 이은 url 생성가능
<2. >
authorized 누르면 app의 authorized url으로 code와 함께 이동
code는 10분 뒤 만료
Github 에서 받은 토큰을 Access 토큰으로 바꿔야 함
POST https://github.com/login/oauth/access_token
*backend에서 fetch를 쓰는 법 : node-fetch를 설치 후 import
<3. >
access_token을 가지고 Github API를 사용해 user의 정보를 알 수 있음
access_token은
fetch(https://github.com/login/oauth/access_token?id+secret+code,
{method:"POST", headers: {Accept:"application/json"}, },}).json으로
구할 수 있음
access_token의 형태
{
access_token: 'asd_fsafdffdfsfdsfs',
token_type: 'bearer',
scope: 'read:user'
}
구한 access_token을 깃헙에 GET으로 보내야 함
깃헙의 user public정보는 await fetch("https://api.github.com/user",
{headers: {Authorization: `token ${access_token}`,},})).json();으로
구할 수 있음
<로그인규칙>
*소셜로그인 시 데이터베이스상에 똑같은 email이 있을 때 그것으로 로그인 시켜 줌
const existingUser = await User.findOne({ email: emailObj.email });
req.session.loggedIn = true;
req.session.user = existingUser;
<logout>
req.session.destroy();
return res.redirect("/");
<github login recap>
startGithubLogin - baseUrl과 config를 이어 finalUrl을 만든 후 User를 그곳으로 보냄
깃헙 사이트에서 동의하면 우리 웹사이트로 돌아옴(/github/finish라는 Url으로) 그것의 controller
finishGithubLogin - url에 ?code=xxx가 붙어서 들어옴, baseUrl+config = finalUrl
모든것이 올바르다면 github는 우리에게 access_token을 줌
apiUrl/user로 access_token을 보내면 user데이터를 받을 수 있음
apiUrl/user/emails로 access_token을 보내면 useremail데이터를 받을 수 있음
그 email들에서 primary이고 verified인 이메일을 찾은 후 로그인
<middleware +>
로그인하지 않은 사용자가 edit페이지로 들어왔을 때 protect
로그인한 사용자가 다시 login페이지로 들어왔을 때 protect
로그인한 사용자만 video upload하게 protect
적용 : controller에서 controller부르기 전에 middleware를 삽입
.route로 만든 controller는 all(middleware).get().post()
<edit profile>
db의 user의 정보를 업데이트하고 session에서도 업데이트 해야 함
1. 직접 작성
...req.session.user : 기존에 req.session.user에 있는 내용을 전달
2. updatedUser를 새로 만들고 new: true를 줌
const updatedUser = await User.findByIdAndUpdate(_id, {update contents},
{new: true})
이미 있는 내용으로 바꿀 때 할 수 없다고 말해줘야 함
<password 변경>
*href="link" /없이 link를 작성할 경우 상대경로를 사용
"link" === "/previouslocation/link"
github로 로그인했을 때 비밀번호가 없음
1. home으로 redirect
2. 비밀번호가 있어야 변경가능이라고 알림
3. 깃허브로 로그인한 사람들에겐 안보이게
pre save middleware가 password를 hash해 줌- create나 save시 hash됨
session의 password또한 update 해야 함
또는 DB에서 가장 최근의 password를 가져오면 session을 건들필요 없음
<<upload file>>
<multer> : file을 업로드할 수 있도록 도와줌
설치 : $npm i multer
사용조건 : form을 multipart form으로 만듬
form(enctype="multipart/form-data")
파일을 백엔드로 보내기 위한 encoding type
사용 : 1. middleware 설정
uploadFiles = multer({ dest: "uploads/"})
upload한 파일들의 경로를 설정
2. 경로의 폴더 생성
3. middleware router에 추가
.post(uploadFiles.single("avatar"), postEdit);
*single : file을 하나만 받아옴, avatar = input name
*multer가 input으로 avatar 파일을 받아 uploads폴더에 저장한 후 그 파일 정보를 postEdit에 전달
req.file에 파일이 있음
*db에는 절대 file을 저장하면 안됨, 폴더에 파일을 저장후 그 위치만 db에 저장
*static files serving 활성화 : 폴더를 브라우저에게 노출시킴
app.use("/uploads", express.static("uploads"))
-"/url"에 폴더 "uploads"를 노출
<video upload>
multer에 file 크기 제한
multer({limits: { fileSize: 10000000,},});
*ES6 문법
const fileUrl = req.file.path;
=> const { path: fileUrl } = req.file;
=== const {path} = req.file;
const fileUrl = path;
<User profile>
1. video에 owner(id)를 추가,
owner는 objectId, video Schema에서 owner를 설정할 때
type:mongoose.Schema.Types.ObjectId
ref:"model name" - mongoose에게 owner에 어떤 model의 objectId를 저장하겠다고 알림
upload video부분에서 owner: req.session.user._id 추가
*populate: 문서의 경로를 다른 컬렉션의 실제 문서로 자동으로 바꾸는 방법
const owner = await User.findById(video.owner)를 추가로 쓸 필요없이
const video = await Video.findById(id).populate("owner");로
schema에서 ref="model"한 부분의 내용을 id값에서 실제값으로 채워줌
video의 모든 값들을 owner에 추가
2. user에는 소유한 video(id) list 추가
한 user가 여러 video를 소유할 수 있으므로 user schema의 videos는 array
videos: [{ type: mongoose.Schema.Types.ObjectId, ref: "Video" }],
upload video부분에서 const user = await User.findById(_id);
user.videos.push(newVideo._id);
user.save();
user controller에서 .populate("videos");추가 => user에 videos : {,,,}가 추가됨
*video가 push되고 save될 때마다 password가 hash되는 bug발생
if(this.isModified("password")){}
<<frontend>>
<Webpack> : backend의 babel처럼 브라우저가 이해할 수 있도록 css로 바꿔주는 역할
.scss -> css
cf) Gulp - Webpack의 대체제 중 하나, Webpack만큼 유용친 않음
Webpack을 직접 사용할 일은 거의 없음, react나 react native등 대부분의 프레임 워크에 내장
<webpack.config.js>
module.exports = {
entry: "",// src code, 처리하고 싶은 코드
output: {
filename: "",
path: "절대경로", //저장될 경로
}
}
<rules>
각각의 파일 종류에 따라 어떤 전환을 할 건지 결정
1. js를 bable로
* mode : 개발중에는 코드를 압축하면 안되므로 코드가 완성본인지 아닌지 알려줘야함
production - 완성본
development - 진행중
client폴더(webpack 처리 전)를 작업하면 browser가 assets폴더(webpack 처리 후) 내용을 읽음
하지만 브라우저가 assets폴더의 존재를 모르기 때문에 알려줘야함
*app.use("/assets", express.static("assets")) // 전자 : url경로설정, 후자 : 폴더경로
: express한테 사람들이 이 폴더 안에 있는 파일들을 볼 수 있게 하라고 요청하는 것
*pug와 assets폴더 연결
script(src="/asset/js/main.js")
2. scss(sassy css)를 css로
세가지의 loader
1) scss를 일반 css로 변형하는 loader(sass-loader)
2) 폰트같은걸 불러올 때 css에 굉장히 유용하게 쓰이는 loader(css-loader)
3) 변환한 css를 웹사이트에 적용할 loader(style-loader)
역순으로 합침
use:["style-loader", "css-loader", "sass-loader"]
style-loader대신 webpack plugin을 사용 <- js파일에서 css를 넣고 싶지 않기 때문
분리된css파일을 사용하기 위해 MiniCssExtractPlugin!
<정리>
client파일이 webpack에 의해 로딩되고 assets폴더에 변환된 js, css파일이
저장되고 base.pug가 그 파일을 읽으면서 브라우저가 이해할 수 있게 됨
*watch: true - run dev assets 매번 할 필요 x
back-end까지 재시작 됨
nodemon.json에서 ignore해줌
*clean : output folder를 build(재시작)전 지워 줌
**중요! 두가지 콘솔을 이용해야함
1. backend 2. watch client파일
<css작업>
client > scss > components - header, footers..
client > scss > screens - home, search...
<<player>>
사전 작업 : webpack 의 entry를 object로 만들어 video player만의 js를
만들고 둘을 변환하도록 만듬
output의 filename이 중복되므로 "[name].js"로 webpack의 기능을 활용해
entry의 이름으로 두개의 파일을 만들도록 만듬
*주석처리
// : frontend에서도 보임
//- : 보이지 않음
*input(type="range")의 eventlistener "input"은 실시간변화, "change"는 마지막변화
<loadedmetadeta> (event)
metadate : video의 움직이는 이미지를 제외한 모든 엑스트라들
<timeupdate> : video의 시간이 변화될 때마다 발생
* Math.ceil() : 올림, Math.floor() : 버림
video에서만 timeupdate라는 event사용 가능
<format time>
new Date(0) - zerotime
zerotime : 1970년 1월 1일 00시 00분(영국)
new Date(29*1000).toISOString().substr(from, length)
우리가 원하는 부분만 추출
<fullscreen>
document.fullscreenElement : 풀스크린인 element추출
<cursor를 넣으면 contoller>
event - mousemove, mouseleave
*id = setTimeout(function, time) : time밀리초 후 function이 불려짐\
id는 setTimeout의 하나뿐인 id
*clearTimeout(id)로 초기화 가능
<cursor멈추면 controller x>
움직일 때 마다 timeout을 설정하고 움직임이 있으면 timeout을 clear함
<<API view>> : rendering 하지 않는 view
*요즘 시대에는 백엔드에서 템플릿을 렌더링하지 않음
API : frontend와 backend가 서버를 통해 통신하는 방법
interactivity: url이 바뀌지 않아도 페이지가 변화하는 것
*url을 바꾸는 것이 백엔드에게 강제로 무언가를 하게 하는 유일한 방법
event로 backend에 요청 : fetch("id") - id를 얻을 수 없음
<data attribute> : HTML element에 데이터를 저장할 수 있음
controller1({}) -> pug(dataset) -> controller2(fetch)
(data-videoId=video._id)
fetch("url", {method:"POST"})
상태코드를 보내야 함 : res.sendStatus(400)
<<Recorder>>
MediaDevices.getUserMedia(constraints) : navigator에서 카메라와 오디오를 가져다 줌
실행 시 시간이 걸리므로 promise나 await를 사용해야 함
*await를 사용하려면 regeneratorRuntime을 설치
constraint : { audio: true, video: true}
preview.srcObject = stream;
*srcObject : video가 가질 수 있는 무언가
<MediaRecorder>
const recorder = new MediaRecorder(stream);
recorder.start();
recorder.stop()을 호출하면 dataavailable event가 발생됨,
e는 data property를 받은 BlobEvent
handler : ondataavailable
<URL.creatObjectURL(event.data)> : 브라우저 메모리에서만 가능한 URL을 만들어 줌
이 URL은 파일을 가리키고 있음
이 URL을 video.src에 저장하고 video.play를 하면 녹화되어 재생됨
*video.loop = true - 반복재생
<download>
a.href = videoFile;
a.download - a.href로 가는 것이 아니라 download를 시켜줌
a.click();
<<WebAssembly>>
*objectURL : url을 사용해서 파일을 가리키도록 브라우저가 만든 마법의 url
<FFmpeg> : video를 압축하거나 video format을 변환하거나 video에서 audiou를 추출하거나
video의 스크린샷을 찍는 등 많은 것들을 할 수 있음
ex) video를 네가지 화질의 버전으로 나누는 것
백엔드에서 실행해야만 함 - 비쌈 => webassembly를 사용
<webassembly> : 우리가 프론트엔드에서 매우 빠른 코드를 실행할 수 있게 해줌
- 실행 비용이 큰 프로그램들을 브라우저에서 실행할 수 있음
<ffmpeg.wasm> : 비디오를 변환하기 위해 사용자의 컴퓨터를 사용함(사용자의 브라우저)
import { createFFmpeg, fetchFile } from "@ffmpeg/ffmpeg";
const ffmpeg = createFFmpeg({});
await ffmpeg.load();
사용자가 다른 소프트웨어를 사용할 것이기 때문에 await
ffmpeg.FS("writeFile", "filename.webm", binary data function)
- ffmpeg의 가상의 세계에 파일을 생성, 즉 ffmpeg의 파일시스템에 영상의 정보를 작성
await ffmpeg.run("-i", "filename.webm", "-r", "60", "ouput.mp4")
-초당 60프레임 mp4 인코딩
const mp4File = ffmpeg.FS("readFile", "output.mp4"); - output.mp4라는 파일을 읽음
return 값은 Uint8Array
*Uint8Array : unsigned integer (양의 정수)
*Blob : binary 정보를 가지고 있는 파일, 만드려면 [binary data]를 줘야 함
binary data에 접근하려면 mp4File.buffer를 사용해야 함
*ArrayBuffer : 영상을 나타내는 bytes의 배열 (mp4File.buffer)
const mp4Blob = new Blob([mp4File.buffer], {type: "video/mp4"})
const mp4Url = URL.createObjectURL(mp4Blob)
<thumbnail>
await ffmpeg.rund("-i", "recording.webm", "-ss", "00:00:01", "-frames:v", "1", "thumbnail.jpg")
-ss : 영상의 특정시간대로 가게 해줌
-frames:v : 특정 프레임의 스크린샷을 찍어줌
const thumbFile = ffmpeg.FS("readFile", thumbnail.jpg")
const thumbBlob = new Blob([thumbFile.buffer], {tupe: "image/jpg"})
const thumbUrl = URL.creatObjectURL(thumbBlob)
*unlink - 브라우저 속도가 느려지는걸 방지
ffmpeg.FS("unlink", "recording.webm")
ffmpeg.FS("unlink", "output.mp4")
ffmpeg.FS("unlink", "thumbnail.jpg")
<두 개의 파일 multer upload>
fields([{name: },{name :}])
controller에서 req.files로 변경
<<Flash message>>
<express-flash>
설치 : npm i express-flash
app.use(flash()); - session에 연결해서 user에게 메세지를 남김
사용 : req.flash("error", "message");
messages라는 locals를 만들어줌
<<comment>>
default behavior를 막는 법 - event.preventDefault();
fetch(`/api/videos/${videoId}/comments`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify{text},
}); - 이런식으로 url변경없이 req.body를 보낼 수 있음
app.use(express.json()); - express에게 json을 보내고 있다는 것을 알려주는 middleware
fetch로 통해서 보내는 데이터는 JSON이므로 JSON.stringfy해줘야 함
즉 JS object를 string으로 바꿔서 인터넷으로 보낼 수 있도록 했고
string이 서버에 도착하면 JS object로 바꿔서 req.body.text를 쓸 수 있도록 함
1. fetch로 백엔드에 요청을 보낼 때에 URL과 더불어 이 세 가지를 덧붙여야 한다.
method: "POST",
headers: {},
body: {},
2. headers에는 이 요청의 세부 사항을 명시하며, body에는 실질적인 컨텐츠가 포함된다.
3. 따로 명시하지 않을 시에 모든 body의 컨텐츠는 Text File로서 전송되고 받아 인식된다.
4. 특히 body: { ... }, 이런 식으로 자바스크립트 오브젝트를 넘겨줄 시 외부에서 이 오브젝트는 [object Object]라는 의미 없는 문자열로 변환된다.
5. 오브젝트와 그 안의 세부 변수 목록들을 넘겨주고 싶을 시, JSON이라는 규약에 의거한 오브젝트 내의 모든 기록을 텍스트화하여 넘겨주어야 하는데, 이때 JSON.stringify({ ... }) 라는 편리한 자체 표준 함수를 사용하면 된다.
6. 덧붙여 headers 안에 "Content-Type": "application/json"이라는 명시를 해 주어 전송된 텍스트가 JSON파일임을 백엔드에 인식시켜 준다.
7. 백엔드에 (이를테면 express를 사용 중이라면) app.use(express.json()); 미들웨어를 추가해주어 자체 내에서 JSON.parse("..."); JSON파일을 다시 자바스크립트 오브젝트로 변환해주는 표준 함수로 요청 body 내의 컨텐츠를 디코딩하는 작업을 한다.
comment를 보낼 때 또한 cookie를 전송
*sendStatus() vs status()
sendStatus - status code를 보내고 request를 끊어버림
<comment를 db에 저장후 Video에 연결>
video.comments.push(comment._id);
video.save();
await video.find().populate("comments");
* window.location.reload(); 새로고침 but 부하
=> js로 fake comment를 추가
* fetch의 status code를 추출 :
const response = await fetch();
const status = response.status;
<controller에서 comment id를 response하기>
return res.json({ newCommentId: comment._id});
const response = await fetch();
const json = await response.json();
-> $ newCommentId: 123141;
<<build backend>>
<babel/cli>
설치 : npm i --save-dev @babel/cli
사용 : script { "build:server": "babel src -d bulid"},
-d : build할 directory
=> bulid 디렉토리에 init.js가 생김
nodemon은 파일을 실행하고 그 파일이 모든걸 실행함
babel은 src폴더를 빌드, 결과물은 build 폴더에 저장
=>build폴더에 src에 있는것들의 old code들이 있음
script { "start": node build/init.js" }
=>$ npm start로 시작가능(npm 기본 명령어)
-babel의 도움없이 node.js가 이 코드를 모두 이해가능
<Error fix>
*client폴더는 빌드안하게 하는 법
*build한 코드가 async와 await를 사용할 수 있도록 하는 법
*build폴더에 pug파일들이 없음
이유: views set에서 process.cwd()는 node를 실행한 위치, backend가 알아서 위치를 찾아감
같은 이유로 .env파일을 찾아갈 수 있음
<<build frontend>>
webpack의 mode를 development가 아닌 production로 바꾸고 싶음
"build:assets": "webpack --mode=production",
=>모든 코드가 한줄로 압축
development mode에서만 watch하도록
"dev:assets": "webpack --mode=development -w"
<Heroku>
create app
Heroku에 backend를 업로드하는 방법
1. Heroku Git
Heroku는 오직 git history만 본다 - commit을 안하면 Heroku가 볼 수 없음
- .gitignore에 있는 것들은 볼 수 없다(.env)
=>Heroku config vars
Heroku는 random으로 port번호를 줌(process.env.PORT)
2. Github
*Heroku는 배포를 하면 모든 프로젝트에 대해 새로운 서버를 만듦
=> 기존의 collection 파일들이 없어짐
<AWS>
S3 - 실질적인 미디어 저장소
IAM - S3의 권한 부여
<mongo DB atlas>
local에서 mongo의 역할
<Multer S3> : AWS에 업로드 함
npm i aws-sdk