웹 개발 실전 프로젝트 - 3주차
Node JS, Express, MongoDB를 배우고 연동해보자
313. Node JS 개요
- JavaScript는 설치하지 않는 대신 ECMAScript 사양을 갖추어야 한다.
- 오랫동안 브라우저는 JavaScript를 작성하고 실행할 수 있는 유일한 장소였다. JavaScript가 웹 애플리케이션에 적합한 이유도 바로 이 때문.
- Node는 브라우저 밖에서 작동되는 JavaScript의 실행 환경
- 2009년에 Ryan Dahl이 Node.js를 만들었고, 2011-2012년 사이 엄청난 주목을 받기 시작했다.
- 사람들이 개발한 애드온, 패키지, 도구 등 모듈의 수가 엄청나게 많다. ⇒ Node에 관련된 커뮤니티가 강력하다.
314. Node는 어디에 사용하는가?
- 웹 서버 구축 즉, 서버측 로직을 써서 풀스택 애플리케이션을 만들 수 있다.
- 지금 AJAX로 API를 요청해서 정보를 받고 브라우저에서 작업할 수 있지만, 내 API를 만들고 싶다면 어떻게? 내 DB를 만들어 거기에 웹 서버를 연결하고 풀 애플리케이션을 만들고 싶다면? ⇒ Node
- Netflix, Uber, NASA도 Node를 사용한다.
- Node의 유명한 프레임워크 Express 사용도 나중에 배울 예정
- line tool(명령줄 도구)란 전통적 사용자 인터페이스가 없는 경우에, 명령줄에서 실행되는 애플리케이션을 말한다. 조만간 배울 NPM 등도 명령줄 도구에 해당된다.
- 네이티브 앱도 Node로 만든다.
- VS Code, Slack 등
316. Node REPL
- Node REPL
- Read, Evaluate, Print, Loop
- 브라우저의 JavaScript 콘솔은 REPL이다.
.help
를 입력하면 사용할 수 있는 명령어를 제공한다.
- Node에는 DOM API와 Window, Document가 없다.
- Node에서 전역 객체는
global
이다.
- 브라우저에서 쓰는 기능이 없지만, 브라우저에 없는 기능도 많다.
- 전부 내장 모듈이고, 이 모듈을 이용해 운영 체제와 파일과 폴더를 다룰 수 있다.
317. Node 파일 실행
- JavaScript 파일에서 코드를 작성하고 Node에서 실행하기
node firstScript.js
318. 프로세스와 Argv
- Express가 없는 Node
- Node 버전 보기
node -v
process
는 Node에서 사용하는 객체로, 사용 중인 Node의 정보를 담고 있고, 전역 범위에 있다.


process.argv
- 첫 번째 요소는 process.execPath
- 두 번째 요소는 실행 중인 파일

- 스크립트에서 세 번째 인수를 전달할 수 있다.
const args = process.argv.slice(2);
for (let arg of args) {
console.log(`Hi there, ${arg}`);
}

319. 파일 시스템 모듈의 충돌 과정
- Node에는 상당한 양의 내장 메서드와 다양한 특성이 있다.
- fs(file system) 모듈
- 가져와서 써야 한다.
require
- 파일이나 폴더를 삭제하는 메서드는 동기/비동기 두 가지 방식이 있다.
- 동기 메서드는 작업이 끝날 때까지 모든 프로세스를 차단
const fs = require('fs');
// console.log(fs);
// 비동기 메서드
fs.mkdir('Dogs', { recursive: true }, (err) => {
console.log('IN THE CALLBACK!')
if (err) throw err;
})
// 동기 메서드
fs.mkdirSync('Cats');
console.log('I COME AFTER MKDIR IN THE FILE!');
- 사이에 코드를 추가하기 전에 디렉토리 생성부터 해야 한다면 동기 메서드를 사용하는 것이 쉽다.
mkdirSync
const fs = require('fs');
const folderName = process.argv[2] || 'Project';
// argv의 0번째 인수는 실행 가능한 경로, 1번째는 파일 경로
// argv의 2번째 인자는 사용자가 처음으로 작성한 인수
// 이를 변수로 저장하고 디폴트를 'Project'로 작성해 주었다.
fs.mkdirSync(folderName);
- 파일 생성 메서드는
writeFile
과writeFileSync
가 있다.
- 파일에 데이터를 기록하거나 기존 파일을 대체한다.
const fs = require('fs');
const folderName = process.argv[2] || 'Project';
try {
fs.mkdirSync(folderName);
fs.writeFileSync(`${folderName}/index.html`);
fs.writeFileSync(`${folderName}/styles.css`);
fs.writeFileSync(`${folderName}/app.js`);
} catch (e) {
console.log('SOMETHING WENT WRONG!!');
console.log(e);
}
321. Module.exports 사용하기

- app.js에서 math.js 파일을 불러오더라도, math.js 파일 안에서 명시적으로 특정 항목을 내보내라고 명령하지 않는 한 app.js에서는 아무것도 가져오지 않는다.
- 자동으로 파일 내 모든 항목에 액세스할 수는 없다.
module.exports
는 기본 값으로 객체에 해당하는 특성이 있다.- 아무 것도 적지 않아서 빈 객체를 내보낸 것
- 액세스 하려는 항목을 프로퍼티든 함수나 변수든 module.exports에 포함시켜줘야 한다.
const add = (x, y) => x + y;
const PI = 3.141592;
const square = x => x * x;
module.exports.add = add;
module.exports.PI = PI;
module.exports.square = square;
const { PI, square } = require('./math');
console.log(PI);
console.log(square(9));
const add = (x, y) => x + y;
const PI = 3.141592;
const square = x => x * x;
const math = {
add: add,
PI: PI,
square: square
}
module.exports = math;
module.exports.add = (x, y) => x + y;
module.exports.PI = 3.141592;
module.exports.square = x => x * x;
- module을 생략한
exports
단축어 구문으로 사용할 수도 있다.
322. 디렉터리의 필요성
const blue = require('./blue');
const janet = require('./janet');
const sadie = require('./sadie');
const allCats = [blue, janet, sadie];
module.exports = allCats;
const cats = require('./shelter');
console.log('REQUIRED AN ENTIRE DIRECTORY!', cats);
- 디렉토리를 불러오기 하면, 디렉토리의 index.js가 내보내는 것을 가져오게 된다.
- 라이브러리를 만들거나 라이브러리로 작업을 할 때도 index.js가 중요한 역할을 한다.
323. NPM 개요
- 패키지는 다른사람이 쓴 코드로, 설치해서 프로젝트에 통합시킬 수 있게 쓰여진 코드다.
- NPM은 그런 노드 패키지를 위한 표준화된 저장소로, Node Package Manager를 뜻한다.
- 노드 커뮤니티는 보통 NPM을 지칭한다.
- Node는 NPM 명령줄 도구(command line tool)와 같이 제공되어서, 손쉽게 이 패키지들을 설치하고 관리할 수 있게 해준다.
324. 패키지 설치하기 - Jokes와 Rainbow
- 패키지 설치해보기
npm install
- 설치할 때는 정확한 패키지 이름을 적었는지 유의해야 한다.
npm i give-me-a-joke
- 설치 후 node_modules 폴더를 보면 많은 항목들이 있는데, 설치한 패키지에 대한 dependencies 의존성에 해당된다.
- 패키지를 불러오는 방법은 파일의 경로를 쓰지 않는다는 것이다.
node_modules/give-me-a-joke/index
❌ 그냥 패키지의 이름을 참조하면 된다.
const jokes = require('give-me-a-joke'); // ⭕
console.dir(jokes);

npm i colors
const jokes = require('give-me-a-joke');
const colors = require('colors');
jokes.getRandomDadJoke((joke) => {
console.log(joke.rainbow);
console.log(joke.inverse);
})

325. 글로벌 패키지 추가하기
- 패키지의 버전 관리를 package.json에서 한다.
- 프로젝트 디렉토리 같은 하위 디렉토리가 아니라,
전체 계정 및 전체 사용자에 대해 전역 설치 하려면
-g
플래그를 추가하면 된다.
npm i -g cowsay

cowsay 안뇽
326. Package.json의 중요성
- package.json은 모든 노드 앱에 들어가고, 특정 프로젝트나 패키지, 앱에 대한 메타 데이터 즉, 정보를 가지고 있다.
- description, license, name, version, dependencies, ...
- 보통 NPM 명령어를 통해 만들어 진다.
npm init

- 여러 정보들을 묻고나면 package.json 파일이 생성된다.

npm i figlet

- 설치하면 하단에 dependencies가 붙는다.
- 이렇게 이 앱에서 사용하고 있는 모든 것들을 기록한다.
figlet과 colors 설치
const figlet = require('figlet');
const colors = require('colors');
figlet('Hello World!!', function (err, data) {
if (err) {
console.log('Something went wrong...');
console.dir(err);
return;
}
console.log(data.rainbow);
});

- 유용한 이유 1) 착수 중인 작업에 대한 기록을 남길 수 있고 2) 다른 사람 또는 다른 기기에서 스스로에게 이 파일을 공유할 때 모든 디펜던시들을 한번에 다운로드할 수 있기 때문
327. 한 프로젝트에 대한 모든 종속 요소 설치하기
- npm install을 실행하고 package.json 파일에 기반하여 디펜던시 설치
- 더이상 쓰지 않게 된 디펜던시는 package.json에서 삭제하면 된다.
328. 언어 맞추기 프로젝트 도전
npm i franc langs
- ‣
- ‣
// const franc = require('franc');
// const langs = require('langs');
import { franc } from 'franc';
import langs from 'langs';
import colors from 'colors';
const input = process.argv[2];
const langCode = franc(input);
if (langCode === 'und') {
console.log("SORRY, COULDN'T FIGURE OUT! TRY WITH MORE SAMPLE TEXT!")
}
const language = langs.where('3', langCode);
console.log(`Our best guess is: ${language.name}`.rainbow);
- require이 안먹혀서 import 구문을 쓰고 package.json에
"type": "module",
을 추가해주었다.
330. Express 개요
- Express는 웹 개발을 위한 프레임워크로, Node를 사용하여 서버를 생성하고 실행할 수 있다.
- 웹 애플리케이션 개발을 위해 다양한 메서드, 애드온, 플러그인을 제공한다. API 생성 등의 작업도 도와준다.
- 요청을 받아들일 서버 구축을 돕고, 들어오는 요청(request)을 파싱하는데, HTTP 요청이 텍스트 정보이기 때문이다. (JavaScript 객체가 아님)
- /about 이나 /contact 등과 같이 어떤 요청인지에 따라 실행되는 다양한 코드와 함수를 작성할 수 있다.
- 응답(response)을 만드는 것도 도와준다. 상태 코드 설정, 헤더 등의 콘텐츠를 어떻게 응답할 지 설정
- 프레임워크와 라이브러리는 다른 사람이 작성한 코드이고, NPM 같은 도구를 이용해 다운로드한다.
- 단일 스크립트이거나 Axios를 쓴다면 HTTP 요청 라이브러리
- 프레임워크의 목표와 목적에는 차이가 있는데, 제어와 제어의 역전이다. (control / inversion of control)
- 라이브러리는 언제든지 코드에 결합할 수 있고, 어떻게 사용할지를 결정하는 메서드와 기능을 제공한다. 제어권을 우리가 갖는다.
- 프레임워크는 구조를 제공하지만 제어를 역전시켜서 우리는 제어에 거의 관여하지 않는다. 프레임워크의 작동 원리에 따라 코드를 작성하기만 하면 된다. 왜냐면 풀 애플리케이션의 개발을 프레임워크가 돕기 때문이다.
- 라이브러리는 그보다 작은 HTTP 요청이나 단일 사용 단일 목적의 역할만 한다.
- 개발 속도와 프레임워크가 제공하는 기능을 얻는 대신, 코드를 작성하는 부분에서 유연성과 자유를 놓치게 된다.
- 파일에 뭘 넣는지, 어느 폴더에 저장하는지도 제어하니 우리가 많은 제어권을 포기해야 한다. 하지만 배우는 단계를 넘어서면 빠른 앱 개발을 도와준다.
331. 우리의 첫 번째 Express 앱
FirstApp 폴더 생성 후
npm init -y
(질문 생략)익스프레스 설치
npm i express
- 구버전 NPM에서 패키지를 package.json에 저장하려면
--save
를 입력해야 했다. (신버전에서는 넣을 필요 없음)
const express = require('express');
const app = express();
app.listen
서버가 요청을 받게 해보기
첫 번째 인수로 포트 번호, 두 번째 인수로 콜백 함수를 추가해서
앱이 요청을 받기 시작하거나 포트로 요청을 받으면 함수가 실행되게 할 수 있다.
app.listen(3000, () => {
console.log('LISTENING ON PORT 3000!')
})
- 로컬 호스트는 사용하는 로컬 기기를 의미한다.

- Cannot GET / 은 서버는 있지만 응답을 받지 못했다는 뜻
- Express 앱을 여러 개 실행해야 하면 포트 번호를 각각 다르게 지정하면 된다.
app.use
- 서버에 요청이 들어오면 콜백을 실행시킨다.
- 요청이 어디에서(/home이든 /contact든) 어떤 형태로(GET, POST, ...) 들어오든 상관 없이 요청(request)이라면 모두 작동한다.
const express = require('express');
const app = express();
app.use(() => {
console.log('WE GOT A NEW REQUEST!!');
});
app.listen(3000, () => {
console.log('LISTENING ON PORT 3000!');
});
332. 요청 및 응답 객체
- 들어오는 모든 요청은
app.use
함수에서 자동으로 전달되는 두 개의 매개변수에 접근한다. req
들어오는 요청을 의미하는 객체res
응답을 의미하는 객체
Express가 두 객체를 전달하면 요청 객체는 들어오는 요청인 HTTP 요청을 받아들이고, 응답 객체는 요청을 한 누군가에게 보내질 응답을 생성하는 데 쓰인다.
- HTTP 요청은 JavaScript객체가 아니라 텍스트 정보다.
- Express는 자동으로 HTTP 요청 정보를 파싱해 JavaScript 객체로 변환하고, app.use의 콜백 함수의 첫 번째 인수로 전달한다.
- 응답 객체의 메서드
res.send
는 HTTP 요청을 보낸다. 예를 들어, 자동으로 HTTP 응답 헤더 필드를 할당한다.
const express = require('express');
const app = express();
app.use((req, res) => {
console.log('WE GOT A NEW REQUEST!!');
res.send('<h1>This is my webpage!</h1>');
});
app.listen(8080, () => {
console.log('LISTENING ON PORT 8080!');
});
333. Express 라우팅 기초
- 각 요청에 맞는 다른 콘텐츠로 응답하기
- 라우팅 - 요청과 요청된 경로를 가져와서 응답을 갖는 어떠한 코드에 맞추는 것
app.get
- 일치하는 경로 이름과 콜백 함수를 인수로 받는다.
const express = require('express');
const app = express();
// app.use((req, res) => {
// console.log('WE GOT A NEW REQUEST!!');
// res.send('<h1>This is my webpage!</h1>');
// });
app.get('/cats', (req, res) => {
// console.log('CAT REQUEST!');
res.send('MEOW!!');
});
app.get('/dogs', (req, res) => {
res.send('WOOF!!');
});
app.listen(8080, () => {
console.log('LISTENING ON PORT 8080!');
});
- app.use를 주석처리한 이유는 res.send로 응답을 보낼 때마다 하나의 요청으로 끝나기 때문이다.
- 모든 요청에 응답한다.
- 하나 이상의 응답을 얻는 HTTP 요청을 받을 수 없다.
- 홈(루트) 라우트와 post 요청 추가하기
const express = require('express');
const app = express();
// app.use((req, res) => {
// console.log('WE GOT A NEW REQUEST!!');
// res.send('<h1>This is my webpage!</h1>');
// });
app.get('/', (req, res) => {
res.send('This is the home page!')
})
app.post('/cats', (req, res) => {
res.send('POST REQUEST TO /cats!!')
})
app.get('/cats', (req, res) => {
// console.log('CAT REQUEST!');
res.send('MEOW!!');
});
app.get('/dogs', (req, res) => {
res.send('WOOF!!');
});
app.listen(8080, () => {
console.log('LISTENING ON PORT 8080!');
});
- 제네릭(generic) 응답을 만들어서 라우트와 일치하지 않을 때 응답할 내용을 작성할 수 있다.
app.get('*', () => {})
- 가장 끝에 위치해야 한다!
app.get('*', (req, res) => {
res.send(`I don't know that path!`)
})
334. Express 경로 매개변수
req.params
경로 매개변수
라우트의 콜론
:
뒤에 경로를 정의하는 문자열을 넣고, 키-값 쌍이 있는 req.params
를 사용하면 된다.- 라우트 제네릭 패턴 정의하기
- subreddit 명에 따라 콘텐츠를 바꾸려면 req 객체를 통해 이름에 접근하면 된다.
app.get('/r/:subreddit', (req, res) => {
console.log(req.params);
res.send('THIS IS A SUBREDDIT!')
})

- 정확히 매치되는 라우트가 아닌 경우, 경로 매개변수(params)나 경로 변수를 라우트나 패턴에 설정하는 기본 방법
app.get('/r/:subreddit', (req, res) => {
const { subreddit } = req.params;
res.send(`<h1>Browsing the ${subreddit}</h1>`);
});
- 중첩 라우팅
app.get('/r/:subreddit/:postId', (req, res) => {
const { subreddit, postId } = req.params;
res.send(`<h1>Viewing Post ID: ${postId} on the ${subreddit} subreddit</h1>`);
});
335. 쿼리 문자열 작업하기
- 쿼리란 URL의 일부로 물음표
?
뒤에 위치 - 쿼리스트링의 한 부분으로써 키-값 쌍으로 정보를 담을 수 있다.
- 쿼리스트링을 파싱하고 Express 앱에서 접근하는 것은 중요하다.
req.query
쿼리스트링 파싱
app.get('/search', (req, res) => {
console.log(req.query);
res.send('HI!');
});


app.get('/search', (req, res) => {
const { q } = req.query;
if (!q) {
res.send('NOTHING FOUND IF NOTHING SEARCHED!');
}
res.send(`<h1>Search results for: ${q}</h1>`);
});
336. Nodemon을 사용한 자동 재시작
코드를 변경할 때마다 재시작하지 않으려면?
nodemon
- nodemon을 실행하면 노드 대신 파일의 변경 사항을 체크하고, 변화를 감지할 때마다 서버를 재시작한다.
npm i -g nodemon
nodemon -v

- 파일을 변경하고 저장할 때마다 nodemon이 이를 감지하고 재시작한다.
337. 섹션 주제
- Express 앱은 매우 기본적이며, HTML이나 텍스트의 문자열에 대해 응답
- 완전한 응답 페이지나 완전한 웹사이트가 아님
- 응답할 수 있는 수준 높이기 ⇒ 템플레이팅
- EJS (Embedded JavaScript) templates 알아보기
- 템플릿 구축을 위해 JavaScript 코드, Excel JavaScript 구문을 HTML 내부에 포함할 수 있는 것
338. 템플레이팅이란?
- 정적인 HTML 코드를 쓰는 대신 정보와 로직을 넣어서 루프로 여러 번에 걸쳐 템플릿을 반복하는 것을 말한다.
- 최종 목표는 특정 로직과 HTML 응답 생성을 결합하는 것
- Nunjucks
- Pug
- Handlebars
- 변수 추가 후 HTML에 평가될 이중 중괄호
{{ }}
사용. 조건문 및 루프도 사용 가능
339. EJS용 Express 구성하기
- 새 Express 앱 만들기
npm init -y
npm i express
- Express에 우리가 사용하고자 하는 템플레이팅 엔진을 알리기
npm i ejs
설치app.set(’view engine’, ‘ejs’);
view 엔진을 ejs로 설정하면 ejs 파일을 찾는다.mkdir views
touch views/home.ejs
- app.get이나 라우트 안에 문자열 대신 파일, 템플릿 보내기
res.render
메서드
const express = require('express');
const app = express();
app.set('view engine', 'ejs');
app.get('/', (req, res) => {
res.render('home')
});
app.listen(3000, () => {
console.log('LISTENING ON PORT 3000');
});
340. 뷰 디렉토리 설정하기
- 현재 views의 디렉토리 확인하기
process.cwd()
- index.js 뒤에 /views가 붙게 경로 설정하기
const path = require('path');
path 모듈 불러오기- path 모듈은 파일과 디렉토리 경로에 관한 메서드를 제공
- path의
join
메서드로 전체 디렉토리에 /views 경로 붙이기
const express = require('express');
const app = express();
const path = require('path');
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, '/views'))
app.get('/', (req, res) => {
// res.send('HI');
res.render('home')
});
app.listen(3000, () => {
console.log('LISTENING ON PORT 3000');
});
341. EJS 보간 구문

- EJS language support 익스텐션 설치
<%= %>
구문 - 평가된 값을 템플릿으로 출력해 준다.- 간단한 연산, 메서드 넣어보기
<%= 4 + 5 + 1 %>
<%= 'hello world'.toUpperCase() %>
- 보통은 데이터베이스나 템플릿에 제공될 다른 곳의 데이터를 사용할 때 쓰인다.
342. 템플릿에 데이터 전달하기
<body>
<h1>Your random number is:
<%= Math.floor(Math.random() * 10) + 1 %>
</h1>
</body>
- 난수를 넘겨줘서 렌더링 하는 방법
app.get('/rand', (req, res) => {
const num = Math.floor(Math.random() * 10) + 1;
res.render('random', { rand: num });
});
<h1>Your random number is: <%= rand %></h1>
- 라우트에서
res.render
의 두 번째 인수를 통해 템플릿으로 데이터를 넘기면, 템플릿에서는 해당 이름의 변수에 접근할 수 있다.
343. 서브레딧 템플릿 데모
res.params
활용
app.get('/r/:subreddit', (req, res) => {
const { subreddit } = req.params;
res.render('subreddit', { subreddit });
});
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= subreddit %></title>
</head>
<body>
<h1>Browsing The <%= subreddit %> Subreddit</h1>
</body>
</html>

본 스터디는 Udemy의 <【한글자막】 The Web Developer 부트캠프 2022> 강의를 활용해 진행됐습니다. 강의에 대한 자세한 정보는 아래에서 확인하실 수 있습니다.
프밍 스터디는 Udemy Korea와 함께 합니다.