본문 바로가기

WINK-(Web & App)/Express.js (Node.js) 스터디

[2023 백엔드 스터디] 목진협 #2주차 - 6.3~6.5장 Express 웹 서버 만들기 part2

반응형

6.3  Router 객체로 라우팅 분리하기

익스프레스를 사용하는 이유 중 하나는 바로 라우팅을 깔끔하게 관리할 수 있다는 점이다.

6.3.1 Express를 이용한 Router 분리 실습

이전 실습까지 작성했던 app.js의 app.get 같은 메소드가 라우터 파트이다. 라우터를 많이 연결하면 app.js 코드가 매우 길어지고 복잡해지기 때문에 Express에서는 라우터를 분리할 수 있는 방법을 제공한다.

라우터 실습을 위해 아래와 같이 routes폴더 안에 index.js와 user.js를 작성하고 app.use를 통해 app.js에 연결한다.

[index.js]
const express = require('express');

const router = express.Router();

// GET / 라우터
router.get('/', (req, res) => {
  res.send('Hello, Express');
});

module.exports = router;
[user.js]
const express = require('express');

const router = express.Router();

// GET /user 라우터
router.get('/', (req, res) => {
  res.send('Hello, User');
});

module.exports = router;

index.js와 user.js는 코드가 거의 비슷하지만, app.use로 연결할 때의 차이 때문에 다른 주소의 라우터 역할을 한다.

[app.js]
...
const path = require('path');

dotenv.config();

//index.js 연결
const indexRouter = require('./routes');
// require('./routes/index.js') == require('./routes') index.js 생략가능!

// user.js 연결
const userRouter = require('./routes/user'); 
...
  name: 'session-cookie',
}));

app.use('/', indexRouter);
app.use('/user', userRouter);

app.use((req, res, next) => {
  res.status(404).send('Not Found'); // 에러처리 미들웨어 위에 404 상태 코드를 응답하는 미들웨어 추가
});

app.use((err, req, res, next) => {
...

indexRouter는 app.use('/')에 연결 <-> userRouter는 app.use('/user')에 연결

indexRouter는 app.use의 '/'app.get의 '/'가 합쳐져 GET / 라우터가 되었고,

userRouter는 app.use의 '/user'get의 '/'가 합쳐져 GET /user 라우터가 된다.

 

여기서 라우터들을 app.use로 연결할 때 주소가 합쳐진다는 것을 기억해야 한다. [ ex. use('/') + get('/'), use('/user') + get('/') ]

최종적으로 서버를 실행한 뒤, localhost:3000localhost:3000/user로 접속하면 각각에 해당하는 응답을 받을 수 있다.

localhost:3000 > Hello, Express (index.js)
localhost:3000/user > Hello, User (user.js)

 

6.3.2  next('route')

next('route') : 라우터에 연결된 나머지 미들웨어들을 건너뛰고 싶을 때 사용한다.
router.get('/', (req, res, next) => {
  next('route');
  // 첫 번째 라우터의 첫 번째 미들웨어에서는 next() 대신 next('route')를 호출했는데, 
  // 이 경우에는 두 번째, 세 번째 미들웨어는 실행되지 않는다. 
  // 대신 해당 주소와 일치하는 다음 라우터로 넘어간다.
}, (req, res, next) => {
  console.log('실행되지 않습니다');
  next();
}, (req, res, next) => {
  console.log('실행되지 않습니다');
  next();
});
router.get('/', (req, res) => {
  console.log('실행됩니다');
  res.send('Hello, Express');
});

 

 

라우터가 몇 개든 간에 next()를 호출하면 다음 미들웨어가 실행된다.

 

6.3.3  유용한 Tip

라우터 주소에는 정규표현식을 비롯한 특수한 패턴을 사용할 수 있다.
자주 쓰이는 패턴으로는 '라우트 매개변수' 패턴이 있다.

Tip. 라우트 매개변수 패턴

router.get('/user/:id', (req, res) => {
  console.log(req.params, req.query);
});

위 코드를 살펴보면, 주소 부분에 :id가 있는데, 이는 문자 그대로 :id를 의미하는 것이 아니다. 이 부분에는 다른 값을 넣을 수 있는데,

예를 들어, /user/1 이나 /users/123 등의 요청도 이 라우터가 처리하게 된다.

 

라우트 매개변수 패턴의 장점

- :id에 해당하는 1이나 123을 조회할 수 있다

- req.params객체 안에 들어있어서 :id -> req.params.id로, :type -> req.params.type 으로 조회가 가능하다.

 

주의 해야할 점

- 다양한 라우터를 아우르는 와일드카드 역할을 하므로 일반 라우터보다 뒤에 위치해야 다른 라우터를 방해하지 않는다.

[틀린코드]
router.get('/user/:id', (req, res) => {
  console.log('얘만 실행됩니다.');
});
router.get('/user/like', (req, res) => {
  console.log('전혀 실행되지 않습니다.');
});

위의 코드와는 달리 /user/like 라우터/user/:id 같은 라우트 매개변수를 쓰는 라우터보다 위에 위치해야 한다.

[옳은코드]
router.get('/user/like', (req, res) => {
  console.log('전혀 실행되지 않습니다.');
});
router.get('/user/:id', (req, res) => {
  console.log('얘만 실행됩니다.');
});

- 주소에 쿼리스트링을 쓸 때도 있다.

쿼리스트링의 키-값 정보는 req.query 객체 안에 들어 있어서, 예를 들어 /users/123?limit=5&skip=10 이라는 주소의 요청이 들어왔을 때 req.params와 req.query 객체는 다음과 같다.

req.params : { id: '123'}

req.query : { limit: '5', skip: '10' }

 

6.3.4  마무리

1. 에러 처리

app.js에서 에러 처리 미들웨어 위에 넣어둔 미들웨어는 일치하는 라우터가 없을 때 404 상태 코드를 응답하는 역할을 한다.
미들웨어가 존재하지 않아도 익스프레스가 자체적으로 404 에러를 처리해주지만, 웬만하면 404 응답 미들웨어와 에러 처리 미들웨어를 연결해주는 것이 좋다.
app.use((req, res, next) => {
  res.status(404).send('Not Found');
});

이 미들웨어를 제거하고 localhost:3000/abc에 접속하면 404 상태 코드와 함께 Cannot GET /abc 메시지가 응답된다.

 

2. 라우터에서 자주 쓰이는 유용한 활용법 (app.route or router.route)

router.get('/abc', (req, res) => {
  res.send('GET /abc');
});
router.post('/abc', (req, res) => {
  res.send('POST /abc');
});

위와 같이 주소는 같지만 메소드는 다른 코드가 있을 때 이를 하나의 덩어리로 줄일 수 있다.

router.route('/abc')
  .get((req, res) => {
    res.send('GET /abc');
  })
  .post((req, res) => {
    res.send('POST /abc');
  });

위와 같이 router.route로 관련 있는 코드끼리 묶어, 더 보기 좋게 만들 수 있다.

 


 

6.4  req, res 객체 살펴보기

익스프레스의 req, res 객체는 http 모듈의 req, res 객체를 확장한 것으로, 기존 http 모듈과 익스프레스가 추가한 메소드나 속성을 사용할 수 있다. 예를 들어 res.writeHead, res.write, res.end 메소드를 그대로 사용할 수 있으면서, res.send나 res.sendFile 같은 메소드도 사용이 가능하다. 다만, 익스프레스의 메소드가 워낙 편리하므로 기존 http 모듈의 메소드는 잘 쓰이지 않는다.

1. req 객체

  • req.app : req 객체를 통해 app 객체에 접근할 수 있다. req.app.get('port')와 같은 식으로 사용할 수 있다.
  • req.body : body-parser 미들웨어가 만드는 요청의 본문을 해석한 객체.
  • req.cookies : cookie-parser 미들웨어가 만드는 요청의 쿠키를 해석한 객체.
  • req.ip : 요청의 ip주소가 담겨 있다.
  • req.params : 라우트 매개변수에 대한 정보가 담긴 객체.
  • req.query : 쿼리스트링에 대한 정보가 담긴 객체.
  • req.signedCookies : 서명된 쿠키들은 req.cookies 대신 여기에 담겨 있다.
  • req.get(헤더 이름) : 헤더의 값을 가져오고 싶을 때 사용하는 메소드.

2. res 객체

  • res.app : req.app처럼 res 객체를 통해 app 객체에 접근할 수 있다.
  • res.cookie(키, 값, 옵션) :  쿠키를 설정하는 메소드.
  • res.clearCookie(키, 값, 옵션) : 쿠키를 제거하는 메소드.
  • res.end() : 데이터 없이 응답을 보낸다.
  • res.json(JSON) : JSON 형식의 응답을 보낸다.
  • res.locals : 하나의 요청 안에서 미들웨어 간에 데이터를 전달하고 싶을 때 사용하는 객체.
  • res.redirect(주소) : 리다이렉트할 주소와 함께 응답을 보낸다.
  • res.render(뷰, 데이터) : 다음에 다룰 템플릿 엔진을 렌더링해서 응답할 때 사용하는 메소드.
  • res.send(데이터) : 데이터와 함께 응답을 보낸다. 데이터는 문자열, HTML, 버퍼, 객체, 배열로 주어진다.
  • res.sendFile(경로) : 경로에 위치한 파일을 응답한다.
  • res.set(헤더, 값) : 응답의 헤더를 설정.
  • res.status(코드) : 응답 시의 HTTP 상태 코드를 지정한다.

req와 res 객체의 메소드는 메소드 체이닝(method chaining)을 지원하는 경우가 많다.

[메소드체이닝 활용]
res
  .status(201)
  .cookie('test', 'test')
  .redirect('/admin');

다음과 같이 메소드 체이닝을 활용하면 코드양을 줄일 수 있다.

 


 

6.5  템플릿 엔진 사용하기

템플릿 엔진은 JavaScript를 사용해서 HTML을 렌더링할 수 있게 한다.
대표적인 템플릿 엔진으로는 퍼그(Pug)와 넌적스(Nunjucks)가 있다.

6.5.1 퍼그(제이드)

 

퍼그 설치하기
$ npm i pug

 

익스프레스와 연결하기 - app.js

[app.js]
...
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views')); // 파일들이 위치한 폴더 지정
app.set('view engine', 'pug'); // 템플릿 엔진 설정

app.use(morgan('dev'));
...

- views : 템플릿 파일들이 위치한 폴더를 지정하는 것이다.

- res.render 메소드 : 이 폴더 기준으로 템플릿 엔진을 찾아서 렌더링한다.

- res.render('index')라면 views/index.pug 렌더링 / res.render('admin/main')이라면 views/admin/main.pug 렌더링

- view engine : 어떠한 종류의 템플릿 엔진을 사용할지 나타낸다. (현재 pug로 설정되어 있다)

 

지금부터 퍼그의 문법을 알아보면서 HTML과는 어떻게 다른지 살펴보자

6.5.1.1 HTML 표현

 

퍼그와 HTML 비교

- 기존 HTML과 다르게 화살괄호( < > )와 닫는 태그( </> )가 없다.

- 랩 또는 스페이스로만 태그이 부모 자식 관계를 규명한다.

- 자식 태그는 부모 태그보다 들여쓰기되어 있어야하고, 들여쓰기에 오류가 있으면 제대로 렌더링되지 않으니 주의!

   밑의 코드에서 html, head, title 태그에서 자식 태그 들여쓰기 확인 가능.

- doctype html은 <!DOCTYPE html>과 같다.

- 화살괄호가 없으므로 태그의 속성도 다르게 표현한다. 태그명 뒤에 소괄호로 묶어 적는다.

퍼그(pug) HTML
doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
<!DOCTYPE html>
<html>
  <head>
    <title>익스프레스</title>
    <link rel="stylesheet" href="/style.css" />
  </head>
</html>

- 속성 중 아이디와 클래스가 있는 태그는 다음과 같이 표현할 수 있다. div 태그인 경우 div 문자는 생략 가능

퍼그(pug) HTML
#login-button
.post-image
span#highlight
p.hidden.full
<div id="login-button"></div>
<div class="post-image"></div>
<span id="highlight"></span>
<p class="hidden full"></p>

- HTML 텍스트는 다음과 같이 태그 또는 속성 뒤에 한 칸을 띄고 입력.

퍼그(pug) HTML
p Welcome to Express
button(type='submit') 전송
<p>Welcome to Express</p>
<button type="submit">전송</button>

- 에디터에서 텍스트를 여러 줄 입력하고 싶다면 다음과 같이 파이프( | )를 넣는다. (HTML 코드에서는 한줄로 나온다.)

퍼그(pug) HTML
p
  | 안녕하세요.
  | 여러 줄을 입력합니다.
  br
  | 태그도 중간에 넣을 수 있습니다.
<p>
  안녕하세요. 여러 줄을 입력합니다.
  <br />
  태그도 중간에 넣을 수 있습니다.
</p>

- style이나 script 태그로 CSS 또는 JavaScript 코드를 작성하려면 다음과 같이 태그 뒤에 점(.)을 붙인다.

퍼그(pug) HTML
style.
  h1 {
    font-size: 30px;
  }
script.
  const message = 'Pug';
  alert(message);
<style>
  h1 {
    font-size: 30px;
  }
</style>
<script>
  const message = 'Pug';
  alert(message);
</script>

 

6.5.1.2 변수

 

HTML과 다르게 JavaScript 변수를 템플릿에 렌더링할 수 있다.

// res.render를 호출할 때 보내는 변수를 퍼그가 처리한다.
[index.js]
router.get('/', (req, res, next) => {
  res.render('index', { title: 'Express' });
});

- res.render(템플릿, 변수 객체) : 익스프레스가 res 객체에 추가한 템플릿 렌더링을 위한 메소드.

- index.pug를 HTML로 렌더링하면서 { title: 'Express' }라는 객체를 변수로 설정.

- layout.pugindex.pugtitle 부분이 모두 Express로 치환된다, -> HTML에도 변수를 사용할 수 있게 된 셈!

 

res.render 메소드에 두 번째 인수로 변수 객체를 넣는 대신, res.locals 객체를 사용해서 변수를 넣을 수 있다.

router.get('/', (req, res, next) => {
  res.locals.title = 'Express';
  res.render('index');
});

위와 같이 코드를 작성하면, 템플릿 엔진이 res.locals 객체를 읽어서 변수를 집어넣는다.

- 이 방식의 장점은 라우터뿐만 아니라 다른 미들웨어에서도 res.locals 객체에 접근할 수 있다는 것이다.

- 따라서 다른 미들웨어에서 템플릿 엔진용 변수를 미리 넣을 수 있다.

이제 퍼그에서 변수를 사용하는 방법을 살펴보자
퍼그(pug) HTML
h1= title
p Welcome to #{title}
button(class=title, type='submit') 전송
input(placeholder=title + ' 연습')
<h1>Express</h1>
<p>Welcome to Express</p>
<button class="Express" type="submit">전송</button>
<input placeholder="Express 연습" />

서버로부터 받은 변수는 다양한 방식으로 퍼그에서 사용할 수 있다.

- 변수를 텍스트로 사용하기 >>   태그 뒤에 '=' 을 붙인 후 변수를 입력.

                                                 속성에도 '=' 을 붙인 후 변수를 사용할 수 있다.

- 텍스트 중간에 변수를 넣기 >>  #{변수} 

서버에서 데이터를 클라이언트로 내려보낼 때 #{ }와 =을 매우 자주 사용하니 꼭 기억하자!

 

내부에 직접 변수를 선언할 수도 있다. 

퍼그(pug) HTML
- const node = 'Node.js'
- const js = 'Javascript'
p #{node}와 #{js}
<p>Node.js와 Javascript</p>

- 빼기( - ) 를 먼저 입력하면 뒤에 JavaScript 구문을 작성할 수 있다.

 

퍼그는 기본적으로 변수의 특수 문자를 HTML 엔티티(entity)로 이스케이프(escape)(문법과 관련 없는 문자로 바꾸는 행위)한다.

이스케이프를 원치 않는다면 = 대신 != 을 사용하면 된다.

퍼그(pug) HTML
p= '<strong>이스케이프</strong>'
p!= '<strong>이스케이프하지 않음</strong>'
<p>&lt;strong&gt;이스케이프&lt;/strong&gt;</p>
<p><strong>이스케이프하지 않음</strong></p>

(HTML 엔티티와 이스케이프에 대한 추가 정보는 교재의 Note에서 확인!)

 

 

6.5.1.3 반복문

 

HTML과 다르게 반복문도 사용할 수 있고, 반복 가능한 변수인 경우에만 해당된다.

다음과 같이 each로 반복문을 돌릴 수 있다.

퍼그(pug) HTML
ul
  each fruit in ['사과', '배', '오렌지', '바나나', '복숭아']
    li= fruit
<ul>
  <li>사과</li>
  <li>배</li>
  <li>오렌지</li>
  <li>바나나</li>
  <li>복숭아</li>
</ul>

반복문 사용시 인덱스(index)도 가져올 수 있다.

퍼그(pug) HTML
ul
  each fruit, index in ['사과', '배', '오렌지', '바나나', '복숭아']
    li= (index + 1) + '번째 '  + fruit
<ul>
  <li>1번째 사과</li>
  <li>2번째 배</li>
  <li>3번째 오렌지</li>
  <li>4번째 바나나</li>
  <li>5번째 복숭아</li>
</ul>

 

6.5.1.4 조건문

 

조건문으로 편리하게 분기 처리할 수 있고 if, else if, else를 사용할 수 있다.

ex) isLoggedIn 변수로 로그인 여부에 따라 다르게 HTML을 렌더링하는 코드

퍼그(pug) HTML
if isLoggedIn
  div 로그인 되었습니다.
else
  div 로그인이 필요합니다.
<!-- isLoggedIn이 true일 때 -->
<div>로그인 되었습니다.</div>
<!-- isLoggedIn이 false일 때 -->
<div>로그인이 필요합니다.</div>

case문도 가능하다.

퍼그(pug) HTML
case fruit
  when 'apple'
    p 사과입니다.
  when 'banana'
    p 바나나입니다.
  when 'orange'
    p 오렌지입니다.
  default
    p 사과도 바나나도 오렌지도 아닙니다.
<!-- fruit이 apple일 때 -->
<p>사과입니다.</p>
<!-- fruit이 banana일 때 -->
<p>바나나입니다.</p>
<!-- fruit이 orange일 때 -->
<p>오렌지입니다.</p>
<!-- 기본값 -->
<p>사과도 바나나도 오렌지도 아닙니다.</p>

 

6.5.1.5 include

 

다른 퍼그나 HTML 파일을 include 할 수 있다.

- 헤더, 푸터, nav 처럼 웹을 제작할 때 공통되는 부분을 따로 관리할 수 있어 페이지마다 동일한 HTML을 넣어야하는 번거로움을 없앤다.

- 'include 파일 경로' 형태로 사용

퍼그(pug) HTML
header.pug
header
  a(href='/') Home
  a(href='/about') About

footer.pug
footer
  div 푸터입니다

main.pug
include header
main
  h1 메인 파일
  p 다른 파일을 include할 수 있습니다.
include footer
<header>
  <a href="/">Home</a>
  <a href="/about">About</a>
</header>
<main>
  <h1>메인 파일</h1>
  <p>다른 파일을 include할 수 있습니다.</p>
</main>
<footer>
  <div>푸터입니다.</div>
</footer>

 

6.5.1.6 extends와 block

 

레이아웃을 정할 수 있고, 공통되는 레이아웃 부분을 따로 관리할 수 있다.

퍼그(pug) HTML
layout.pug
doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/style.css')
    block style
  body
    header 헤더입니다.
    block content
    footer 푸터입니다.
    block script

body.pug
extends layout

block content
  main
    p 내용입니다.

block script
    script(src="/main.js")
<!DOCTYPE html>
<html>
  <head>
    <title>Express</title>
    <link rel="stylesheet" href="/style.css" />
  </head>
  <body>
    <header>헤더입니다.</header>
    <main>
      <p>내용입니다.</p>
    </main>
    <footer>푸터입니다.</footer>
    <script src="/main.js"></script>
  </body>
</html>

- 레이아웃이 될 파일에는 공통된 마크업을 넣되, 페이지마다 달라지는 부분을 block으로 비워둔다. (block[블록명] 형태로 block 선언)

- block이 되는 파일에서는 extends 키워드로 레이아웃 파일을 지정하고 block 부분을 넣는다. block 선언보다 들여쓰기되어 있어야..

- 나중에 익스프레스에서 res.render('body')를 사용해 하나의 HTML로 합쳐 렌더링할 수 있다.

- 퍼그 확장자는 생략 가능하고, block 부분이 서로 합쳐진다.

 

마무리

[layout.pug]
doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/style.css')
  body
    block content
[index.pug]
extends layout
// layout.pug의 block content 부분에 
// index.pug의 block content를 넣는다.
block content
  h1= title // res.render로 부터 title이라는 변수를 받아 렌더링
  p Welcome to #{title}
[error.pug]
extends layout
// block content가 layout.pug와 연결된다.
block content
  h1= message // res.render로부터 message와 error 변수를 받아 렌더링.
  h2= error.status 
  pre #{error.stack}

 

지금부터 또다른 템플릿 엔진인 '넌적스'에 대해서 살펴보자

6.5.2 넌적스

 

넌적스(Nunjucke)는 퍼그의 HTML 문법 변화에 적응하기 힘든 개발자에게 유용한 템플릿 엔진이며, 파이어폭스를 개발한 모질라에서 개발했다. HTML 문법을 그대로 사용하되 추가로 JavaScript 문법을 사용할 수 있고, 파이썬의 템플릿 엔진인 Twig와 문법이 유사하다.

넌직스 설치

$ npm i nunjucks

 

view engine을 퍼그 대신 넌적스로 교체.

[app.js]
...
const path = require('path');
const nunjucks = require('nunjucks');

dotenv.config();
const indexRouter = require('./routes');
const userRouter = require('./routes/user');

const app = express();
app.set('port', process.env.PORT || 3000);
app.set('view engine', 'html');

nunjucks.configure('views', { 
  express: app,
  watch: true,
});

app.use(morgan('dev'));
...

- configure의 첫 번째 인수로 views 폴더의 경로를 넣고, 두 번째 인수로 옵션을 넣는다.

- 이때 express 속성에 app 객체를 연결하고, watch 옵션이 true이면 HTML 파일이 변경될 때 템플릿 엔진을 다시 렌더링

 

지금부터 넌적스의 문법을 살펴보자. 퍼그와 예제가 동일하므로 비교하면서 공부하자

6.5.2.1 변수

 

res.render 호출 시 보내는 변수를 넌적스가 처리한다.

[넌적스]
<h1>{{title}}</h1>
<p>Welcome to {{title}}</p>
<button class="{{title}}" type="submit">전송</button>
<input placeholder="{{title}} 연습" />

넌적스에서 변수는 {{ }}로 감싼다.

 

내부에 변수를 사용할 수 있다.

변수 선언 : {% set 변수 = '값' %)   파이썬의 Twig와 문법이 비슷하다

넌적스 HTML
{% set node = 'Node.js' %}
{% set js = 'Javascript' %}
<p>{{node}}와 {{js}}</p>
<p>Node.js와 Javascript</p>

HTML을 이스케이프하고 싶지 않다면 {{ 변수 | safe }}를 사용한다.

넌적스 HTML
<p>{{'<strong>이스케이프</strong>'}}</p>
<p>{{'<strong>이스케이프하지 않음</strong>' | safe }}</p>
<p>&lt;strong&gt;이스케이프&lt;/strong&gt;</p>
<p><strong>이스케이프하지 않음</strong></p>

 

6.5.2.2 반복문

 

넌적스에서는 특수한 문을 {% %} 안에 쓴다. 반복문도 {% %}안에 쓰면 된다. (for in문과 endfor 사이에 위치)

넌적스 HTML
<ul>
  {% set fruits = ['사과', '배', '오렌지', ' 바나나', '복숭아'] %}
  {% for item in fruits %}
  <li>{{item}}</li>
  {% endfor %}
</ul>
<ul>
  <li>사과</li>
  <li>배</li>
  <li>오렌지</li>
  <li>바나나</li>
  <li>복숭아</li>
</ul>

반복문에서 인덱스를 사용하고 싶다면 loop.index라는 특수한 변수를 사용.

넌적스 HTML
<ul>
  {% set fruits = ['사과', '배', '오렌지', ' 바나나', '복숭아'] %}
  {% for item in fruits %}
  <li>{{loop.index}}번째 {{item}}</li>
  {% endfor %}
</ul>
<ul>
  <li>1번째 사과</li>
  <li>2번째 배</li>
  <li>3번째 오렌지</li>
  <li>4번째 바나나</li>
  <li>5번째 복숭아</li>
</ul>

 

6.5.2.3 조건문

 

조건문은 {% if 변수 %} {% elif %} {% else %} {% endif %} 로 이루어져 있다.

넌적스 HTML
{% if isLoggedIn %}
<div>로그인 되었습니다.</div>
{% else %}
<div>로그인이 필요합니다.</div>
{% endif %}
<!-- isLoggedIn이 true일 때 -->
<div>로그인 되었습니다.</div>
<!-- isLoggedIn이 false일 때 -->
<div>로그인이 필요합니다.</div>

case문은 없지만 elif(else if 역할)를 통해 분기 처리할 수 있다.

넌적스 HTML
{% if fruit === 'apple' %}
<p>사과입니다.</p>
{% elif fruit === 'banana' %}
<p>바나나입니다.</p>
{% elif fruit === 'orange' %}
<p>오렌지입니다.</p>
{% else %}
<p>사과도 바나나도 오렌지도 아닙니다.</p>
{% endif %}
<!-- fruit이 apple일 때 -->
<p>사과입니다.</p>
<!-- fruit이 banana일 때 -->
<p>바나나입니다.</p>
<!-- fruit이 orange일 때 -->
<p>오렌지입니다.</p>
<!-- 기본값 -->
<p>사과도 바나나도 오렌지도 아닙니다.</p>

{{ }} 안에서는 다음과 같이 사용한다.

넌적스 HTML
<div>{{'참' if isLoggedIn}}</div>
<div>{{'참' if isLoggedIn else '거짓'}}</div>
<!-- isLoggedIn이 true일 때 -->
<div>참</div>
<!-- isLoggedIn이 false일 때 -->
<div>거짓</div>

 

6.5.2.4 include

 

[넌적스]

[header.html]
<header> 
  <a href="/">Home</a> 
  <a href="/about">About</a> 
</header>

[footer.html]
<footer> 
  <div>푸터입니다.</div> 
</footer>

[main.html]
{% include "header.html" %} 
<main> 
  <h1>메인 파일</h1> 
  <p>다른 파일을 include할 수 있습니다.</p> 
</main> 
{% include "footer.html" %}

 

6.5.2.5 extends와 block

 

[넌적스]

[layout.html]
<!DOCTYPE html> 
<html> 
  <head> 
    <title>{{title}}</title> 
    <link rel="stylesheet" href="/style.css" /> 
    {% block style %} 
    {% endblock %} 
  </head> 
  <body> 
    <header>헤더입니다.</header> 
    {% block content %} 
    {% endblock %} 
    <footer>푸터입니다.</footer> 
    {% block script %} 
    {% endblock %} 
  </body> 
</html>

[body.html]
{% extends 'layout.html' %} 

{% block content %} 
<main> 
  <p>내용입니다.</p> 
</main> 
{% endblock %} 

{% block script %} 
<script src="/main.js"></script> 
{% endblock %}

- block 선언 : {% block[블록명] %},  block 종료 : {% endblock %}

- {% extends 경로 %} 키워드로 레이아웃 파일을 지정하고 block부분을 넣는다.

- 나중에 익스프레스에서 res.render('body')를 사용해 하나의 HTML로 합친 후 렌더링할 수 있다. 같은 이름의 block으로 합쳐진다.

 

마무리

 

[layout.html]
<!DOCTYPE html>
<html>
  <head>
    <title>{{title}}</title>
    <link rel="stylesheet" href="/style.css" />
  </head>
  <body>
    {% block content %}
    {% endblock %}
  </body>
</html>
[index.html]
{% extends 'layout.html' %}
// layout.html {% block content %} 부분에
// index.html의 {% block content %}를 넣는다.
{% block content %}
<h1>{{title}}</h1> // res.render로부터 title이라는 변수를 받아 렌더링.
<p>Welcome to {{title}}</p>
{% endblock %}
[error.html]
{% extends 'layout.html' %}
// {% block content %} 부분이 layout.html과 연결.
{% block content %}
<h1>{{message}}</h1> // res.render로부터 message와 error 변수를 받아 렌더링.
<h2>{{error.status}}</h2>
<pre>{{error.stack}}</pre>
{% endblock %}

 

 

6.5.3 에러 처리 미들웨어

 

404 응답 미들웨어와 에러 처리 미들웨어를 다음과 같이 수정해 에러 발생 시 error.html에 에러 내용을 표시.

[app.js]
...
app.use((req, res, next) => {
  const error =  new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  next(error);
});

app.use((err, req, res, next) => {
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
  res.status(err.status || 500);
  res.render('error');
});
...

- 만약 404에러가 발생한다면 next(error)에서 넘겨준 인수가 에러 처리 미들웨어의 err로 연결되기 때문에

   res.locals.message는 '${req.method} ${req.url} 라우터가 없습니다' 가 된다.

- 에러 처리 미들웨어는 error라는 템플릿 파일을 렌더링한다.

- 렌더링 시 res.locals.message와 res.locals.error에 넣어준 값을 함께 렌더링한다.

   res.render에 변수를 대입하는 것 외에도, res.locals 속성에 값을 대입해 템플릿 엔진에 변수를 넣을 수 있다.

- error 객체의 스택 트레이스(error.html의 error.stack)는 시스템 환경(process.env.NODE_ENV)이 production(배포 환경)이 아닌 경우에만 표시된다.

- 배포 환경인 경우에는 에러 메시지만 표시된다.  why? 에러 스택 트레이스가 노출되면 보안에 취약할 수 있기 때문에..

 

서버를 실행하고 localhost:3000/abc에 접속하면 에러 메시지와 함께 응답 코드, 스택 트레이스를 확인할 수 있다.

위 그림에서 404 아래의 Error 부분이 스택 트레이스다. 스택 트레이스를 통해 서버 폴더 구조를 유추할 수 있으므로 배포환경에서는 숨김.

 

6.6 함께 보면 좋은 자료

• Express 공식 홈페이지: http://expressjs.com
• 퍼그 공식 홈페이지: https://pugjs.org
• 넌적스 공식 홈페이지: https://mozilla.github.io/nunjucks
• morgan: https://github.com/expressjs/morgan
• body-parser: https://github.com/expressjs/body-parser
• cookie-parser: https://github.com/expressjs/cookie-parser
• static: https://github.com/expressjs/serve-static
• express-session: https://github.com/expressjs/session
• multer: https://github.com/expressjs/multer
• dotenv: https://github.com/motdotla/dotenv

마무리 Quiz

Q1. indexRouter는 app.use('/')에 연결하고, userRouter는 app.use('/user')에 연결했을 때의 주소는?
      (단 indexRouter, userRouter 각각, index.js와 user.js의 app.get('/')와 연결)

Q2. (1) 퍼그 문법 중에서, 변수를 텍스트로 사용할 때 태그나 속성 뒤에 붙이는 것은?
       (2) 퍼그 문법 중에서, 텍스트 중간에 변수를 넣을 때 사용하는 문법은?
       (3) 넌적스 문법 중에서, 변수를 선언하는 문법은?
       (4) 넌적스의 조건문 중에는 case문은 없다 ( O / X )

Q3. 시스템 환경(process.env.NODE_ENV)이 production(배포 환경)인 경우에는 에러메시지만 표시된다. 그 이유는?

출처

Node.js 교과서 개정 3판(조현영)

https://thebook.io/080334/

반응형