Handlebars 사용방법의 모든 것: Nodejs 뷰 템플릿 엔진
NodeJS와 함께 널리 사용하는 뷰템플릿 엔진인 Handlebars에 대해 정리하겠습니다.
1. Handlebars
Handlebars는 JavaScript 템플릿 엔진입니다.
웹 애플리케이션에서 동적으로 HTML을 생성하는 데 사용되는데요.
중괄호'{{ }}'를 사용하여 템플릿 내에 변수, 조건문, 반복문 등을 삽입해 줍니다.
HTML형식을 해치지 않으면서도,
서버에서 데이터를 가져와 동적인 HTML을 생성하는 데 유용합니다.
2. Handlebars 설치
아래는 npm을 이용해서, handlebars의 모듈을 설치하는 코드입니다.
npm install express-handlebars --save
3. 중괄호 문법
handlebars 를 설정하고 사용하기 전에, 핵심이 되는 중괄호 문법을 보겠습니다.
간단하기 때문에, 쓱 읽어만 보아도 이해하실 수 있습니다.
3-1. 데이터 출력
handlebars에서는 데이터를 {{ }} 와 같이 중괄호들로 출력해 줍니다.
서버에서 렌더링시 name이라는 변수 '서버에서 넘겨준 이름'이라는 데이터를 저장해 넘겨주었다면,
이를 아래와 같이 중괄호 2개로 감싸서 출력할 수 있습니다.
<p>{{name}}</p>
위의 결과물은 아래와 같이 보입니다.
<p>서버에서 넘겨준 이름</p>
만약 서버에서 객체를 넘겨 주었다면, 아래와 같이 사용해 데이터를 출력하는 것도 가능합니다.
<p>{{user.name}}</p>
3-2. 이중 중괄호 vs 삼중 중괄호
Handlebars 템플릿 엔진에서 데이터를 출력할 때는 중괄호를 사용하는데요.
중괄호 2개를 사용하는 이중 중괄호 '{{}}'와 삼중중괄호 '{{{}}}'는,
데이터를 HTML에 삽입하는 방식에 있어 중요한 차이를 가집니다:
구분 | 내용 | 예 |
{{ }} | 데이터를 출력할 때 사용. HTML 태그를 포함하는 문자열이 이스케이프 처리됨 태그가 문자열로 표시되므로, XSS(Cross-Site Scripting) 공격을 방지하는 데 도움이 됨 |
const data = "<script>alert('XSS');</script>"; {{data}}로 데이터를 html에서 표시하면, <script>alert('XSS');</script> 와 같이 이스케이프 처리되 스크립트가 실행 안됨 |
{{{ }}} | HTML 이스케이프 없이 데이터를 출력 변수의 값에 포함된 HTML 태그가 브라우저에 의해 파싱되, DOM의 일부로 렌더링 됨 |
const data = "<strong>Important</strong>"; {{{data}}}를 사용하면, 출력은 <strong>Important</strong>로 됨 |
이 정도만 알고 있어도 많은 활용을 할 수 있는데요.
이제 실제로 express에서 handlebars를 뷰템플릿으로 설정해주고 사용해 보겠습니다.
4. express 설정
4-1. 뷰 템플릿 설정
nodejs 의 express에서 handlebars를 사용하려면,
아래 예제와 같이 해 주어야 합니다.
'app.set('views', './views');' 부분은,
뷰템플릿 디렉토리가 되는 'views'폴더안에,
hbs확장자를 가지는 handlebars 파일을 집어넣어주겠다는 의미입니다.
const express = require('express');
const { engine } = require('express-handlebars');
const app = express();
app.engine('hbs', engine({
extname: '.hbs',
defaultLayout: false
}));
app.set('view engine', 'hbs'); # 뷰엔진으로 handlebars 설정
app.set('views', './views');
// 라우트 설정
app.get('/', (req, res) => {
res.render('home', {name: '김군'});
});
// 서버 시작
app.listen(3000, () => console.log(`Server is running on http://localhost:${PORT}`));
위에서 app.engine()호출시 다음의 값들을 변경해 주었는데요.
각각은 확장자변경과, 커스텀 레이아웃파일사용이라는 의미를 가집니다.
- extname: 확장자를 변경
- 기본값은 handlebars라서 길기 때문에, 'hbs'로 변경해 주었습니다.
- defaultLayout: 기본 레이아웃 파일을 사용하지 않음
- false로 하지 않으면 handlebars가 디폴트 값으로 포함하고 있는 레이아웃 구조의 뷰를 찾습니다.
4-2. view에 데이터 전달
view에 데이터를 전달할 때는,
res.render()의 2번째 인자에 객체를 넣어줍니다.
app.get('/', (req, res) => {
res.render('home', {name: '김군'});
});
아래와 같이 hbs파일을 작성해 주고,
중괄호 2개로 감싸서, 객체의 name의 value 값을 출력할 수 있습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link rel="stylesheet" href="/assets/style.css">
</head>
<body>
<h1>안녕하세요 {{name}}</h1>
<a href="/profile">프로필 페이지로 이동합시다.</a>
</body>
</html>
5. 정적파일 사용하기
5-1. 정적파일
handlebars를 이용해 뷰를 그릴 때,
image, css, javascript 파일 등 정적파일들이 필요한데요.
보통은 express 프로젝트 폴더의 'public'폴더에 저장하고 여기에 액세스를 합니다.
첫번째 인자에 '/assets'을 넣었는데요.
이렇게 하면 가상 경로(prefix)를 제공해서,
브라우저의 URL주소상에 '/assets'라는 주소를 통해서,
두번째 인자에 들어간 정적파일의 실제디렉토리에 접근하게 해 줍니다.
가상인 '/assets'와 실제 디렉토리인 '/public'폴더를 매칭해준다고 생각하면 쉽습니다.
app.use('/assets', express.static(path.join(__dirname, 'public')))
handlebars파일에서는 정적파일의 위치를 '/assets'로 시작해 접근하면 되겟지요.
예를 들어, css파일의 경우, '/assets/css/style.css' 로 handlebars에서 아래와 같이 접근해 주면 됩니다.
<!DOCTYPE html>
<html lang="en">
<head>
...
<link href="../public/css/style.css" rel="stylesheet">
</head>
가상의 디렉토리인 '/assets'가 아니라,
'root'의 '/public'에서 그대로 사용하고자 할 경우에는 아래와 같이 해주면 됩니다.
- app.use(express.static(path.join(__dirname, "public")))
5-2. Nginx를 사용하는 경우
실제 프로덕션 레벨에서는,
정적파일은 nginx가 맡아서 하고,
express같은 웹앱은 동적인 데이터를 담당하도록 분리하게 됩니다.
아래는 nginx에서 정적파일에 대해 proxy_pass를 정의한 conf 파일입니다.
'/assets/'로 들어오는 url요청에 대해서,
서버내의 'srv/http/test.io/assets/'폴더에서 찾아서 처리해 줍니다.
server {
listen 80;
server_name test.com www.test.com;
location / {
proxy_pass http://localhost:3000; # Express 앱으로 요청을 전달
proxy_http_version 1.1;
proxy_set_header Host $host;
}
location ^~ /assets/ {
alias /srv/http/test.io/assets/;
access_log off; # 정적 리소스에 대해서는 로그를 하지 않도록 함
expires 30d;
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
}
이제 express의 handlebars에서는,
'/assets'로 시작하는 url로 정적파일을 아래와 같이 사용해 주기만 하면 됩니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test Site</title>
<link href="/static/css/dist/style.css" rel="stylesheet">
</head>
<body>
<h1>Welcome to Our Website</h1>
<img src="/assets/images/logo.png" alt="Logo">
</body>
</html>
6. 템플릿 렌더링
아래는 서버에서 Handlebars 템플릿을 이용해서, home뷰를 렌더링 합니다.
데이터로 msg 를 전달하고 있습니다.
app.get('/', (req, res) => {
res.status(200).render('home', { msg: '핸들바 렌더링 메세지' });
});
이렇게 서버로 부터 받은 msg 는,
위에서 보았던 중괄호 문법을 사용해,
html에서 아래와 같이 보여줄 수 있습니다.
<!DOCTYPE html>
<html>
<head>
<title>핸들바 테스트</title>
</head>
<body>
<h1>{{msg}}</h1>
</body>
</html>
7. if문의 렌더링
handlebars를 이용해서, if문도 구현할 수 있습니다.
nodejs의 express에서 라우트를 아래와 같이 설정햇다고 가정해 보겠습니다.
render의 2번째 인자로 객체로, loggedIn과 username을 전달해 줍니다.
app.get('/dashboard', (req, res) => {
const user = {
loggedIn: true,
username: "박이원"
};
res.render('dashboard', {
loggedIn: user.loggedIn,
username: user.username
});
});
이제 handlebars 파일에서 아래와 같이 설정해 줍니다.
handlebars에서 if문은 아래와 같이 구성됩니다.
if문 앞에 '#'을 사용해서 시작하고,
/if문으로 끝을 닫아준다는 점이 특이합니다.
{{#if loggedIn}}
...
{{else}}
...
{{/if}}
위의 if문을 적용해서,
loggedIn값이 true일 경우에는, username이 보이게 하고,
그렇지 않으면 guest라고 나오도록 해 보겠습니다.
<!DOCTYPE html>
<html>
<head>
<title>Dashboard</title>
</head>
<body>
{{#if loggedIn}}
<h1>Welcome back, {{username}}!</h1>
{{else}}
<h1>Welcome, guest!</h1>
{{/if}}
</body>
</html>
8. 반복(iteration) 다루기
handlebars에서 iteration을 어떻게 다루는지 보겠습니다.
다음과 같이 route를 구성한다고 가정해 보겠습니다.
여기서 users라는 배열을 넘겨주었으니,
handlebars 파일에서 활용해 주면 됩니다.
app.get('/users', (req, res) => {
const users = [
{ name: "park", age: 20 },
{ name: "lee", age: 23 },
{ name: "hyun", age: 31 }
];
res.render('usersList', { users });
});
html에서 반복을 다룰 때는,
아래와 같이 {{#each}} 을 사용해 주면 됩니다.
if문 처럼, {{/each}} 로 닫아줍니다.
이 때, {{this.name}}와 {{this.age}}로 현재 항목의 name과 age 속성을 참조할 수 있습니다.
<!DOCTYPE html>
<html>
<head>
<title>Users List</title>
</head>
<body>
<h1>Users List</h1>
<ul>
{{#each users}}
<li>{{this.name}} - {{this.age}} years old</li>
{{/each}}
</ul>
</body>
</html>
8. js코드 다루기
handlebars를 js에서 다루는데는 다음의 방법들이 있습니다.
8-1. 서버의 파일 가져와 사용하기
가장 간단한 방법은, html코드에서 서버의 static한 js파일을 직접 외부링크로 포함시키는 것 입니다.
아래와 같이, 서버의 public폴더아래에 있는 js폴더에,
js코드를 작성후 아래와 같이 작성해 주면 됩니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>테스트</title>
<script src="/static/js/index.js"></script>
</head>
'/static'이라는 static파일의 디렉토리는,
nodejs에서 직접 지정해 사용할 수도,
nginx같은 정적파일을 전달하는 서버에서 설정할 수도 있겠습니다.
다만, html 문서에 css나 js의 링크가 너무 많으면 로딩이 오래 걸리게 되므로,
이러한 점에 주의할 필요가 있습니다.
8-2. ajax 사용하기
페이지를 로딩한 후에,
클라이언트 사이드의 JavaScript를 사용해서,
서버에 AJAX 요청을 보내고, 응답으로 데이터를 받아 처리할 수 있습니다.
fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log(data);
// 데이터를 사용하여 페이지를 업데이트합니다.
});
위와 같이 하려면,
NodeJS같은 서버에서 아래와 같이 해당하는 경로에 대한 처리가 필요하겠지요.
router.get('/api/data', function(req, res) {
const data = { key: 'value' }; // 여기서 데이터를 생성하거나 데이터베이스에서 가져옵니다.
res.json(data);
});
Jquery를 사용할 수도 있습니다.
아래 함수들을 이용하면,
비동기적으로 서버로부터 데이터를 요청하고,
받아온 데이터를 DOM에 삽입할 수 있습니다.
- $.ajax()
- $.get()
- $.getJSON()
이상으로 nodejs의 뷰템플릿인 handlebars에 대해서 정리해 보았습니다.