• [Node.js] 유튜브 클론 01

    2020. 2. 24.

    by. 나나 (nykim)

    + 이 글은 노마드 코더의 [유튜브 클론 코딩] 내용을 담고 있습니다.

     

     

     

     

    (2-0) What is a Server

    소프트웨어 의미의 서버란, URL에 응답하고 접속을 허락하는 일을 해줍니다.

    예를 들어, nykim.net 이란 웹 사이트도 서버가 있을 텐데요, 이 서버는 누군가 접속하길 기다리고 있다가 요청이 들어오면 갖고 있던 정보를 알려줍니다.

    요약하자면, 서버란 접속을 Listen하는 무언가👂라고 할 수 있죠 

     

     

     

     


     

    (2-1) What is Express

    Express.js 는 프레임워크에요!

    프레임워크는 훌륭하신 분들이 저 같은 쪼렙을 위해 만들어주신 위대한 산물로, 코드를 쓱쓱 손쉽게 가져다 쓸 수 있게 해주죠.

    예를 들면, 쟝고→파이썬 / Rails→루비 /라라벨 →PHP등이 있겠네요.

    그럼 Express.js는? Node.js를 위한 프레임워크죠!

     

     

     


     

    (2-2) Installing Express with NPM

     

    Installing NodeJS

    우선, Node.js를 설치합시다. 

    그런 다음 index.js 파일을 만들어줍니다. (터미널에서는 touch index.js로 생성할 수 있어요!)
    그 안에는 console.log("안녕, 나나!)" 라고 쓰윽 써보자구요.

    터미널로 해당 파일이 있는 위치로 이동한 다음, node index.js 명령어를 치면 콘솔에 찍혀나오는 걸 볼 수 있어요.

     

     

     

     

    Installing ExpressJS

    Node.js가 설치된 걸 확인했으니 이제 Express.js를 설치할 차례입니다.

    이건 NPM이란 슈퍼어썸🤘한 녀석을 이용해 설치해볼 거에요 

     

    NPM(Node Package Manager)은 node.js 월드의 중심지 같은 곳이랄까요? Node.js로 만든 것들을 손쉽게 공유할 수 있습니다. 업데이트도 수시로 가능하고 다운로드도 간편하죠. 말 그대로 노드로 만든 패키지들을 손쉽게 관리해주는 매니저입니다. 그럼 NPM은 어디서 설치하냐 하면은, 이미 node.js 설치할 때 덤으로 딸려왔기 때문에 그냥 쓰면 됩니다.

     

    아까 index.js 가 있던 폴더로 가서 npm init를 입력합니다.
    그럼 package 이름, 버전, 작성자 등을 입력하라고 뜰 텐데요, 적당히 슥슥 입력하고 나면... package.json 파일이 뿅 생겨나죠!

    앞으로 npm을 실행할 때는 이 package.json이 있는 폴더에서 실행해야만 합니다. 이게 없으면 읽지를 못해서 다른 곳에다가 또 package.json을 만들어버리거든요 🙄

     


    그럼 이 폴더에서 npm Install express를 해줍니다.

    완료 후 폴더를 살펴보면 node_modules/ 폴더가 생성되었고, 그 안에 여러 파일이 들어있는 걸 볼 수 있습니다. 우리는 npm을 통해 무사히 expressJS 관련 파일을 다운로드 받은 거죠 🙌

    package.json 파일을 편집기로 열어보면 dependencies 항목에 express가 추가된 걸 볼 수 있습니다.

     

     

     

     


     

    (2-3) Your First Express Server

     

    Git Setting

    다음은 깃 저장소를 만들 차례에요. Github에 가입 후 저장소를 하나 만들어둡니다.

     

    1. git remote add origin {저장소 주소}로 원격 저장소와 연결합니다.

    2. .gitignore 파일을 만들고, node_modules/를 적어줍니다.

    3. git checkout -b {브랜치이름}는 브랜치를 새로 만들고 바로 체크아웃할 수 있게 해줍니다. workspace란 브랜치를 만들죠.

    4. git add .로 폴더 내 모든 파일을 인덱스에 추가합니다.

    5. 그 다음,git commit -m “{설명}”으로 커밋을 해줍니다.
    6. 하지만 아직 원격저장소엔 올라가지 않은 상태입니다.git push origin workspace로 푸쉬합니다.

     

     

    (어... 저는 그냥 Sourcetree 🌳쓰려고요)

     

     

     

    Create Server

    아래 내용을 복사해서 index.js에 붙여넣습니다.

    /* index.js */
    
    const express = require('express');
    const app = express();
    const PORT = 4000;
    
    function handleListening() {
      console.log(`Example app listening on port ${PORT}!`);
    }
    
    app.listen(PORT, handleListening);

     

    여기서 const express = require(“express");는 뭔 뜻일까요?

     

    자, require는 요구한다는 뜻이죠. 그래서 require를 하면 어딘가에서 node modules를 가져온다는 뜻이 됩니다.
    이 경우에는 express란 이름의 폴더를 현재 위치에서 찾으려고 하겠죠.

    만약 못 찾으면 하위 폴더인 node_modules/ 안에서 찾으려고 할 거고요.

     

     

    실제로 node_modules/ 폴더 안을 보면 express/ 란 폴더가 있습니다.

    이 안에도 index.js가 있는데, 여기에도 require(‘./lib/express’);라고 써있네요.

    들어가보면 express.js 내에서도 수많은 require가 있는 걸 볼 수 있어요.

     

     

    이게 바로 node.js가 동작하는 방식입니다.

    모든 걸 작은 블럭단위로 쪼개고, 그걸 require하거나 import하는 거죠!

    결국 위의 코드는 express라는 걸 가져온 거고, 그걸 app 변수에 담아 express를 실행한 것이 됩니다.

     

    터미널에 node index.js를 실행해봅니다. 그리고 브라우저에 localhost:4000으로 접속해 보면!!!

    앗... 웹 페이지에 접속은 되는데 화면에 Cannot Get / 이라고 뜨네요. 뭐 어찌됐든 서버가 돌아가긴 하나 봅니다!! (풍악)

     

    아, 근데 매번 node index.js로 실행하기가 좀 번거롭네요.
    그럴 땐 package.json을 열고, 아래와 같이 써주면 됩니다.

     

    /* package.json */
    
    "scripts":{
        "start": "node index.js"
    }

     

    이제부터는 npm start라고만 써도 저 명령어가 실행될 거에요. ✧٩(•́⌄•́๑)얍

     

     

     


     

     

    (2-4) Handling Routes with Express

     

    그나저나 Cannot GET/ 은 무슨 뜻일까요? GET 할 수 없다?!

    GET이란, '웹에서 클라이언트-서버 사이에 요청과 응답을 통해 데이터를 주고받는 프로토콜'이라고 보면 됩니다.
    간단히 말하자면 '어떻게 데이터를 주고 받을지 정한 약속'입니다.

     

    주소창에 이 블로그의 도메인 주소인 nykim.work 를 치면 무슨 일이 발생할까요? IP 주소를 알아내서 접속한 다음 정보를 요청하겠죠. 그러면 nykim.work 서버는 GET 메서드를 실행합니다.

    반면 제가 이 블로그에 로그인을 시도할 때는요? 아마 POST 메서드를 쓸 거에요.

    "GET = 가져오는 것 / POST = 수행하는 것" 이라고 기억해두면 편합니다.

     

    한편 우리는 localhost:4000으로 접속했을 때 어떻게 데이터를 보여줄지 정해놓은 게 없죠.

    그래서 이 작업을 지금부터 진행하려고 합니다. 우선은 GET 방식으로 응답해보죠!

     

    handleHome() 함수를 만들고, 누군가 루트 주소로 접속한 경우 GET 메서드로 실행시켜줍니다.

     

    /* index.js */
    
    function handleHome() {
      console.log("홈화면!");
    }
    
    app.get("/", handleHome);

     

    이제 localhost:4000에 접속하면?! 웹페이지엔 아직 암것도 안뜨네요ㅠㅠ 물론 이건 내용이 없기 때문에 그렇습니다.

    대신 터미널을 확인해보면 콘솔에 뭔가 찍힌 걸 볼 수 있습니다.

     

     

     

    지금은 응답할 내용이 없기 때문에 화면이 로드되지 않으므로 응답할 내용을 작성해 줍니다.

     

    handleHome()에는 2개의 인자가 들어갈 수 있습니다. 바로, request object & response object 2개입니다.

    누가 어떤 페이지를 요청했는지, 어떤 종류의 데이터가 페이지로 전송됐는지 확인하려면 request object를 이용합니다.

    한편 응답할 내용을 설정할 때는 response object를 사용합니다.

     

    자, 아래와 같이 handleHome()에 req, res라는 두 개의 인자를 넣어줍니다.
    req로 어떤 정보가 넘어오는지를 콘솔로 찍어보고, res로 페이지에 문자를 넘겨주는 거죠.

     

    function handleHome(req, res) {
      console.log(req);
      res.send("홈 화면입니다.")
    }

     

    다시 npm start 후 lcocalhost:4000에 접속합니다.

    그러면 request object 정보가 콘솔에 찍히고, 화면에는 문장이 나타납니다.

     

     

    좋아요! 그럼 다른 페이지도 만들어보죠.

     

    /* index.js */
    
    function handleProfile(req, res) {
      console.log(req);
      res.send("프로필 화면입니다.")
    }
    
    app.get("/profile", handleProfile);

     

    localhost:4000/profile 로 접속한 결과는...?!

     

     

    아주 잘 나옵니다 (크으으으)

    하지만 언제까지 이런 텍스트를 보여줄 수는 없습니다. 그냥 send하는 대신 html, css 같은 파일을 응답해줘야 합니다.

    그게 우리가 앞으로 할 일이죠 💪

     

     


     

    (2-5) ES6 on NodeJS using Babel

     

    자, 우리는 이제부터 최신 문법을 써서 스크립트를 작성할 거에요! 까리하고 간지나지만 문제가 있죠. 어르신 브라우저에서는 읽을 수 없다는 겁니다. 

    그런데 우리가 작성할 때는 최신 문법으로 편하게 작성하고, 그걸 브라우저에서 동작시킬 때는 어르신들도 이해할 수 있는 옛날 문법으로 바꿔주는 무언가가 있으면 편하지 않을까요?

    그렇게 해주는 어썸한 존재가 있었으니, 바로 Babel 입니다.

     

    우리는 nodeJs에서 babel을 사용할 것이므로 아래와 같이 설치합니다 (npm 매니저님 짱짱!)

     

    $ npm install --save-dev @babel/node

     

    babel의 기능을 제대로 사용하기 위해선 여러가지 플러그인을 함께 설치해야 합니다.

    babel은 사실 하는 게 없고 일하는 건 플러그인이 다 하거든요. 예를 들어, 화살표 함수를 평범한 함수로 바꾸려고 한다면, @babel/plugin-transform-arrow-functions을 설치해야 합니다. (어이쿠야)

    하지만 저같은 쪼렙이 플러그인을 하나하나 설치하기엔 너무 어렵죠. 그래서 그런 플러그인을 모아놓은 preset이 있습니다.

    우리는 여기서 preset-env라는 프리셋을 사용할 거에요.

     

    $ npm install --save-dev @babel/preset-env

     

    그리고 @babel/core도 설치해줍니다.

     

    $npm install --save-dev @babel/core

     

    babel에도 설정을 관리하는 파일이 있을까요? 당연히 있습니다, 바로 .babelrc죠.

    .babelrc는 플러그인들을 모아놓고 설정하는 파일입니다. 아마 자동으로 생성되었을 텐데, 열어보면 우리가 preset-env를 쓸 거라고 적어놓은 게 보입니다.

     

    /* .babelrc */
    
    {
      "presets": ["@babel/preset-env"]
    }

     

    이제 babel이 제대로 동작하는지 확인해봅시다.

    우선 package.json 파일을 열어 node가 아닌 babel이 실행되도록 설정합니다.

     

    /* package.json */
    
    "scripts": {
        "start": "babel-node index.js"
      }

     

    그리고 index.js 파일을 열어서, const express = require("express"); 부분을 import express from "express";로 바꿉니다.

     

     

     

     

    이렇게 해두면 babel이 코드를 옛날 방식으로 바꿔준 다음에 node를 실행시켜줄 거에요.

    서버를 껐다가 다시 npm start 를 입력하여 잘 동작하는지 확인 후 넘어가요 👇

     

    다른 문법도 잘 동작하는지 볼까요? 이번에는 화살표 함수로 바꾼 후 서버를 재실행해 봅니다.

     

    /* index.js */
    
    const handleListening = () => console.log(`Example app listening on port ${PORT}!`);
    const handleHome = (req, res) => res.send("홈 화면입니다.");
    const handleProfile = (req, res) => res.send("프로필 화면입니다.");

     

    아주 잘 동작하네요! 👏

     

    하지만 좀 불편한 점이 있어요ㅠㅠ 수정이 있을 때마다 계속 껐다 켰다 해야하거든요.

    이걸 해결하기 위해 이번에는 nodemon 이라는 애를 불러다 쓰겠습니다. 얘는 변경사항이 생기면 알아서 서버를 껐다 켜 줄 거에요.

    역시 매니저님을 통해 설치합니다.

     

    $npm install nodemon -D

     

    -D는 devDependencies에 추가하라는 명령어입니다. 이건 개발에만 필요한 기능이니까요.

    아니면 기존처럼 --save-dev를 입력해도 똑같이 동작합니다.

     

    이제 package.json를 열어 아래와 같이 수정해 줍니다.

     

    /* package.json */
    
    "scripts": {
      "start": "nodemon --exec babel-node index.js"
    }

     

    그럼 index.js에 수정이 가해질 때마다 서버가 알아서 재시작되는 걸 볼 수 있습니다. 멋지네요!!

     

     

     


     

    (2-6) Express Core: Middlewares

     

    Express에는 미들웨어(Middleware)라는 게 있습니다. 얘는 음, 말그대로 중간에 있는 애에요!

    클라이언트가 요청을 하고, 서버가 거기에 응답하는 과정 중간에서 복작복작 여러 가지 일을 해주는 함수라고 할 수 있죠.

     

    사용자가 웹사이트에 접속하면 index.js가 실행되고, route(경로)를 살펴볼 것입니다.

    그럼 적혀있는 대로 handleHome()을 실행할 거고요.

    바로 이 과정 중간에서 실행되는 함수를 미들웨어라고 합니다.

     

    한 번 만들어보죠! betweenHome() 이라는 미들웨어를 만들고, handleHome 전에 넣어줍니다.

     

    /* index.js */
    
    const betweenHome = () => console.log("중간에 있어요!");
    
    app.get("/", betweenHome, handleHome);

     

    betweenHome()이 실행되면서 콘솔은 잘 찍혀나오는데, 문제는 handleHome()이 실행되지 않아요!

    그래서 betweenHome() 내에 next라는 걸 추가하겠습니다. 미들웨어 함수는 일반적으로 next란 이름의 변수로 표시됩니다.

     

    /* index.js */
    
    const betweenHome = (req, res, next) => {
      console.log("중간에 있어요!");
      next();
    }
    
    app.get("/", betweenHome, handleHome);

     

    next() 함수를 호출하면 앱 내의 그 다음 미들웨어 함수가 호출됩니다. 그러니 우리가 지정한 handleHome()이 실행되겠죠?

     

    이렇게 미들웨어는 중간에 껴서 다양한 일을 해줍니다. 우리는 원하는 만큼 미들웨어 함수를 만들어서 여러 가지를 해볼 수 있어요. 유저의 로그인 여부를 체크한다거나, 접속에 대한 로그를 기록한다던가 등등요!

     

    그런데 이 betweenHome은 홈화면에서만 작동하고, /profile로 접속했을 땐 실행되지 않습니다.

    어떤 루트로 들어오든 미들웨어가 실행되도록 하려면 어떻게 할까요?

     

    /* index.js */
    
    app.use(betweenHome);
    app.get("/", handleHome);
    app.get("/profile", handleProfile);

     

    이렇게 해두면 위에서 아래로 실행이 되면서, betweenHome - next 를 거쳐 우리가 원하는대로 동작하게 됩니다.

    그러니 루트 처리 이전에 우리가 원하는 만큼 미들웨어를 배치하면 되겠죠 👷

     

     


     

     

    (2-7) Express Core: Middlewares Part Two

     

     

    express의 미들웨어 중 하나인 Morgan을 설치해보겠습니다. 얘는 기록자에요! 로그를 기록해주죠.

    매니저님을 불러다가 npm install morgan으로 설치해줍니다. 

    그리고 우리가 express를 쓸 때 import 했던 것처럼 morgan도 불러와줍니다.

     

    /* index.js */
    
    import morgan from "morgan";

     

    그리고 기존 app.use(betweenHome) 부분을 아래와 같이 바꿔줍니다.

     

    /* index.js */
    
    app.use(morgan("short"));

     

    morgan은 여러 가지 포맷을 제공합니다. dev라면 개발용 로그를, short라면 짧게, tiny라면 최소한의 형태로 로그를 출력해줍니다.

    short로 지정했더니 이렇게 출력되어 나오네요!

     

     

    그밖에 helmet 이라는 미들웨어도 있습니다. 뭔가 튼튼해 보이는 얘는 이름처럼 보안에 도움을 줍니다.

    npm install helmet 으로 설치하고 똑같이 import 해줍니다.

     

    /* index.js */
    
    import helmet from "helmet";
    
    app.use(helmet());

     

    한편, 미들웨어는 중간에서 연결을 끊어버릴 수도 있습니다.

    아래와 같이 작성하면 어떻게 될까요?

     

    /* index.js */
    
    const middleware = (req, res, next) => {
      res.send("예히!");
    };
    
    app.get("/", middleware, handleHome);

     

    그럼 res.send("예히!")가 동작하는 바람에 handleHome이 실행되지 않는 걸 볼 수 있습니다.

     

    이어서 다른 미들웨어들도 쭉쭉 설치해 봅니다.

     

    $ npm install body-parser
    $ npm install cookie-parser

     

    이 미들웨어는 바디와 쿠키 데이터를 사용할 때 쓸 애들입니다.

    마찬가지로 import 해줍니다.

     

    /* index.js */
    
    import cookieParser from "cookie-parser";
    import bodyParser from "body-parser";
    
    app.use(cookieParser());
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));

     

    bodyParser 뒤에 붙는 건 이 파서가 어떻게 알아듣게 할 건지를 말하는데요,

    지금 시점에선 뭔소린가 싶지만 일단 이렇게 작성하고 넘어가겠습니다. (이해는 미래의 나에게 토오쓰)

     

     

     


     

    (2-8) Express Core: Routing

     

    app.js

    지금까지 index.js 파일 한군데다 몰아 작성하느라 좀 더럽💩하네요.

    작업과 유지보수에 편리하도록 분리를 좀 해야겠습니다.

     

    우선 기존의 index.js를 app.js로 이름을 바꿉니다. 그리고 init.js 파일을 새롭게 생성합니다.

    우리는 app.js를 init.js에서 실행되도록 할 거에요. 그러려면 init.js가 app을 import하면 되겠군요.

    import가 가능케 하려면 우선 export를 해줘야겠죠?

     

    app.js 파일 내에서 app.listen(PORT, handleListening) 부분을 지운 다음,

    맨 끝에 export default app;이라 작성합니다.

     

    이제 새로 만들었던 init.js의 윗부분에 import app from "./app"; 을 작성합니다.

    그리고 기존 index.js에 작성했던 부분을 잘라다가 init.js에 붙여넣기 합니다.

     

    /* init.js */
    
    import app from "./app";
    
    const PORT = 4000;
    const handleListening = () =>
      console.log(`🎧 Listening on port ${PORT}!`);
    
    app.listen(PORT, handleListening);

     

    마지막으로 pacakge.json에서 "scripts" 부분의 index.js를 init.js로 바꿔줍니다.

     

     

     

    localhost:4000을 확인해 보면 무사히 서버가 실행되는 걸 볼 수 있습니다.

     

     

    express.Router()

    그나저나 app.js를 좀 더 깔끔하게 만들 수는 없을까요?

    handleHome, handleProfile이 각각 적혀있으니 너저분하게 보이거든요.

    예를 들어 /user/edit이나 user/password 처럼 여러 하위 루트가 생기면 더 혼란스러울 테고요.

    그래서 이번엔 router를 활용해 루트를 효율적으로 관리해볼까 합니다 후후후  ψ(`∇´)ψ

     

    router.js를 새로 생성한 뒤, import express from "express";를 해줍니다.

    const userRouter = express.Router();로 userRouter를 만들고, send 해주는 임의의 익명함수를 작성합니다.

     

    마지막으로 userRouter를 export 시킬 건데요,

    export default...를 쓰는 대신 const userRouter 앞에 export 키워드를 붙여줍니다.

     

    /* router.js */
    
    import express from "express";
    
    export const userRouter = express.Router();
    
    userRouter.get("/", (req, res) => {res.send("user home")});
    userRouter.get("/edit", (req, res) => {res.send("user edit")});
    userRouter.get("/password", (req, res) => {res.send("user password")});

     

    이제 app.js로 가서 import 해줄 차례인데요, 얘는 default로 export 시킨 게 아니기 때문에 import { userRouter } from "./router"; 와 같이 작성해야 합니다.

     

    그리고 기존의 app.get 부분을 app.use로 바꿉니다. 누군가 /user 루트로 접속하면 userRouter를 사용하겠단 뜻이죠.

     

    /*** app.js ***/
    
    import { userRouter } from "./router";
    app.use("/user", userRouter);

     

    한 번 실행해볼까요? localhost:4000/user에 접속하면... 'user home' 글자가 나타나네요!

    localhost:4000/user/edit에 접속하면 'user edit'이라 적힌 화면이 나오고요.

     

     

    와, 이렇게 쪼개서 관리할 수 있다니 멋지군요 😎

    이걸 응용하면 user 외에 video/edit이나 video/update 같이 여러 루트를 만들 수 있겠네요.

     

     


    (2-9~11) MVC Pattern

     

    Routers

    MVC라는 패턴이 있죠! Model, View, Control를 뜻하는데, 우리도 이렇게 나눠서 작업을 진행해 봅시다.

     

    일단 기존의 userRouter 외 다른 router들도 만들어보겠습니다.

    router.js를 userRouter.js로 이름을 바꿀게요. user 외에 다른 루트들도 만들 거니까요.

    그리고 routers 폴더를 만들어 이 안으로 옮깁니다.

     

    userRouter.js 아래의 get 부분을 삭제합니다. 이렇게 하나하나 루트를 get 하는 건 비효율적이거든요.

    일단은 주석처리 해두겠습니다.

     

    /* userRouter.js */
    
    //userRouter.get("/", (req, res) => {res.send("user home")});
    //userRouter.get("/edit", (req, res) => {res.send("user edit")});
    //userRouter.get("/password", (req, res) => {res.send("user password")});

     

    아까는 const userRouter를 export 시켰는데, 이제는 이 파일 전체를 export 시킵니다.

     

    /* userRouter.js */
    
    import express from "express";
    const userRouter = express.Router();
    export default userRouter;

    i

    그리고 routers/videoRouter.js 파일을 만들어 비슷하게 작성합니다.

     

    /* videoRouter.js */
    
    import express from "express";
    const videoRouter = express.Router();
    export default videoRouter;

     

    이제 app.js로 가서 import 해줘야죠.

     

    /* app.js */
    
    import userRouter from "./routers/userRouter";
    import videoRouter from "./routers/videoRouter";
    
    app.use("/users", userRouter);
    app.use("/videos", videoRouter);

     

    엇, 하지만 만들고보니 루트 디렉토리에 암것도 없네요.

    얘를 위해 globalRouter.js도 만들어줍니다.

     

    /* app.js */
    
    import globalRouter from "./routers/globalRouter";
    app.use("/", globalRouter);

     

    그럼 디렉토리가 요런👇 상태일 거에요.

     

     

     

    routes.js

    모든 route를 손쉽게 관리할 수 있도록, routes.js 파일을 만듭니다.

    우리가 구조를 다 기억할 순 없잖아요? /users/id/edit... 음, 뭐더라... (뒤적뒤적) ← 이런 상황을 방지해야죠.

    그러니 route.js에 URL을 박아놓고 여기서 꺼내다 쓰도록 합시다.

     

    /* routes.js */
    
    // Global
    const HOME = "/";
    const JOIN = "/join";
    const LOGIN = "/login";
    const LOGOUT = "/logout";
    const SEARCH = "/search";

     

    좋아요! 이어서 users랑 videos도 써보죠.

     

    /* routes.js */
    
    // Users
    const USERS = "/users";
    const USERS_DETAIL = "/:id";
    const EDIT_PROFILE = "/edit-profile";
    const CHANGE_PASSWORD = "/change-password";
    
    // Videos
    const VIDEOS = "/videos";
    const UPLOAD = "/upload";
    const VIDEO_DETAIL = "/:id";
    const EDIT_VIDEO = "/:id/edit";
    const DELETE_VIDEO = "/:id/delete";

     

    저 :id로 표시한 부분에는 변수가 들어갈 거에요.

    그럼 URL이 '/videos/nanalike/edit' 이런 식으로 표시되겠죠?

     

    일단 이 정도로 하고 routes란 이름의 object 만들어 연결시켜줍니다.

     

    /* routes.js */
    
    const routes = {
      home: HOME,
      join: JOIN,
      login: LOGIN,
      logout: LOGOUT,
      search: SEARCH,
      users: USERS,
      userDetail: USERS_DETAIL,
      editProfile: EDIT_PROFILE,
      changePassword: CHANGE_PASSWORD,
      videos: VIDEOS,
      upload: UPLOAD,
      videoDetail: VIDEO_DETAIL,
      editVideo: EDIT_VIDEO,
      deleteVideo: DELETE_VIDEO
    };
    
    export default routes;

     

    이렇게 해두면 routes.editVideo처럼 편하게 쓱쓱 꺼내다 쓸 수 있겠죠?

    마지막에 export 시키는 것도 잊지 말자구요!

     

    확인을 위해 app.js로 가봅니다.

    import routes from ".routes";를 해주고, 기존의 URL부분을 변수로 대체합니다.

     

    /* app.js */
    
    import routes from "./routes";
    
    app.use(routes.home, globalRouter)
    app.use(routes.users, userRouter);
    app.use(routes.videos, videoRouter);
    

     

    물론 이대로는 실행이 안 될 거에요. 아까 저희가 get() 부분을 주석처리 해버렸으니까요.

    globalRouter.js로 들어가 해당 부분을 작성하죠.

     

    /* globalRouter.js */
    
    import express from "express";
    import routes from "../routes";
    
    const globalRouter = express.Router();
    
    globalRouter.get(routes.home, (req, res) => res.send("홈 화면!"));
    globalRouter.get(routes.join, (req, res) => res.send("회원가입 화면!"));
    globalRouter.get(routes.login, (req, res) => res.send("로그인 화면!"));
    globalRouter.get(routes.logout, (req, res) => res.send("로그아웃 화면!"));
    globalRouter.get(routes.search, (req, res) => res.send("검색 화면!"));
    
    export default globalRouter;

     

    이제 localhost:4000/login이나 localhost:4000/search 등에 접속하면 잘 동작하는 걸 확인할 수 있습니다.

    이렇게 분리해놓았으니 앞으로 작업하기가 더 수월해지겠군요!

     

    나머지 userRouter나 videoRouter는 user/profile, video/edit 과 같은 하위 페이지를 다룰 때 쓸 거에요.

     

     

     

     

    controller.js

    처음보다 보기 좋아지긴 했지만, 그래도 좀 더 분리시켜보겠습니다. res.send() 부분을 별도로 관리하자는 거죠.

    get()코드는 controller에 의하여 즉시 뼈와 살이 분리됩니다.

     

    routes 폴더 내에 router를 만들었던 것처럼, controllers 폴더를 만들고 controller를 작성할 거에요.

     

    우선 join, login, logout를 다룰 controllers/userController.js 를 만들어 줍니다.

    그리고 res.send() 부분을 똑 떼어 가져옵니다.

     

    /* userController.js */
    
    const join = (req, res) => res.send("회원가입 화면!");

     

    이걸 router 파일 내에서 쓸 거니까 export 시켜야겠죠?

    아래와 같이 작성합니다.

    /* userController.js */
    
    export const join = (req, res) => res.send("회원가입 화면!");
    export const login = (req, res) => res.send("로그인 화면!");
    export const logout = (req, res) => res.send("로그아웃 화면!");

     

    이제 globalRouter로 돌아가 export 시킨 함수를 res.send() 안에 쓱 넣어줍니다.

    아, 그러려면 우선 import 시켜야한다는 사실을 잊지 말고요! (개별 export니까 { } 로 불러와야한다는 것도요!)

     

    /* globalController.js */
    
    import { join, login, logout } from "../controllers/userController";
    
    globalRouter.get(routes.join, join);
    globalRouter.get(routes.login, login);
    globalRouter.get(routes.logout, logout);

     

    호오 잘 동작하는군요? 🦹‍♀️

    나머지 home, search 부분도 videoController.js 를 만들어 분리시켜볼까요 (우두둑)

     

    /* videoController.js */
    
    export const home = (req, res) => res.send("홈 화면!");
    export const search = (req, res) => res.send("검색 화면!");
    /* globalRouter.js */
    
    import { home, search } from "../controllers/videoController";
    
    globalRouter.get(routes.home, home);
    globalRouter.get(routes.search, search);

     

     


     

    (2-13~17) Start with Pug

     

    Installing Pug

    슬슬 View단을 작업해볼까요! HTML을 작성하기 위해 우리는 Pug라는 템플릿 엔진을 사용하겠습니다.

    HTML을 쓰기 쉽고 읽기 쉽게 만들어줄 뿐만 아니라, 동적인 부분과 정적인 부분을 따로 만들 수 있다는 게 장점이죠.

    두말하면 잔소리니까 얼른 설치해 봅시다.npm install pug로 역시 매니저님 소환!!

     

    pug를 쓰기 위해서는 현재 express의 view engine 설정을 바꿔야합니다.

    express의 view engine 기본값은 undefined인데, app.set()을 통해 바꿀 수 있습니다. app.js 내에서 아래와 같이 변경해 줍니다.

     

    /* app.js */
    
    app.set('view engine', 'pug');

     

    html 파일을 저장해야 하는 곳은 기본 디렉토리의 /views 폴더 내입니다.

    views 폴더를 만든 다음, home.pug 파일을 생성합니다. 확장자가 html이 아니라 pug에요!

    (이 글에서 추천하는 아이콘 익스텐션을 설치하셨다면 귀욤뽀짝한 멍멍이🐶가 보일 거에요)

     

    /* home.pug */
    
    h1 웹 퍼블리셔 나나입니다!

     

    위와 같이 작성한 다음, res.send() 부분을 지우고 대신 res.render('home');이라 적어줍니다.

     

    /* videoController.js */
    
    export const home = (req, res) => res.render("home");

     

    현재 view engine이 pug로 되어있으니, views/ 디렉토리 내에서 home.pug를 찾은 다음 렌더링해서 보여주겠죠!

    이제 localhost:4000에 접속해 봅시다 (두근두근)

     

    기대했던 대로 잘 나오네요!!

     

     

    Layouts

    맨땅에서 HTML 작업을 할 때는 반복되는 부분(헤더 등)을 끝없이 복붙해야 하죠 😱

    이 작업을 최소화하기 위해 pug로 템플릿을 만들도록 하겠습니다.

     

    우선 views/ 폴더 아래 layouts/ 폴더를 만듭니다. 그리고 토대가 될 main.pug를 작성합니다.

     

    /* main.pug */
    
    doctype html
    html
      head
        title 나나튜브
      body
        header
          h1.title 나나튜브
        main
          block content
        footer
          p.copyright © 나나라이크

     

    저기 저 block content 는 뭐냐구요? 바로 콘텐츠를 쏙 넣어줄 영역입니다. 이건 이따 작업할 거에요.

     

    만약 pug 문법이 어색하다면, 변환기를 사용해 볼 수 있어요.

    - HTML2Jade

    - HTML to PUG

     

    또는 공식문서를 참고해볼 수도 있고요.

    - pugjs.org

     

    템플릿을 불러오려면, home.pug에서 extends 해주면 됩니다.

     

    /* home.pug */
    
    extends layouts/main
    
    block content
      p 홈 화면입니다 :)

     

     

     

    Partials

    이번에는 페이지 전체 템플릿이 아닌, 특정 부분에만 돌려칠(?) 부분 요소를 작성해보겠습니다.

    예를 들어, 푸터가 main.pug에 들어가 있어도 되지만 이걸 footer.pug로 분리할 수도 있겠죠!

     

     views/ 폴더 안에 partials/ 폴더를 만들고, 그 안에 footer의 내용이 들어간 footer.pug를 생성합니다.

     

     

    가져올 때는 include ../partials/footer 로 가져오면 됩니다.

     

     

     

    한편, pug 내에서 #{}를 써서 자바스크립트를 삽입할 수도 있습니다.

    new Date().getFullYear()를 써서 현재 연도를 구한 다음 푸터에 넣어 봅시다.

     

    /* footer.pug */
    
    footer
      p.copyright © #{new Date().getFullYear()} 나나라이크

     

    참 쉽죠? 🎨🖼

    비슷하게 헤더도 만들어 봐요! partials/header.pug로 작성합니다.

     

    /* header.pug */
    
    header
      h1.title 나나튜브
      ul
        li
          a(href="#") Login
        li
          a(href="#") Join

     

    마찬가지로 include ../partials/header로 가져와 쓰면 됩니다.

    현재는 링크에다 #를 임의로 넣은 상태인데요, 이걸 우리가 지정한 route로 바꿔주면 될 것 같습니다.

    그렇다고 해서 href="/join" 처럼 쓰면 안 되겠죠. 혹시라도 주소가 바뀐다면 pug에서도 업데이트 해야할 테니까요.

     

    Single source of truth 를 바탕으로, 데이터는 한 곳에서만 관리할 거에요 :)

     

     

     

    locals

    local 변수를 global 변수로 사용할 수 있게 하는 미들웨어를 만들어보겠습니다.

    middleware.js 를 생성한 뒤, localsMiddleware를 작성하고 export 합니다.

     

    /* middleware.js */
    
    export const localsMiddleware = (req, res, next) => {
      res.locals.siteName = "NanaTube";
      next();
    }
    /* app.js */
    
    import { localsMiddleware } from "./middlewares";
    app.use(localsMiddleware);

     

     이렇게 해두면 이제 다른 파일에서도 저 변수를 가져다 쓸 수 있습니다. 이렇게요!

     

    /* main.pug */
    
    doctype html
    html
      head
        title #{siteName}

     

    오?! 그렇다면 우리가 만들어둔 routes도 locals에 저장해두면 pug 내에서도 써볼 수가 있겠네요!

    locals.routes에 저장하고, a(href=routes.home)과 같이 사용합니다.

     

    /* middleware.js */
    
    import routes from "./routes";
    
    export const localsMiddleware = (req, res, next) => {
      res.locals.siteName = "NanaTube";
      res.locals.routes = routes;
      next();
    }
    /* header.pug */
    
    header
      h1.title
        a(href=routes.home) #{siteName}
      ul
        li
          a(href=routes.login) Login
        li
          a(href=routes.join) Join

     

    한편, res.render()는 첫 번째 인자(필수)로 view, 두 번째 인자(옵션)로 locals를 받습니다.

    그래서 아래처럼 {} 안에 변수값을 넣어 우리가 넘기고 싶은 내용을 넘길 수가 있습니다.

     

     

    페이지별로 페이지 제목을 넣기 위해, pageTitle이란 locals를 활용해 볼 수 있겠죠.

     

    /* userController.js */
    
    export const join = (req, res) => res.render("join", {pageTitle: "Join"});
    /* join.pug */
    
    extends layouts/main
    block content
      p 회원가입 하세요! 롸잇나우!
    /* main.pug */
    
    doctype html
    html
      head
        title #{pageTitle} | #{siteName}
    ...

     

     

    아아 멋져부려 ★ヾ(´▽`)ゝ

     

     

    앞으로 router와 controller에 더 많은 내용을 추가할 거에요.

    그럼 다음 글에서 계속 진행해 보자구요!

     

     

    댓글 1

    • 프로필사진
      공부 2020.09.15 01:01

      안녕하세요! 유익한 정보 감사합니다~ 노마드 코더 인스타그램 도 해보셨나요??