풀스택 유투브 클론코딩 (노마드코더)

Y·2020년 7월 22일
2

자바스크립트

목록 보기
9/20

본 글은 nomadCoders.co에서 제공하는 "[풀스택] 유튜브 클론 코딩"을 바탕으로 작성되었습니다.
딱 이해한만큼 작성된거라 설명이 잘못됐을수도 있습니다.. 지적환영..

이 글을 작성한 이유는, 글로 한번 더 정리하면서 CRUD 시스템을 만드는 과정을 체득하기 위함이었습니다. 강의보면서 따라서 대충 슥슥 따라하기만하니까 내가 정말 사이트를 만들줄 아는가에 대한 의문이들어서.. 보고 따라하는건 누구나 하니까 제가 직접 만들줄 알기 위해선 복습이 꼭 필요하다 생각했습니다. 혹시 문제가 있다면 말씀해주시면 바로 내리겠습니다..

package.json 생성 , express 설치


프로젝트 폴더에서 npm init 명령어 실행

npm init

package.json 파일이 생성된다.

npm install express

다음과 같이 설치가 됬다면 준비 끝

GitHub 리포지토리 생성


깃헙에 업로드하기 전에 .gitignore 파일을 생성하여 깃헙에 올리지 않을 파일들을 설정해보자

  • package-lock.json 파일은 보안과 관련
  • node_modules 폴더는 수많은 모듈을 담고있고, 굳이 안올려도 협업시에 설치할 수 있음

깃헙에서 리포지토리를 생성하고, 밑의 명령어를 실행.
url부분엔 나의 리포지토리 주소가 들어가면됨!

git init
git remote add origin {url}
git add .
git commit -m "initial commit"
git push origin master

README.md 파일도 만들었는데, 그건 각자의 방식으로 작성하면 된다.

express


express를 이용해서 서버를 만들어보자.

  • express module을 불러오고, app변수로 express를 호출한다.
  • pp.listen(4000) 여기서 4000은 포트이다.

  • 터미널에 npm index.js 명령어를 실행
  • http://localhost/4000 로 접속했을때, 위와같은 화면이 나온다. 나의 첫 서버가 생성된 것이다.

매번 npm index.js를 하기 귀찮으므로 , 좀 더 쉽게 해보자.

  • package.jsonscripts 엔트리포인트를 추가하고, 위와 같이 작성
  • 이제 npm index.js 대신 , npm start 명령어만 치면 서버가 실행된다.

index.js 코드를 좀 더 가독성이 좋게 바꿔보자.

  • handleListening 콜백함수를 정의하고, PORT 변수에 포트를 저장
  • app4000번 포트를 listen하고, listen이 시작되면, handleListening 을 호출

서버 실행시, 정상적으로 작동함을 확인!

전체적인 흐름을 보자면,

  1. express 호출
  2. node_modules에서 import
  3. const app = express()를 통해 express 실행
    4.app4000번 포트를 listen
  4. handleListening 호출을 통해 서버 연결 확인

이상없이 됬다면, 다음 파트로 가보자!

Handling Routes


GET POST

일반적으로 우리가 웹사이트에가면 url을 적는다. 예를들면 www.naver.com 이라던지.. request를 보내고, 브라우저가 get 메소드를 실행하여 페이지를 읽어온다.
로그인 할때는 브라우저가 웹사이트에 post메소드를 통해 정보를 전달한다.

  • handleHome, handleProfile 함수를 정의하고, 요청이 오면 각각 'Hello from Home' , 'This is Profile page'를 응답(response)한다. Responseres.send 를 통해 이루어진다.
  • get 메소드를 통해 "/" , "/profile" 라우트를 받고, handleHome, handleProfile 함수를 호출하고, 각 함수들은 request object,res object를 호출한다.
  • "/" 라우트 (메인화면,홈) 에 대한 응답
  • "/profile" 라우트에 대한 응답

더 자세한 설명은
http://expressjs.com/en/guide/routing.html
영어 할줄알아야 이해가능

Babel nodemon


Babel


설치

npm install @babel/node @babel/preset-env @babel/core

Babel은 최신의 자바스크립트 문법을 사용할 수 있게 해주는 아주 좋은 라이브러리다!

  • 바벨을 설치하면, import 문을 사용할 수 있다.

nodemon


설치

npm install nodemon -D
  • -D 를 붙이는 이유는, 프로젝트에 꼭 필요한 패키지라기 보단, 코딩에 편리함을 주는 툴이라서 "devDependencies" 로 설정하기 위함.

nodemon은 javascript파일을 저장할때마다, 자동으로 서버를 재시작해주는 라이브러리다!

package.json 을 살펴보면, 이렇게나온다! 여기에 들어갈 것들은 개발자를 편리하게 만들어줄 것들이다.

적용


package.json 에서 서버 시작하는 명령어를 다음과 같이 설정해주면 최신문법을 이해하고(Babel), 자바스크립트를 저장할때마다 자동으로 서버를 재시작해주는(nodemon) 엄청난 편리성을 가져다 준다!

이렇게 뜨면 정상적으로 설치 완료!

middleware


Express 에서는 DRY를 어떻게 달성할 수 있을까? 바로 미들웨어(middleware)를 작성함으로써 가능하다. 미들웨어는 서버가 요청을 받아들이는 것과 응답을 전송하는 것 사이에서 작동하는 코드를 말한다. 즉, 두 HTTP 패킷 전송 사이에 일어나는 일들을 수행한다. Express 에서 미들웨어는 함수이다. 미들웨어는 들어온 요청을 검사하거나, 어떠한 로직을 실행하거나, 추가적인 정보를 응답에 붙이고 상태 코드를 수정한 후 유저에게 전달하거나, 또는 단순히 다른 미들웨어로 요청 및 응답을 전송하는 등 요청과 응답 객체를 갖고 여러 기능들을 수행할 수 있다. 자바스크립트 함수가 구현할 수 있는 모든 기능을 미들웨어 안에서도 구현할 수 있다. 다음 코드를 보자

app.use( (req,res,next) => {
  console.log('Request recieved');
});

위 코드는 미들웨어의 한 예시이다. app.use() 는 콜백 함수를 파라미터로 취하고, 이는 모든 요청이 들어올 때마다 실행된다. 위 예시에서 서버는 요청을 받을때마다 처음으로 등록된 미들웨어 함수를 찾아 호출한다. 위 경우엔 단순히 "Request received"를 출력한다

그런데 미들웨어가 낯선 독자들에겐 한 가지 궁금한 것이 생길 수 있다. 우리가 만들고자 하는 어플리케이션이 미들웨어와 무슨 상관이 있는건데? 라는 것이다. 그에 대한 답변은 간단하다. Express의 공식 문서에 의하면, 다음과 같이 설명하고 있다.

An Express application is essentially a series of middleware function calls

우리가 Express를 활용하는 것이 바로 미들웨어를 활용하는 것이란 뜻이다. 웹 서버 상에서 분리된 엔드포인트 상으로 적절하게 데이터를 라우팅 하는 것 뿐만 아니라 필요한 미들웨어를 작성함으로써 우리가 수행하고자 하는 어플리케이션 로직을 정확하게 수행할 수 있는 것이다

next()


미들웨어는 라우트와 마찬가지로 코드상에서 나타나는 순서대로 실행된다. 중요한것은 이게 메서드와는 관련이 없다는 것이다. app.get() 뒤에 나타나는 app.use()app.get()이 호출된 뒤에 실행된다. 다음 코드를 보자

const express = require('express');
const app = express();
const PORT = 4001;

app.use((req, res, next) => {
  console.log("A sorcerer approaches!");
  next();
});

app.get('/magic/:spellname', (req, res, next) => {
  console.log("The sorcerer is casting a spell!");
  next();
});

app.get('/magic/:spellname', (req, res, next) => {
  console.log(`The sorcerer has cast ${req.params.spellname}`);
  res.status(200).send();
});

app.get('/magic/:spellname', (req, res, next) => {
  console.log("The sorcerer is leaving!");
});

app.listen(PORT, () => console.log(`Server is listening on port ${PORT}`) );

// Accessing http://localhost:4001/magic/fireball 
// 출력:
// "A sorcerer approaches!"
// "The sorcerer is casting a spell!"
// "The sorcerer has cast fireball"

위 코드에서 라우트는 각각이 코드상에서 나타나는 순서대로 호출되었고, 이전 라우트의 next()에 의해서 다음 미들웨어로 컨트롤이 넘어가게 되었다. 여기서 마지막 app.get()은 호출되지 않았음을 확인할 수 있는데, 이는 이전의 라우트에서 next()를 호출하지 않았기 때문이다. Express에서 미들웨어는 (req, res, next) 세 가지 파라미터를 취하는 함수이다. 세 번째 파라미터인 next 는 미들웨어의 마지막에서 명시적으로 호출해주어야 다음 미들웨어로 요청과 응답을 핸들링 할 수 있는 컨트롤을 전송할 수 있다.

Morgan


로깅을 도와주는 Morgan을 설치해보자!

npm install morgan



누가 들어오는지, 어디에 접속하는지 status code와 함께 알 수 있다.

helmet


node.js 앱의 보안에 도움이 되는 helmet을 설치해보자!

npm install helmet
import helmet from "helmet";

import하는 것을 잊지말자.

cookieParser , bodyParser


cookiebody를 다루는데 도움이 되는 cookieParser, bodyParser를 설치해보자. 이부분은 우선은 적용만하고, 어떤 역할을 하는지에 대한 자세한 내용은 나중에 이해하게 된다.

npm install body-parser cookie-parser
import cookieParser from "cookie-parser";
import bodyParser from "body-parser";

강의에서 들은 내용을 토대로 간단히 요약해보자면, 유저가 전송하는 데이터를 이해할 수 있게 특정 형태로 처리해 주는 미들웨어인 것 같다.


궁금해서 stack overflow에 검색해본 결과...
cookie-parser는 쿠키 헤더를 파싱하고, seperation, decrypt 등을 처리해준다.

본 강의에서는 session을 다루기위해 쿠키에 유저 정보를 저장할 것이므로, cookie-parser 가 필요하다.

bodyparser


body-parserrequestbody를 서버가 POST/GET request 형태의 내가 설정한/원하는 형태로 받을 수 있게끔 파싱해준다고 한다. (ex. JSON,URLencoded,text,raw)

본강의에서 쉽게(?) 설명해준 바로는 이러하다.
body로부터 정보를 얻을 수 있게 해주고, 유저가 데이터를 전송하면 서버가 이해할 수있게 바꿔준다. 바꿔주는 형태는 내가 설정할 수 있다. 본 강의에서는 json,urlencoded를 설정했다.

middleware는 위에서 아래로 작동한다는 것을 유의해야 한다. 순서에 민감하다고 한다..

라우팅


express에서 라우터란 , 라우트들의 복잡함을 쪼개주는데 사용한다.

우선적으로 기존의 index.jsapp.js로 이름을 바꾸고, init.js를 새로 만들자.

앞으로, 수많은 미들웨어, 수많은 라우트, 콜백함수들이 작성될 것이기 때문에 코드를 여러 파일로 분할하여 exportimport를 통해 코드를 효율적으로 작성하기 위한 초석이다.

app.js파일에는 init.js로 전달할 app의 대한 내용이, init.js 파일에는 서버를 실행시킬 코드를 작성하자!

  • app.jsinit.js에 전달하기 위해, export 를 해줘야 한다.

  • init.js 코드
  • app.jsimport한다.

라우트들이 담길 router.js 파일도 작성해보자!

  • "/" url을 요청하면, 'user index'를 보낸다.
  • "/edit" url을 요청하면, 'user dedit'를 보낸다.

app.js 에서 사용해야 하므로 , export 해주는 것을 잊지말자.

  • defaultexport 하지 않았을 경우, router.js 파일 내부의 userRouter만을 import하려면 위와 같이 코드를 작성하여야 한다.

app.get("/",handleHome);
app.get("/profile",handleProfile);

이 부분도 다 밑의 app.use~ 처럼 바꿔주는 작업을 할 것이다!

  • app.use("/user",userRouter); 부분에서의 use"/user" url의경우 userRouter의 모든 라우트를 사용할 수 있다.


  • 위와 같이, userRouter에서 정의한 두개의 라우트들이 모두 정상적으로 실행되는 것을 확인할 수 있다. 이러한 과정이 라우팅이다.

Model View Control Pattern (MVC)


Model : Data
View : How does the Data look
Control : Function that looks for the Data

Model 은 이후에 배울 데이터베이스 쪽 내용이니 잠시 접어두고,
View 는 어떻게 그 데이터를 시각화해 줄 것인가인데 template을 통해서 처리할 것이다.
Control은 데이터를 말 그대로 컨트롤 하는 것이다. 지금까지는, Url에 해당하는 라우터를 사용하고, 콜백함수가 Controller의 역할을 했다. 이후에는 따로 Controller역할을 하는 javaScript 코드를 작성할 것이다.

코드 간결화 (Routes, express.Router(), ... )


프로젝트가 커지면 수많은 경로와 수많은 라우트들을 사용하게 되는데, 어디선가 새로 사용할 때마다 기억해내고 반복해서 사용하는 것은 아주 비효율적이다. 이러한 상황을 대비해서 코드를 처음 짤때부터 라우트와 라우터를 따로 js 파일에 정리하는 것이 미래에 아주 좋은 결과를 가져다 줄 것이다.

route 경로


"/", "home" 등과 같은 경로들을 한 파일에 정리하여, 필요 할때마다 import 해서 쓰기 위해 routes.js를 작성해보자.

  • 라우트 경로를 작성할 때 영역별로 정리하는 습관을 들이자. 중구난방 작성하는것보다 훨씬 파악하기 쉽다.

  • routes 라는 객체를 만들고, 그 안에 각 변수들을 값으로 지니는 프로퍼티들을 추가한다.
  • "/users" 라는 경로를 사용하고 싶을때 routes.users를 쓰면 되는 것이다.
  • export 하는것도 잊지말자.

Routers Controller


앞서 작성하였던 app.use() 부분을 이런 형태로 정리 할 것이다. Router를 따로 만들어서 사용하는것이 편리하다.

  • globalRouter.js userRouter.js videoRouter.js 를 routers 라는 폴더에 생성하고 작성해보자.

globalRouter


  • express의 Router()메소드를 이용하여 위와 같이
    라우터.get/post( 라우트경로, 콜백함수 ) 의 형태로 이루어진다.

Controllers

앞서 get 메소드의 인자로 들어간 콜백함수가 Controller의 역할을 한다고 말했다. 이 또한, 따로 만들어 관리하는 것이 훨씬 효율적이다. controller 폴더에 globalController.js userController.js videoController.js 를 작성해보자.


  • 이런식으로 따로 작성하고, import 하여 사용하면 코드가 아래와 같이 훨씬 간결하고 이해하기 쉽다.

userController


export const join = (req,res) => res.send('join');
export const login = (req,res) => res.send('login');
export const logout = (req,res) => res.send('logout');
export const users = (req,res) => res.send('Users');
export const userDetail = (req,res) => res.send('User Detail');
export const editProfile = (req,res) => res.send('Edit Profile');
export const changePassword = (req,res) => res.send('Change Password');

videoController


export const home = (req,res) => res.send('Home');
export const search = (req,res) => res.send('Search');
export const videos = (req,res) => res.send('Videos');
export const upload = (req,res) => res.send('Upload');
export const videoDetail = (req,res) => res.send('Video Detail');
export const editVideo = (req,res) => res.send('Edit Video');
export const deleteVideo = (req,res) => res.send('Delete Video');

이러한 코드 분리(?) 과정은 프로젝트가 커질때에 굉장히 유용하다고 한다. 위와 같이 컨트롤러 코드를 모두 작성하였다면, 다음파트로 가보자!

Pug (View)


설치 및 기본 설정


Pug는 html코드를 보다 깔끔하게 작성할 수 있는 템플릿이다. 이를 사용하기위해선 설치 및 express 설정을 해야하니 알아보자!

npm install pug
app.set("view engine","pug"); // express 뷰엔진 세팅
  • app.js에 작성해주면 된다.
  • <p>태그를 작성한다고 하면 기존의 HTML 코드처럼 태그를 작성할필요없이, p Hello! 라고 간단하게 작성할 수 있다. 또한, 기본설정으로 views 폴더에서 템플릿파일을 검색하기 때문에, 내가 사용할 템플릿 파일은 views 폴더를 새로 만들어 관리해줘야 한다.

  • express에서 템플릿을 찾아서 렌더링할 수 있게 해주는 render() 메서드를 이용하면 메서드의 인자에 들어간 문자열의 이름을 가진 템플릿파일을 검색하여 보여준다.

  • 이처럼 pug 템플릿에 작성한 코드가 정상적으로 출력됨을 확인할 수 있다.

레이아웃


pug에서는 페이지의 레이아웃을 정해놓고, 필요한 부분마다 다른 pug 파일의 내용을 넣어서 쓸 수 있다. 밑의 설명을 보면 어떤건지 감이 올거다!

  • layout 폴더를 생성하고, 레이아웃으로 쓸 pug 파일을 위와 같이 작성한다.
  • block content를 통해서 main 태그에 내용들을 넣을 수 있다. 이렇게하면 각 페이지마다 레이아웃을 복사 붙여넣기하는 수고를 덜을 수 있으며, 이후에 수정할때 매우 효율적이다. block이 없으면 내용이 적용이 안되니 명심하자.

-위와 같이 extends layouts/main 코드를 작성하면, 현재 pug파일의 내용에 main.pug 레이아웃을 적용시킬수있다.

  • main.pug의 레이아웃을 갖추면서, block content 부분에 home.pug 파일에 작성한 <p>태그가 적용이 되었다.

partials


header 태그와 footer 태그를 작업해보자.

  • 작성법은 조금 다르지만, 기본적인 틀자체는 html 문법과 같다. 자식요소는 들여쓰기해주면 된다.
  • #{}를 사용하면 바로 자바스크립트를 사용할 수 있다. Date() 생성자 함수의 getFullYear() 메서드를 이용하여 올해의 년도를 가져오는 코드다.
  • .fab.fa-youtube는 fontawesome에서 가져온 유투브 아이콘이다.

  • 회원가입 버튼과 로그인 버튼 생성

적용

  • include를 통해 따로 작성한 partials의 파일들을 레이아웃에 적용시킬 수 있다.

이러한 형태로 적용이 됐다면 성공! 이처럼, 하나의 페이지마다 들어갈 요소들을 매번 작성하는 것이 아니라 따로 파일들로 관리하고 필요한 요소들을 include하여 사용하면 수정작업을 아주 효율적으로 할 수 있다!

Local variables


express에서는 로컬변수들을 프로젝트 전역적으로 사용할 수 있게 하는 기능이 존재한다. 앞서 수많은 route들을 정의해놨고 이들을 pug 템플릿에서도 사용하기 위해서 res.locals()를 사용한다.

  • Expressjs 에서 view engine을 사용할 경우, 미들웨어에 세팅한res.locals를통해 정의한 데이터를 템플릿에서 사용할 수 있다. 단순하게 홈화면의 "WeTube"라는 문자열이될 수도 있고, routes.js파일에 정의해놓은 라우트들을 불러오고싶다면, res.locals.routes = routes 코드를 이용하면 된다.

  • 정의한 미들웨어를 export하고, app.use(localsMiddleware);를 app.js에 추가해주면 된다.

  • 라우트레벨 미들웨어에 전에 위치해놓아야 view가 정상적으로 작동한다.

  • 이런식으로 routes.join,routes.login 처럼 경로를 가져다가 쓸 수 있다.

Template variables


내가 원하는 템플릿에만 변수를 추가할 수 있는 방법은 없을까?

res.render() 함수의 첫번째 인자는 템플릿이고, 두번째 인자는 템플릿에 전달할 정보이다. 따라서, 두번째 인자에서 변수를 정의할 수 있다.

  • 위와같이 객체를 정의하고, pageTitle을 정의하면 home.pug 파일에서 사용할 변수를 정의할 수 있다.
  • 아래처럼 정상적으로 작동한다.

search controller


검색기능을 하는 검색창을 만들어보자!

html<form><input> 를 이용하여 검색창을 만든다.

  • <form>action속성은 routes.search로, methodget으로 설정한다.

  • 검색을하면, searchingBy 검색어를 나타내기위해 search 콜백함수를 위와같은 방식으로 바꾼다. req.query.term에 검색한 문자열이 저장되어있고, req.query.termsearchingBy라는 변수에 저장하고, res.render함수의 두번째인자의 객체에 추가한다.

Join : Login HTML


이부분 부터는 html 코드 작성하는 부분이다. 딱히 정리할게없다. 이해가되지않는다면, html코드부분을 더 공부해야 한다고 보면 된다!

  • 회원가입 양식
  • 로그인화면 양식
  • 소셜로그인
  • join.puginclude

Change Profile HTML


  • 프로필설정 폼 / .form-container 클래스명을 공유한다.

  • 정상적으로 작동한다.

upload,changepassword,editVideo


각 페이지마다 뼈대만 작성해주는 과정이다!

upload.pug


extends layouts/main

block content
    .form-container
        form(action=`/videos${routes.upload}`, method="post", enctype="multipart/form-data")
            div.fileUpload
                label(for="file") Video File
                input(type="file", id="file", name="videoFile", required=true, accept="video/*")
            input(type="text", placeholder="Title", name="title", required=true)
            textarea(name="description", placeholder="Description", required=true)
            input(type="submit", value="Upload Video")

changePassword.pug


extends layouts/main

block content
    .form-container
        form(action=`${routes.users}${routes.changePassword}`, method="post", enctype="multipart/form-data")
            input(type="text", placeholder="Current password", name="password")
            input(type="text", placeholder="New Password", name="password2")
            input(type="text", placeholder="Verify Password", name="password3")
            input(type="submit", value="change")

editVideo


extends layouts/main


block content
    .form-container
        form(action=routes.editVideo, method="post")
            input(type="text", placeholder="Title", name="title", value="abc")
            textarea(name="description", placeholder="Description")
            input(type="submit", value="Update Video")
        a.form-container__link(href=routes.deleteVideo)
            button.delete Delete Video

each문


home화면에 비디오 및 정보들을 출력해보자.

현재까지는 어떤 유저의 데이터가 없으니, 가상의 데이터베이스 db.js를 작성해보자.

export const videos= [
    {
        id: 324393,
        title: 'Video awesome',
        description: 'This is something I love',
        views: 24,
        videoFile:"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
        creator: {
            id: 121212,
            name: "PSM",
            email: "qkr5882103@gmail.com"
        }
    }
]
  • 데이터들의 배열을 생성하고, 각 유저마다 id,title,description,view,videoFile,creator의 프로퍼티를 가진 객체가 있으며, creator(유저)의 정보가 담긴 객체가 존재한다.

  • videoController.js 파일에서 render함수에 두번째인자 객체내부에 db.js에서 import 한 videos 배열을 추가하면, 템플릿에서 videos라는 배열을 사용할 수 있다.

  • each video(변수명) in videos 을 통하여 videos 배열 요소 하나당 <h1> 태그에 video 객체의 title 프로퍼티 값을 불러오고, <p> 태그에 description 프로퍼티 값을 불러온다.

  • 이처럼, 타이틀과 설명이 정상적으로 출력된다.

video thumbnails


영상 썸네일을 구현하기에 앞서, mixins의 개념에대해 알아보자!

minxin


mixin은 pug에서 쓰이는 함수의 일종으로, 동일한 구조의 html코드를 반복해서 작성하지 않기 위해서 쓰인다. 즉, 재사용할 수 있는 pug 블록을 만들어준다. 앞서 만든 영상제목 , 영상 설명 등등의 구조를 mixin을 이용하여 구현하는 과정은 다음과 같다.

  • mixin videoBlock() 내부의 인자는 넘겨줄 객체이며, default로 빈 객체를 가진다. 밑에는 원하는 html 구조를 작성하면 된다.

  • 영상이 표시될 블록을 구현하는 것이므로 코드는 위와 같다.

  • include mixins/videoBlock include 할 파일의 경로를 작성하면 해당 파일에서 mixin으로 만든 블록을 사용할 수 있다. 사용할 위치에 +videoBlock(mixin 함수명)({객체})를 작성한다. 위 코드에서는, each문으로 받아온 videos들의 각 item의 속성값들을 프로퍼티값으로 설정해준다. 그러면, 이 객체가 mixin에서 인자로 받아올 video 객체가 되고, <video>,<h4>,<h6> 태그에 각각 프로퍼티들이 출력된다.

Join controller


회원가입과정을 구현해보자!

  • 기존의 join 함수를 get methodpost method 에따라 다르게 작동하도록 함수를 구현한 것이다. get /join 에 대한 응답으로는 getJoin 함수를, post /join 에 대한 응답으로 postJoin 으로 나눈것이다.

  • <form> 태그에서 받은 body의 정보들을 조건문을 통해 회원가입이 성공적일때, 성공하지 못하였을때로 구분하였다. 성공할경우, 홈으로 redirect 시키고, 성공하지 못할경우 status code(400)과 함께 다시 회원가입창으로 되돌린다.

  • controller 함수를 수정했으므로, 당연히 라우터도 바꿔주어야한다.


Login controller


  • 이후에, 로그인했을때 각자의 id값에 의해서 작동하는 방식이 달라야하므로, 기존의 함수들과 route를 수정해야 한다.
videoDetail : (id) => {
    if (id){
        return `/videos/${id}`;
    }else {
        return VIDEO_DETAIL;
    }
},
editVideo : EDIT_VIDEO,
deleteVideo : DELETE_VIDEO

userDetail : (id) => {
    if(id){
        return `/${id}`;
    }else {
        return USER_DETAIL;
    }
}
  • 라우트 경로를 수정하였으므로, 라우터도 수정해줘야 한다.

MongoDB Mongoose


이제는 실제 데이터베이스를 이용해보자!

MongoDB는 noSQL기반이고, 규칙이적고 유연한 성격을 가진다고 한다. Mongoose는 javaScript로 MongoDB를 연결하기 위한 라이브러리다.
두가지 모두 설치하고 진행해야 한다.

Connecting to MongoDB


기존에 썼던 가상의 가짜 데이터를 전부 삭제하고 실제로 MongoDB와 연결해보자.

  • mongoose.connect를 통해 나의 Database가 어디에 저장되어있는지의 string으로 req를 받는다. 두번쨰 인자의 객체는 기본설정이라고 한다.

  • MongoDB와의 connection을 db라는 변수에 저장한다.

  • 커넥션이 정상적으로 open 되면, once() 메서드를 통해 확인하고, 에러가 발생하는지도 확인하기위해 에러핸들링 함수를 만들어 확인한다.

  • init.jsdb.js파일을 import 해주면 끝.

dotenv로 환경변수 숨기기


dotenv를 이용하면, 중요한 정보들을 따로 관리할 수 있다.

  • .env 파일을 작성하고, 중요한 정보들을 변수에 저장해준다.

  • 사용하고자 하는 파일에서 dotenv를 import하고, dotenv.config()를 통해 .env파일의 변수들을 process.env.key에 저장한다.

  • 위와 같은 방식으로 사용하면 된다.

Video Model


업로드할 비디오 데이터의 형태를 잡는 단계이다. mongoos.Schema를 통해 말 그대로 스키마를 만드는것이다.

  • 각 비디오 데이터들은 fileUrl,title,views,createdAt 를 가진다. 말그대로 url링크,제목,조회수,생성날짜이다.

Comment Model


흔히 말하는 댓글의 데이터 형태를 잡는 단계이다. 위와 똑같은 방법이며 비디오가 올라간 게시물마다 각 댓글들이 있어야하므로 둘사이의 연관관계가 필요하다. 따라서, Comment마다 video의 id를 저장하는 코드가 필요하다.

  • Comment 데이터에 video의 id를 저장하는 video 속성을 만들어주고, 그 타입은 mongoos.Schema.Types.ObjectID이다. 이는 앞서 만든 Video 모델을 참조하며, this가 참조하는 모델에 바인딩된다.

Home Controller Finished(aysnc,await,try/catch)


Home화면의 마무리를 짓는 파트이다. 우리가 만들었던 Video 모델을 이용하여 Home화면에 표시하는 과정이다.

  • Video.js를 import하고, home 콜백함수에 async 키워드를 추가한다. javaScript에서 asyncawait키워드와 함께 쓰이며, await 코드를 추가한 부분의 코드 실행될 준비가 되었을때까지 기다리는 것이다.
  • Video.find({})Video 모델에서 어떠한 비디오 데이터든 찾는 것이고, await 문과 함께 적용되어 데이터를 찾을때까지 기다렸다가 준비가 되면 그 이후에 코드를 실행하게 된다.
  • try,catch문은 error가 발생하지않았을때 try 내부 코드가 실행되고, error가 발생했을때, 그 error를 캐치하고 error를 throw해준다. 본 코드에서는 error가 발생했을경우, default로 'home'템플릿에 넘겨줄 videos 인자를 빈 리스트를 할당해준 코드이다.

    지속적으로 업데이트 될 예정입니다!

Uploading and Creating Video


비디오 업로드하는 기능을 구현하는 파트이다. Multer 라이브러리를 통해, file의 fileUrl을 반환하는 middleware를 생성하고, 이를 통해 영상을 업로드하면 영상url링크를 서버에 저장하고 화면에 나타내게끔 하는 과정이다.

  • postUpload 콜백함수를 위와같이 수정하면, VideoSchema의 형태를 가지는 Video 데이터가 생성되고, 이 Video 데이터가 home 콜백함수에서 find메서드를 통해 videos 변수에 저장되어 home 템플릿으로 전달되고, 메인화면에 mixineach문을 통해 맨 아래 첨부 사진과 같이 나타난다.

  • Multer 가 프로젝트 폴더내부에 videos폴더를 생성하고, url 링크를 저장한다. single()메서드 내부의 videoFile<input> 태그에 file속성의 값이다. 이는 미들웨어의 역할을 하므로, Router를 아래와 같이 수정해주어야 한다.

videoRouter.post(routes.upload, uploadVideo, postUpload);

Manipulating with MongoDB


use we-tube // we-tube 데이터베이스 전환
show collections // 데이터를 보여줌
db.videos.remove({}) // videos 데이터 삭제
  • 위의 명령어로 MongoDB에 있는 데이터를 다룰수 있다.
app.use("/uploads", express.static("uploads"))
  • /uploads로 가면, uploads라는 directory에서 file Url 을 전달하는 미들웨어

  • 이처럼 성공적으로 업로드가 된다.

Getting Video by ID


홈화면의 비디오를 클릭했을 때, 상세페이지로 넘어가는 과정이다.

  • videoDetail.pug 파일에 상세페이지에 나올 내용을 작성한다.

  • videoDetail.pug를 렌더링하는 콜백함수를 수정한다. videoDetail페이지에서 영상의 id를 가져와야하므로, req.params.id 를 받아오고, findById() 메서드를 통해 해당 id를 가진 video데이터를 가져오고, 변수에저장하여 pug파일로 인자를 넘겨준다.

  • 형광 표시한 부분이 req.params.id 이다.

Editing Video


비디오를 수정하는 과정이다. video의 id로 해당 비디오를 검색하여 내용들을 바꾼다.

  • id로 식별해야하므로 , route를 수정한다.

  • editVideo.pug파일에 필요한 요소들을 다 넣어준다.

  • editVideo 콜백함수도 getpost 메서드에 따라 구분하고, mongooseModel.findById(), Model.findOneAndUpdate() 메서드로 수정하는 코드를 작성한다. 해당 데이터에서 idtitle,description을 불러와야한다.

Deleting Video


  • 방식은 Editing Video와 똑같다.

검색기능 구현을 마무리해보자. 어떤 키워드를 검색하면, 그 단어를 포함하는 영상 제목을 표시하게끔 하기 위해 정규표현식을 이용한다.

  • 빈 배열을 만들어주고, 정규표현식을 통해 데이터를 찾으면 배열을 재 할당해주는 구조이다.

Webpack


이파트는 강의만따라가다보면 막히는 부분이 너무많다. 본인이 공식문서를 뒤적뒤적하면서 작성한 내용들이라 틀릴수도있다..

웹팩은 여러가지 파일들을 하나의 파일로 묶어주는 번들러 역할을 해주는 툴이다. 웹팩은 모든 파일들을 하나의 모듈로 생각하기 때문에 스타일시트도 자바스크립트파일에 import 할 수 있다. 이들을 묶어 하나의 자바스크립트 번들로 만들어준다. 이 강의에서는 scss파일을 웹팩을 로드하여 별도의 css파일로 바꾸어 주는데 사용한다.

package.json 수정


  • 웹팩을 실행시킬 명령어
  • WEBPACK_ENV는 웹팩의 모드 옵션을 설정하기 위해 생성한 변수이다. 이후에 웹팩 세팅에서 사용한다.
  • cross-env를 사용하면 윈도우에서 nodejs 환경변수를 설정할때 사용하는 모듈이다. 설치후 실행명령어 앞부분에 적어주면 된다.

webpack.config.js



webpack을 사용하기위에 필수적인 webpack.config.js파일이다. 웹팩을 세팅하는 중요한 파일이다.

  • 웹팩에서는 엔트리포인트에 있는 파일들을 묶어서 아웃풋포인트에 번들을 생성한다.

  • path는 nodejs 내장 모듈로, 경로를 설정하는데 사용된다.

  • Path 모듈은 파일과 Directory 경로 작업을 위한 Utility를 제공한다.

  • MODE 변수의 할당값은 앞서 package.json에 사용한 변수명과 똑같아야 한다.

  • ENTRY_FILE, OUTPUT_DIR 은 각각 엔트리포인트의 파일경로와 결과물이 저장될 경로이다.

  • config 변수는 사용할 설정들을 담을 객체이다.

  • entry, mode, module, rules, output, plugins 등 여러 속성들이 존재한다.

  • entry 속성은 위에서 정의한 ENTRY_FILE을 할당해준다. 이 경로의 파일을 번들링해준다.

  • mode 속성에는 앞서 정의한 MODE즉 , 웹팩의 모드를 설정한다. 앞서 실행명령어에 따라 development 모드를 할것인지, production모드를 사용할것인지를 하나의 설정으로 정의한 것이다.

  • module속성에는 내가 사용할 모듈들이 들어가는데, 이 강의에서는 babel-loader, postcss-loader,sass-loader이 사용된다. 추가적으로 postcss-loader의 옵션으로 autoprefixer 플러그인을 사용한다. 이는 브라우저 호환의 문제를 해결해준다.

  • output 속성에는 OUTPUT_DIR 결과물이 저장될 경로를 할당해준다. pathfilename의 속성을 갖는 객체가 할당된다.

  • plugins 속성에는 사용할 플러그인이 들어가고, 여기에선 mini-css-extract-plugin을 사용한다. sass파일을 받아서 별도의 css파일로 추출해준다.

  • module.rules 속성에는 사용할 모듈이 어떤파일을 대상으로 할것인지 test하고 이는 정규표현식으로 탐색한다. module.rules.use속성에는 어떤 로더를 사용할 것인지 문자열의 형태로 들어간다.

  • mini-css-extract-plugin 으로 sass파일을 로드하여 css파일로 추출하기위해선 위와 같이 작성된 형태로 코드를 짜야한다.

app.use("/static", express.static("static"))
  • 웹팩에서 아웃풋엔트리를 static폴더로 지정하였으므로, 서버에도 설정을 해줘야 한다.
  • app.js 에 위의 코드를 넣어주면, 인자의 디렉토리의 파일들을 웹브라우저의 요청에따라 제공할 수 있다.

  • 이와 같이 작동한다면 올바르게 설정한 것이다.

User authentication


기본적인 기능 작업이 끝났으므로, 이제는 로그인시에 사용자 인증과정을 구현해보자!

passport.js 라이브러리를 사용하면, 굉장히 굉장히 간단하게 구현해낼 수 있다. 브라우저 상에 쿠키를 설정해주고, 브라우저에서 쿠키를 가져와서 인증기능을 구현해준다. passport.js는 수많은 사용자인증기능을 탑재하고 있다. 가령, 페이스북 로그인, 깃허브 로그인, 인스타그램 로그인 등이 있다. 사용하기에 앞서, 나의 사이트의 유저정보 모델을 만들어준다.

  • passport-local-mongoose는 아이디와 패스워드로 인증을 가능하게 해주는 플러그인이다.
  • passport.js파일을 생성하고, passport.use(모델명.createStrategy) 코드를 통해 아이디와 비밀번호 인증 과정을 쉽게 구현할 수 있다.

serialize


serialization 은 어떤 filed가 쿠키에 포함될 것인지 알려주는 역할을 한다. User.id를 브라우저 쿠키에 전달한다. 그 이후, deserialization 과정을 통해, 그 쿠키의 정보를 어떻게 사용자로 전환하는가를 정의한다. passport-local-mongoose에는 serializing을 기본적으로 제공하기 때문에 따로 작성할 필요는 없다.

  • 위와같이 메서드호출만 하면 된다.

  • postJoin controller 함수를 위와같이 고쳐주면, 패스워드가 일치하는 경우 User모델을 생성하고 인자로는 name,email을 갖는다. 그 후, User.register(user,password)를 통해 정보를 데이터베이스에 등록한다.

  • 위와 같이 저장된 것을 확인할 수 있다.

로그인


현재, 데이터베이스에 회원가입 정보까지 저장했다. 표면적으로 로그인도 가능하지만, 실제로 이 사용자를 구분하는 사용자 인증과정은 거치지 않은 상태이다. 이제 해보자!

앞서 작성한 passport.js,passport모듈 모두 import해주고, app.js파일에 미들웨어를 추가한다.

  • 위에서 실행된 cookieParser로 부터 쿠키를 받고, initialize() 를 통해 초기화되고, passport모듈이 쿠키를 인식하고 해당 사용자 정보를 찾아준다. 그리고 passport는 그 사용자 request의 object인 req.user를 만들어준다.

  • 로컬미들웨어에서 req.user 변수를 지정해주면 passport 가 저장한 req.user를 사용할 수 있다.
  • session() 을통해 쿠키가 express로 전달된 쿠키를 사용할 수 있게 된다. session은 사용자 정보라고 생각하면 된다. 휘발성 데이터기 때문에, 브라우저를 종료하면 사라진다. 따라서 이 역시 처리해주어야한다.
  • deserialziation()를 통해 어플리케이션의 모든 미들웨어나 request 객체에 사용자 정보(req.user)가 할당된다.

gitHub Login


깃허브 아이디를 통해 로그인 할 수 있는 기능을 구현해보자. 우선, 깃허브에서 새로운 어플리케이션을 만들고, 콜백 URL을 설정하고, 유저가 해당 URL에 접근하면 깃허브 Authentication 페이지로 이동하고 유저가 승인하면 깃허브에서 정보를 받아온다.

npm install passport-github

  • passport-github를 import해주고, 설정을 해준다. clientId,clientSecret,callbackURL을 설정해준다. clientID,clientSecret 프로퍼티값은 공개되면 안되는 정보이므로 .env파일에 따로 저장해준다.
  • 깃허브 로그인에 쓰일 라우트를 새로 정의해준다.

  • 깃헙로그인에 쓰이는 라우터들이다. /auth/github url에 접근했을때, githubLogin 함수가 실행된다.

  • /auth/github/callback url에 접근하게될 경우 ,
    authentication 이 이루어지면 깃헙의 유저정보가 서버에 전달되고, 위에 passport.js에서 정의한 githubStrategy에 따라 gitubLoginCallback함수가 실행되면서 유저정보가 생성된 후에 postGitHubLogin 함수가 실행되면서 로그인이 된다.

  • 깃헙에서 보내준 정보중 _json프로퍼티에 id,name,email 등등의 정보가 저장되어있고, 선택적으로 사용할 수 있다. githubLoginCallback함수의 인자로 전달할 profile의 값으로 지정해주면 정보를 바탕으로 mongoosefindOne메서드로 사용자가 존재하는지 찾고, 존재하면 user의 쿠키를 저장하고 브라우저에 저장된 쿠키를 전달한다. 그리고 깃헙아이디를 업데이트해주고, 없으면 새로운 User모델을 생성하고 필수적으로 cb()함수를 return해야 한다.

User Detail


기존의 userDetail()라우트는, /${id}를 사용했기 때문에, url에서 아무런 문자열이나 입력해도 userDetail페이지로 이동했다. 말 그대로 해당 User의 프로필을 봐야하므로 , id를 식별하여 User 개인의 화면을 구현해보자.

  • 기존의 라우트 대신에 me라는 라우트를 사용하기 위해 컨트롤러를 위와 같이 설정해준다. User Detail페이지에서는 템플릿에 자신의 User정보를 가지는 User Detail페이지를 새로 만든다. 이는 me라는 새로운 라우트로 접근할 수 있다.
const ME = "/me";
const routes = {
  ...,
  me : ME,
  ...
}
// globalRouter.js
globalRouter.get(routes.me, getMe);
  • /me 경로로 접근하면 getMe 콜백함수가 실행되며, 로그인한 개인의 UserDetail 화면을 제공한다.

  • 해당 url에서 id를 이용하여 페이지를 식별한다.
  • userDetail.pug는 이 id를 지닌 user를 인자로 넘겨 받는다.

  • UserDetail 페이지에서는, 로그인된 사용자의 id와 해당 userDetail에 접속한 사람의 id를 비교하여 본인인일경우 프로필을 수정할 수 있는 버튼을 화면에 출력한다.

  • 이런 방식으로하면 하나의 템플릿으로 본인과 타인의 화면을 동시에 컨트롤할 수 있다.

Edit Userprofile and Avatar


프로필 업데이트 부분을 구현해보자. 우선적으로, Edit Profile 페이지에서는 avatar, name,email
설정할 수 있다.

  • postEditProfile Multer 파트에서 했듯이 req.body.name , req.body.emailreq.filereq로 받고, mongoose.ModelfindByIdAndUpdate속성을 이용하여 해당 id를 유저의 name,emai,avatarUrl을 받아온다. 삼항연산자를 이용하여 현재 file이 업로드되면 수정하고 아니면 기존의 avatarUrl을 유지하게 설정한다.
  • Multer 역시 설정해주어야 한다. multerAvatar.single() 의 인자로는 <input>name 속성값이 들어간다.

change Password


passport-loca-mongoosechangePassword()메서드를 통해 구현할 수 있다. 다른 컨트롤러함수와 같이 post메서드에대해서 form태그에서 전송된 값을 받아 업데이트 한다.

Adding Creator to Video


게시글 작성자를 추가하고, 작성자만 자신의 프로필을 수정할 수 있게 끔 구현해보자.

  • 우선적으로, 만들어놓은 Video모델에 creator 속성을 추가한다. 타입은 ObjectId이다. ObjectId로 해당 비디오의 작성자를 판별할 것이므로 , User 모델역시 코드를 추가해줘야한다.

  • Video모델과 링크를 해준다. 앞서 Comment를 작업한것과 같은 원리다.

  • passport.js의 힘으로 , 우리는 모든 reqreq.user가 존재하고, 새로운 비디오를 업로드 할때, 추가해준 creator속성을 넘겨준다. User모델에는 videos프로퍼티가 존재하고, 해당 배열에 만든 사람의 id를 넘겨주는 과정이다. 이로서, 게시글에는 작성자의 id가 담기고 이로서 구분할 수 있다.

  • mongoosepopulate메서드를 이용하면, 아래와 같이, creator 속성은 해당 id와 매치되는 객체객체를 저장하고 있다.

  • 아래는 Video Detail 페이지에서, 위에서 설정한 video의 creator프로퍼티 값으로 로그인한 유저와 같은지를 구분하게 끔 작성한 코드이다.

protecting video routes


위의과정과 비슷하다. 아무나 /edit, /delete 라우트로 접근하여 편집, 삭제를 하면 안되므로 , 현재 사용자의 id를 식별하고 idloggedUserid와 다를경우 차단시켜줘야 한다.

Custom Video Player


profile
연세대학교 산업공학과 웹개발 JavaScript

0개의 댓글