유튜브 클론코딩 복습노트-1

Hyuno Choi·2021년 7월 20일
0
post-thumbnail

2021년 7월 18일

"여러분의 CS 교육에서 누락된 학기" 시리즈와 동시에 유튜브 클론코딩 시리즈를 시작하게 되었습니다. 유튜브의 핵심 기능을 클론해보면서 백엔드와 프론트엔드 간의 연결을 배우고 웹 프로그래밍에 많이 쓰이는 여러 기술들을 습득할 계획입니다. 저에게는 제대로 배우는 첫 백엔드 강의가 되겠습니다.

지난 시리즈와 마찬가지로 유튜브 클론코딩 시리즈는 자신을 위한 연습 노트가 될 것 같습니다. 강의에서 새로 배운 개념 및 깨달은 점을 중점적으로 적고, 기본적인 자바스크립트 문법은 넘어가도록 하겠습니다.

기초 셋업⚙️

Git & NPM

우선 프로젝트 폴더를 만들고 폴더 내부에서 $ git init 을 통해 로컬 저장소를 생성합니다. 본인이 사용하는 원격저장소와 $ git remote 로 연결합니다. 저는 GitHub를 사용합니다.

그리고 $ npm init을 통해 package.json 파일을 생성하여 npm 을 사용할 준비도 마칩니다.

마지막으로 프로젝트 폴더 내부에 src 폴더를 생성한 후 그 안에 server.js 파일을 생성합니다. 이 파일이 지금부터 메인 파일이 됩니다.

babel

자바스크립트의 최신 문법은 node.js 나 다른 브라우저에서 작동하지 않습니다. 따라서 프로젝트에 사용한 최신 문법을 브라우저에서 작동하는 문법으로 바꿔줄 필요가 있습니다.

$ npm i @babel/core --save-dev로 babel을 설치합니다. 여기서 --save-dev 는 해당 모듈을 devDependencies로 분류하겠다는 의미입니다.

이렇게 프로젝트에 쓰이는 모듈을 dependencies 에, 개발 과정에서 쓰이는 모듈을 devDependencies 에 따로 분류해놓으면 나중에 package.json을 보고 어떤 모듈이 어떤 목적으로 쓰였는지 알기 쉽습니다.

babel 설치 이후 babel을 사용하기 위해서는 preset을 지정해주어야 합니다. 이 프로젝트에서는 가장 기본적인 preset인 preset-env 를 사용하겠습니다. $ npm i @babel/preset-env --save-dev 명령어로 설치해줍니다.

그리고 babel에게 해당 프리셋을 사용한다고 알려주어야 합니다. 프로젝트 폴더 최상위에 babel.config.json 파일을 만들어줍니다. 그리고 파일 안에 다음 내용을 붙여넣고 저장합니다.

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

nodemon

서버를 개발하면서 소스코드를 수정하고, 저장하고, 서버를 종료하고, 다시 시작하는 과정은 번거롭습니다. nodemon 은 소스코드에 변화가 생기면(변경 사항을 저장하면) 자동으로 서버를 재시작해주는 유용한 모듈입니다. $ npm i nodemon --save-dev 로 nodemon을 설치합니다.

앞서 babel이 최신 자바스크립트 문법을 브라우저가 이해할 수 있는 버전으로 바꿔준다고 했습니다. 이제 소스코드를 저장하면 babel이 코드를 컴파일하고, 그 코드를 nodemon이 실행하게 하는 연속 명령을 만들어보겠습니다.

그러기 전에 nodemon과 함께 쓰는 데 필요한 babel 모듈 하나를 더 다운받습니다. $ npm i @babel/node --save-dev

이제 package.json 파일을 열어 scripts 부분을 다음과 같이 바꿔줍니다.

 "scripts": {
    "dev": "nodemon --exec babel-node src/server"
  }

이제 모든 준비가 끝났습니다. 앞으로 서버를 실행할 때는 $ npm run dev 라는 명령어를 입력하면 모든 작업이 자동으로 일어나게 됩니다. nodemon은 소스코드가 변경될 때마다 자동으로 서버를 재시작해주는데, 서버를 완전히 종료하려면 control + c 키를 누릅니다.

babel에 대한 더 자세한 정보는 다음 링크를 참고해주세요. https://babeljs.io

.gitignore

npm에서 여러 모듈을 설치했기 때문에 프로젝트 폴더 내에 node_modules 라는 폴더가 생성되었을 것입니다. 이 폴더는 다운받은 모듈과 그 모듈의 의존성 모듈을 모아놓은 것으로, package.jsonpackage-lock.json 파일만 있으면 언제든지 npm에서 다운받을 수 있기 때문에 이 폴더를 원격 저장소에 업로드할 필요가 없습니다.

따라서 .gitignore 라는 이름의 파일을 프로젝트 폴더 최상단에 생성한 후 다음 내용을 붙여넣습니다.

/node_modules

.gitignore 파일 안에 적혀있는 파일은 커밋 대상에서 제외됩니다. 참고로 폴더명을 적을 때는 위와 같이 앞에 / 를 붙입니다.

이제 프로젝트를 진행하기 위한 기초 공사가 끝났습니다. 본격적인 프로젝트로 들어가기에 앞서, 프로젝트에서 사용할 프레임워크인 Express와 Express의 Router에 대한 기초를 짚고 넘어가보겠습니다.

Express🚂

node.js를 사용하여 자바스크립트로 서버를 구현할 수 있습니다. 그러나 아무것도 없는 바닥에서부터 서버를 구현할 필요는 없습니다. 이미 서버를 어떤 방식으로 만들지를 구상해놓은 틀을 가져다가 쓰면 됩니다. Express는 그러한 틀 중 하나입니다.

Express 기초공사

Express를 사용하기 위해서 $ npm i express 명령어를 실행합니다.

이제 src 폴더의 server.js 파일에 서버를 만들어보겠습니다. 우선 express를 사용할 것이므로 express를 사용한다는 사실을 첫 줄에 알려줍니다.

import express from "express";

import 뒤의 express에는 어떤 이름도 들어갈 수 있습니다. 주로 모듈 이름이 너무 긴 경우 별명을 지어 사용합니다. 여기서는 express를 그대로 사용하겠습니다.

이제 express의 뼈대를 만들어보겠습니다.

import express from "express";

const app = express(); // 1

const PORT = 4000; // 2

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

순서대로 설명하면 이렇습니다.

  1. app 변수에 express 어플리케이션을 생성합니다.
  2. PORT 변수에 서버가 사용할 포트 번호를 저장합니다.
  3. 서버가 해당 포트에서 요청을 받기 시작합니다. 콜백 함수로 서버가 시작되었다는 알림을 만들어줍니다.

이제 서버의 뼈대가 완성되었습니다. 아까 만들어두었던 dev 스크립트를 실행하기 위해 $ npm run dev 명령어를 입력합니다.

[nodemon] 2.0.12
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `babel-node src/server.js`
Server is listening on port 4000

nodemon 실행과 함께 서버가 시작되었다는 알림이 잘 출력되고 있습니다. 이제 이 서버에 연결을 시도해보겠습니다. 웹 브라우저를 열고 http://localhost:4000 을 입력합니다.

해당 주소로 접속하면 Cannot GET / 이라는 메시지만 보입니다. 해당 주소로 들어가면 브라우저는 루트 / 페이지를 서버에게 요구합니다. 그러나 우리가 만든 서버는 요청을 받기만 할 뿐 그것을 다룰 줄 모릅니다. 이제 요청을 처리하는 방법에 대해 알아보겠습니다.

get( )

app.get()은 사용자의 요청을 처리하는 방법 중 하나입니다.

import express from "express";
const app = express();
const PORT = 4000;

app.get("/", () => {
  console.log("Someone go to Home");
});

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

app.get()을 사용해 /(루트) 요청을 받으면 콘솔에 메시지를 출력하도록 했습니다. 이제 localhost:4000에 접속하면 Cannot GET / 메시지가 뜨는 대신 로딩이 멈춰버립니다.

이유는 간단합니다. app.get("/") 으로 / 요청을 받았습니다. 그러나 요청에 대한 응답은 하지 않았습니다. 브라우저는 요청에 대한 응답이 올 때까지 계속 기다리게 됩니다.

request, response

사용자의 요청에 대한 응답을 보내기 위해서는 request, response에 대해 알아야 합니다. request는 사용자의 요청에 관한 정보와 도구를 담고 있는 객체이며, response는 보내줄 응답에 관한 정보와 도구를 담고 있는 객체입니다.

이 두 객체를 사용하기 위해서는 get()의 콜백 함수가 이들을 인자로 받아야 합니다.

app.get("/", (req, res) => {
  res.end();
});

app.get() 부분을 다음과 같이 수정합니다. 콜백 함수는 reqres 두 객체를 인자로 받습니다. end() 메서드를 사용하여 응답을 종료합니다. end()는 아무런 데이터 없이 응답을 종료할 때 사용합니다. 이렇게 되면 브라우저는 무한 로딩에서 벗어날 수 있게 됩니다.

이번에는 데이터를 응답으로 보내겠습니다.

app.get("/", (req, res) => {
  res.send("Hello World!");
});

이제 브라우저로 돌아가 새로고침 해보면 화면에 Hello World 가 보입니다. send() 메서드는 HTTP 응답을 보냅니다. 문자열 뿐만 아니라 json이나 HTML 형식도 전송할 수 있습니다.

app.get("/", (req, res) => {
  res.send("<h1>Hello World!</h1>");
});
app.get("/", (req, res) => {
  res.send({message: "Hello!"});
});

middleware

middleware는 말 그대로 가운데에 있는 무엇입니다. 여기서 가운데란 requestresponse의 중간을 말합니다. 때로는 사용자의 요청을 조금 더 복잡하게 처리해야 합니다. 예를 들어, 사용자의 로그인 여부를 확인하고 정보를 보여줘야 할 때가 있습니다. middleware는 이럴 때 사용할 수 있습니다.

next

const handleHello = (req, res) => {
  res.send("Hello");
}

const middleware = (req, res) => {
  console.log("I'm middleware");
}

app.get("/", middleware, handleHello);

middlewarehandleHello라는 콜백 함수를 만듭니다. 그리고 두 콜백 함수를 get()에 인자로 전달합니다. 이 상태에서 실행되는 것은 middleware 하나 뿐입니다. middleware가 실행되고 난 이후 handleHello를 실행할 수 있도록 연결을 만들어주어야 합니다.

const handleHello = (req, res) => {
  res.send("Hello");
};

const middleware = (req, res, next) => {
  console.log("I'm middleware");
  next();
};

app.get("/", middleware, handleHello);

middleware 함수는 next라는 세 번째 매개변수를 가질 수 있습니다. next에는 get()에 적어주었던 다음 함수가 인자로 들어가게 됩니다. 여기서는 middleware, handleHello 순서대로 get()에 전달하였으므로 middlewarenexthandleHello가 됩니다.

middleware 함수의 마지막에 next()를 적어주면 handleHello가 이어서 실행됩니다. 이런 식으로 middleware는 다음 middleware에게 차례를 넘겨줍니다.

app.use()

지금까지 app.get()을 사용해 특정 주소의 요청을 받고 미들웨어도 등록했습니다. app.use()를 사용하면 일종의 '전역 미들웨어'를 등록할 수 있습니다. 사용자가 어떤 주소로 요청을 보내도 해당 미들웨어를 거치게 됩니다.

const handleHello = (req, res) => {
  res.send("Hello");
};

const middleware = (req, res, next) => {
  console.log("I'm middleware");
  next();
};

app.use(middleware);
app.get("/", handleHello);

아까 만들었던 미들웨어를 app.use()로 등록했습니다. 이렇게 되면 사용자의 모든 요청은 일단 middleware가 처리한 후 app.get()으로 넘어가게 됩니다.

app.use()를 사용하는 순서는 매우 중요합니다. 만약 순서를 다음과 같이 바꾸면 middleware는 실행되지 않습니다.

app.get("/", handleHello);
app.use(middleware);

미들웨어가 작동하기도 전에 handleHello가 먼저 응답을 종료하기 때문입니다.

middleware 실습

이번에는 조금 더 복잡한 미들웨어를 만들어보겠습니다.

const handleHello = (req, res) => {
  res.send("Hello");
};

const prevent = (req, res, next) => {
  if (req.url === "/ban") {
    return res.send("Not Allowed");
  }
  next();
};

app.use(prevent);
app.get("/", handleHello);

prevent 미들웨어는 사용자가 특정 URL로 접근 시 응답을 종료해버립니다.

morgan

미들웨어는 만들어 쓸 수도 있지만 이미 만들어진 미들웨어를 쓰는 것도 가능합니다. morgan은 HTTP 요청 로그를 콘솔에 출력해주는 미들웨어입니다.

https://www.npmjs.com/package/morgan

morgan을 설치하기 위해

  1. $ npm i morgan 명령어를 입력합니다.
  2. server.js 맨 윗 줄에 import morgan from "morgan";을 작성합니다.

그리고 다음과 같이 코드를 작성합니다.

const handleHello = (req, res) => {
  res.send("Hello!");
};

app.use(morgan("dev"));
app.get("/", handleHello);

morgan은 미들웨어를 생성할 때 포멧을 지정해줄 수 있습니다. 여기서는 dev로 설정합니다.

이제 사용자가 HTTP요청을 보낼 때마다 콘솔에 로그가 표시됩니다.

GET / 200 8.703 ms - 6

요청 메소드, url, 상태, 응답에 소요된 시간 등이 표시됩니다.

아직까지 서버는 / url에 대해서만 응답할 수 있습니다. 다음 포스팅에서는 Express의 router에 대해 알아보겠습니다.


<참고 문서>

profile
프론트엔드 웹 개발자를 목표로 하고 있습니다.

0개의 댓글