HTML 파일처럼 정적인 파일을 연결하는 것은 어렵지 않다.
클릭할 때마다 중간 내용이 바뀌는, 즉 사용자 요청에 따라 내용이 달라지는 컨텐츠를 동적인 컨텐츠라고 하는데, 이러한 동적인 컨텐츠를 보여주려면 템플릿 엔진이 필요하다.
템플릿 파일 : 데이터베이스에서 가져온 데이터 중 어떤 값을 어느 위치에 넣을지 미리 만들어 놓은 틀
템플릿 엔진 : 템플릿 파일을 만들고, 데이터베이스에서 가져온 동적인 데이터를 템플릿 파일에 연결하는 역할

템플릿 엔진은 템플릿 파일을 만들고 데이터베이스에서 가져온 데이터를 템플릿 파일에 연결하는 역할을 한다.
node.js에서 사용할 수 있는 템플릿 엔진은 종류가 여러 가지인데, 우리는 그 중 EJS 엔진을 사용해 볼 것이다.
앞으로 이 애플리케이션에서 EJS라는 뷰 엔진(템플릿 엔진)을 사용하겠다라고 애플리케이션 코드(app.js)에 지정을 해 줘야 한다.
app.set(키, 값)
view engine
뷰에서 사용할 템플릿 엔진을 설정한다.
예를 들어 EJS 엔진을 사용한다면 다음과 같이 작한다.
app.set("veiw engine", "ejs")
views
내가 만들어 놓은 템플릿 파일이 어디에 있는지도 views라는 속성을 통해 그 폴더도 지정을 해 주어야 한다. EJS 엔진에서는 기본적으로 views 폴더에 템플릿 파일을 저장한다.
app.set("views", "./views")
EJS 템플릿 엔진을 설치하고 사용하는 방법을 알아보자.
이 템플릿 엔진도 패키지 형태로 다운로드 해서 설치해야 한다.
npm i ejs
다음으로는 애플리케이션에서 EJS 라는 템플릿 엔진을 사용하겠다라고 알려 주자.
app이라는 서버를 만드는 코드 다음에다가 app.set 함수를 이용해 내가 사용할 템플릿 엔진이 무엇이고, 그 템플릿 파일들이 어디 저장되는지 알려주면 된다.
const app = express(); // app 서버 만드는 코드
app.set("view engine", "ejs");
app.set("views", "./views");
이러면 EJS 엔진을 사용하기 위한 세팅이 완료되었다.
템플릿 파일들이 views라는 폴더에 저장될 것이라고 했기 때문에 이 폴더를 만들어 줘야 한다. 이렇게 만든 views 폴더에 앞으로 템플릿 파일들을 저장하게 될 것이다.
ejs라는 템플릿 파일은 html과 아주 비슷하게 생겼다. 즉, 기존에 알던 웹 문서와 닮았다는 말이다. 그렇기 때문에 html과 css에 대해 알고 있다면 활용이 쉬울 것이다.
우리가 만들었던 컨트롤러 함수 중 contacts 경로로 요청을 하면 db의 모든 자료를 가져와서 화면에 보여주는 getAllContacts가 있었다.
const getAllContacts = asyncHandeler(async(req,res)=>{
const contacts = await Contact.find();
res.send(contacts);
});
이제 이 템플릿 파일을 이용해서 그걸 보여줄 수 있도록 만들어보자.
<!DOCTYPE html>
<html lang="en">
<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>Get All Contacts</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
* {
margin:0;
padding:0;
}
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
}
h1 {
font-family: Arial, Helvetica, sans-serif;
font-size: 3em;
margin-bottom: 20px;
}
h1 i {
margin-right: 0.8em;
}
h2 {
margin-bottom: 15px;
}
</style>
</head>
<body>
<h1><i class="fa-solid fa-address-card"></i>Contacts Page</h1>
</body>
</html>
전체 연락처를 보여주는 웹문서이다. 이 코드를 이용해 EJS 파일을 만들어 보도록 하겠다.
해당 코드를 복사 후 우리가 만든 views 폴더에 getAll.ejs 형태로 새 파일을 만들어 붙여 넣고 저장하자. EJS 엔진을 사용한 템플릿 파일의 확장자는 .ejs이다.
이렇게 템플릿 파일이 만들어지면 컨트롤러 함수에서 이 파일을 표시할 수 있다.
const getAllContacts = asyncHandeler(async(req,res)=>{
const contacts = await Contact.find();
res.render("getAll");
});
템플릿 파일을 표시할 때는 less.render 라는 함수를 사용한다. 파일 이름을 적을 때 확장자인 .ejs는 생략한다.
이렇게 getAll.ejs 파일을 화면에 그려달라고 지정했다.
이제 서버 실행 후 잘 작동되는지 브라우저로 한번 확인해보자.

다음과 같이 아이콘과 텍스트가 표시된다. 해당 내용이 잘 표시된 것을 확인할 수 있다.
방금 우리가 만들어 본 것은 아이콘과 텍스트 하나만 존재하는 정적인 페이지를 표시하는 방법이었다. 하지만 EJS 엔진을 사용하는 이유가 뭐였는가? 바로 db에서 가져온 값을 사용자 요청에 따라 동적으로 표시하기 위해서였다.
EJS 엔진을 사용할 때 변수 값을 어떻게 템플릿 파일에게 넘기는지 알아보도록 하자.
컨트롤러에서 템플릿 파일로 값을 넘길 때는 res.render 함수를 사용한다.
res.render(ejs 파일, {변수: 전송 자료})

getAll.ejs이라는 템플릿 파일을 사용할 것이라 지정하고, heading이라는 변수에 "User List"라는 값을 담아서 넘겨달라는 뜻이다.
이때, EJS 파일에서는 자료를 넘겨받을 텐데, 그 자료가 들어갈 위치에 heading이라는 변수를 넣어주라고 표시를 해 두어야만 그 자리에 값이 나타날 것이다. 그렇게 표시하는 것을 ejs 태그라고 한다.
이 ejs 태그에는 여러 가지가 있다. 그 중 자주 사용하는 세 가지만 보도록 하자.
- <%= 변수 %>
- <% 자바스크립트 코드 %>
- <%- html 코드 %>
직접 코딩하면서 확인해보자.
const getAllContacts = asyncHandeler(async(req,res)=>{
const contacts = await Contact.find();
res.render("getAll");
});
이렇게 우리가 수정했던 getAllContacts 함수에서는 getAll이라는 템플릿 파일을 표시하도록만 했고, 아직 변수 값이 넘어간 것이 없다.
임의로 변수를 생성하고 변수를 한번 넘겨보도록 하자.
const users = [
{name:"Kim", email:"kim@abc.def", phone:"12345"},
{name:"Lee", email:"lee@abc.def", phone:"56789"}
users라고 하는 배열 형태의 변수를 만들었고, 2개의 사용자 정보가 들어갔다.
이 users를 템플릿 파일한테 넘겨주고 그 안에 있는 내용을 표시하려고 한다.
이 변수를 템플릿 파일 getAll.ejs에게 넘겨줘야 하기 때문에 render 함수 안에 {} 로 묶어서 넘겨줄 변수를 지정해 주면 된다.
const getAllContacts = asyncHandeler(async(req,res)=>{
const contacts = await Contact.find();
const users = [
{name:"Kim", email:"kim@abc.def", phone:"12345"},
{name:"Lee", email:"lee@abc.def", phone:"56789"}
]
res.render("getAll", {users: users});
});
users라는 변수의 값을 users라는 변수를 사용해 넘겨주고 싶다, 즉 작성한 사용자 정보를 템플릿 파일에게 users라는 이름으로 넘겨주고 싶다는 의미이다. 이렇게 하면 컨트롤은 완성이다.
이제는 템플릿 파일에서 넘겨받은 users 정보를 화면에 표시해 줘야 한다. 그 때 사용하는 것이 ejs 태그이다. 그 전에 한 가지 알고 가자.
forEach문 : 배열의 처음부터 끝까지 순회하려고 할 때 사용
ex)users.forEach( user =>{ ex) user.name을 화면에 표시 ex) user.email을 화면에 표시 })users라는 배열을 받아서 처음부터 끝까지 순회해 달라는 의미이다. users안의 각각의 자료를 user라는 단수형으로 받게 되고, 처리해야 할 명령은 {} 로 묶게 된다.
우리가 작성할 코드에도 forEach문을 사용할 것인데, 템플릿 안에서 html 태그 중간에 js가 들어간다. 원래 웹문서라면 여기를 script 태그로 묶어주고 나서 그 안에 js를 작성하겠지만, ejs 파일에서는 그렇게 하지 않고 ejs 태그를 이용해서 작성을 하게 된다.

앞서 작성한 ejs 파일 안에 해당 코드를 작성하고 웹 브라우저를 실행해보자.

다음과 같이 잘 나오는 것을 확인할 수 있다.
여기서 이름 뿐만 아니라 email 주소도 표시하고 싶다면 다음과 같이 작성하면 된다.

자바 스크립트 코드를 삽입 할 때는 <% %> 태그를 쓰고, 변수 이름을 삽입 할 때는 <%= %> 이 태그를 쓴다는 것만 잊지 않고 기억해두자.
뷰 엔진을 사용할 때 정적인 파일들을 어떻게 어느 위치에 가져다 놓고 링크해서 사용할 것인지 생각해야 한다. 동적인 컨텐츠가 필요한 문서는 ejs 파일로 넣어주면 되는데 그 문서에 연결된 css 파일이나 js파일, 이미지 이런 것들은 한번 서버에 올리면 사용자 요청에 따라 값이 변하지는 않지 않은가. 이러한 파일들을 정적인 파일이라고 하는데 이는 따로 폴더에 모아 놓고 사용하게 된다.
폴더 이름은 정하기 나름이지만, 대부분 public이라는 폴더를 사용한다.

그리고 이 public 폴더에 정적인 파일들이 다 있다고 app.js에서 지정해주면 된다.

이런 형식으로 public 폴더를 만들고 정적인 파일들을 저장하면 된다.

이렇게 public 폴더를 만들었다면, 애플리케이션한테도 그 역할을 알려 줘야 한다.
뷰 엔진을 설정한 코드 밑에 다음 코드를 작성하자.
app.use(express.static("./public"));
이렇게 지정을 하면 이제부터 템플릿 파일에서 사용하는 정적인 파일들을 public 폴더를 뒤져서 꺼내 가게 된다.
템플릿 파일을 만들고 난 후에는 정적인 파일들의 위치까지도 같이 지정을 해 주는 것이 좋다.
우리가 배운 EJS를 사용해서 연락처 관리 앱의 화면을 한번 구성해보자.

여기서 주의할 것은 [연락처 추가]를 눌렀을 경우에 바로 POST 요청이 발생하는게 아니란 것이다. 연락처 추가를 위한 창을 먼저 보여주는데, 이 때 화면을 브라우저 창에 가져와서 보여주는 것이기 때문에 GET 요청이 발생한다. 그 후 내용 입력 후 [저장하기]를 눌러야 POST 요청이 발생한다.
이전에 했던 코드는 thunder client에서 바로 POST 요청을 보냈었는데, 이 때 우리는 정보 처리를 위해 JSON 형식을 사용했었다. 하지만 실제 앱 사용시에 사용자가 JSON 형식을 사용하지는 않고 from을 이용한다. 이 form을 브라우저 화면에 불러와 주는 GET 요청이 한 번 더 필요하다.
연필 아이콘을 눌러 업데이트 할 때도 마찬가지이다.
이제 실제 ejs 파일을 만들러 가보자.

전체 연락처를 표시할 화면은 index.ejs로 저장 할 것이다. 컨트롤러에서 getAllcontacts라는 함수를 실행하면 어떠한 변수 값을 넘겨줄 텐데, 그 값을 가져와 index.ejs에 그려주면 된다.
다음 html 코드를 이용할 것이다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>연락처 관리하기</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
<!-- Header -->
<header class="border-shadow">
<div class="container ">
<nav>
<a href="/"><i class="fa-solid fa-address-book"></i> My Contacts</a>
</nav>
</div>
</header>
<!-- /Header -->
<!-- Main -->
<main id="site-main">
<div class="button-box">
<a href="#" class="btn btn-light"><i class="fa-solid fa-user-plus"></i>연락처 추가</a>
</div>
<table class="table">
<thead>
<tr>
<th>이름</th>
<th>메일 주소</th>
<th>전화번호</th>
<th> </th>
</tr>
</thead>
<tbody>
<tr>
<td>Username</td>
<td>example@gmail.com</td>
<td>123456</td>
<td>
<a href="#" class="btn update" title="수정">
<i class="fas fa-pencil-alt"></i>
</a>
<a href="#" class="btn delete" title="삭제">
<i class="fas fa-times"></i>
</a>
</td>
</tr>
</tbody>
</table>
</main>
<!-- /Main -->
</body>
</html>
브라우저에서 살펴보면 이런 형태이다.

여기서 우리가 할 일은 이름, 메일, 주소, 전화번호 들어가는 부분을 가져온 값으로 채워 주면 된다. ejs 태그를 이용해서 적절하게 변형시키자.
const getAllContacts = asyncHandeler(async(req,res)=>{
const contacts = await Contact.find();
res.render("index", {contacts: contacts});
});
컨트롤러 코드에서 contact를 index.ejs 템플릿에게 넘겨주자. db에서 가져온 값들을 index.ejs로 넘겨주라는 뜻이다.
index.ejs에서는 사용자가 몇 명인지 모르기 때문에 foreach문을 사용해서 처음부터 끝까지 순회해서 그 안의 값을 꺼내와서 보여줘야 한다.
<tbody>
<% contacts.forEach (contact => { %>
<tr>
<td><%= contact.name %></td>
<td><%= contact.email %></td>
<td><%= contact.phone %></td>
<td>
<a href="#" class="btn update" title="수정">
<i class="fas fa-pencil-alt"></i>
</a>
<a href="#" class="btn delete" title="삭제">
<i class="fas fa-times"></i>
</a>
</td>
</tr>
<% }) %>
</tbody>
다음과 같이 tr 태그 부분을 전체적으로 반복 해달라고 작성했다. 그리고 가져온 contact 자료로 name, email, phone을 변경한다. 해당 부분을 수정한 코드로 새롭게 index.ejs 파일을 만들자.
이를 브라우저에서 실행해보면?

위 정보는 내 데이터베이스에 있는 정보를 가져온 것이다.
이제 애플리케이션에서 필요로 하는 나머지 두 개의 ejs 파일을 만들어 볼 것이다. 다음 html 파일들을 이용했다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>연락처 관리하기</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
<!-- Header -->
<header class="border-shadow">
<div class="container ">
<nav>
<a href="/"><i class="fa-solid fa-address-book"></i> My Contacts</a>
</nav>
</div>
</header>
<!-- /Header -->
<!-- Main -->
<main id="site-main">
<div class="button-box">
<a href="#" class="btn btn-light"><i class="fa-solid fa-list"></i>연락처 목록</a>
</div>
</div>
<h2>연락처 추가</h2>
<p>연락처 정보를 추가합니다.</p>
<form method="POST" id="add-user">
<div class="user-info">
<div class="col-12">
<label for="name" class="col-form-label">이름(Full Name)</label>
<div>
<input type="text" class="form-control" name="name" id="name" placeholder="홍길동">
</div>
</div>
<div class="col-12">
<label for="email" class="col-form-label">메일 주소(E-mail)</label>
<div>
<input type="text" class="form-control" name="email" id="email" placeholder="hong@abc.def">
</div>
</div>
<div class="col-12">
<label for="phone" class="col-form-label">전화번호(Mobile)</label>
<div>
<input type="text" class="form-control" name="phone" id="phone" placeholder="123-4567-8901">
</div>
</div>
<button type="submit">저장하기</button>
</div>
</form>
</main>
<!-- /Main -->
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>연락처 관리하기</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
<!-- Header -->
<header class="border-shadow">
<div class="container ">
<nav>
<a href="/"><i class="fa-solid fa-address-book"></i> My Contacts</a>
</nav>
</div>
</header>
<!-- /Header -->
<!-- Main -->
<main id="site-main">
<div class="button-box">
<a href="#" class="btn btn-light"><i class="fa-solid fa-list"></i>연락처 목록</a>
</div>
<h2>연락처 수정</h2>
<p>연락처 정보를 수정합니다.</p>
<form method="POST" id="add-user">
<div class="user-info">
<div class="col-12">
<label for="name" class="col-form-label">이름(Full Name)</label>
<div>
<input type="text" class="form-control" name="name" id="name" value="도레미">
</div>
</div>
<div class="col-12">
<label for="email" class="col-form-label">메일 주소(E-mail)</label>
<div>
<input type="text" class="form-control" name="email" id="email" value="doremi@abc.def">
</div>
</div>
<div class="col-12">
<label for="phone" class="col-form-label">전화번호(Mobile)</label>
<div>
<input type="text" class="form-control" name="phone" id="phone" value="456-7890-1234">
</div>
</div>
<button type="submit">수정하기</button>
</div>
</form>
</main>
<!-- /Main -->
</body>
</html>
해당 html 파일들을 복붙해서 add.ejs, update.ejs 파일을 만들자.
하나만 더 살펴보고 가자. 우리가 만든 index.ejs, add.ejs, update.ejs 파일을 보면 실제 내용이 바뀌는 부분은 main 이란 부분이다. 이 main을 제외하면 세 ejs파일의 나머지 코드가 동일하다.
이렇게 웹사이트 혹은 애플리케이션에서 웹문서 안에서 똑같이 반복되는 것들은 따로 모듈로 만들어서 불러다 사용할 수 있다.
코드의 main 이전 부분, <!-- Main -->이라고 주석이 붙어있는 부분의 윗 부분을 잘라내서 모듈로 만들어 보겠다. 모듈로 어디에 만드느냐? views 폴더 안에 include라는 새 폴더를 만들어서 거기에 저장해 보겠다.
헤더 부분에 해당하기 때문에 이름은 _header.ejs가 된다. 다른 header.ejs가 있을 수 있기 때문에 다른 파일과 구분하기 위해 모듈로 동작하는 ejs 파일은 이름 앞에 _을 붙여준다. _을 한 개 붙이기도, 두 개 붙이기도 한다.
_header.ejs
이 파일에서 CSS 파일의 경로를 다음과 같이 수정해야 한다.
매우 중요하다. 이 경로을 깜빡하고 수정하지 않았다가 뒤의 10일차에서 매우 쓴 맛을 보게 된다.
<link rel="stylesheet" href="/css/style.css">
그러면 _header.ejs 파일은 이렇게 된다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>연락처 관리하기</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
<!-- Header -->
<header class="border-shadow">
<div class="container ">
<nav>
<a href="/"><i class="fa-solid fa-address-book"></i> My Contacts</a>
</nav>
</div>
</header>
<!-- /Header -->
그 후 index.ejs로 돌아와서 방금 저장한 _header.ejs를 모듈로 여기에 삽입을 해달라고 표시할 것이다. 이 때도 ejs 태그를 사용하게 된다.
<%- include() %>
괄호 안의 파일을 이 안에 넣어달라는 뜻이다.
<%- include("./include/_header.ejs") %>
이 코드를 index.ejs에 추가하자. 확장자(.ejs)는 생략할 수 있다. 이렇게 하면 아까 만들어 놓은 코드가 들어가게 된다.
같은 방법으로 main을 제외한 나머지 부분도 잘라내서 footer로 저장해보겠다.
</body>
</html>
이 부분이다.
include 폴더 안에 _footer.ejs라는 새 파일을 만들자.
_footer.ejs
</body>
</html>
여기서는 짧지만 실제 웹 페이지, 애플리케이션에서의 푸터 코드는 들어가는 내용도 많고 꽤 길다.
원래 푸터가 있던 자리로 돌아와서 아까 헤더 넣었을때와 같은 방식으로 코드를 사용해서 지정해주면 된다.
<%- include("./include/_footer.ejs") %>
이렇게 반복되면서도 변경되지 않는 부분은 이런 식으로 모듈로 해 놓으면 코드가 한결 간단해진다. add.ejs와 update.ejs도 같은 방식으로 수정해주자.
이렇게 작성한 코드들이 제대로 돌아가는지 확인해보자.

아까와 똑같이 나오고, 웹 개발자 도구에서 코드를 보면

헤더 부분과 푸터 부분은 모듈을 끼워 넣은 것이었는데 헤더와 푸터 모듈로 만든 코드가 그대로 들어가 있는것도 확인할 수 있다.
이렇게 템플릿 파일을 만들 때 반복되는 부분은 모듈로 활용할 수 있다.
실제로 ejs에서는 레이아웃을 이용하는 방법도 많이 쓴다. 이 레이아웃을 활용해서 템플릿을 만드는 것은 나중에 해 보겠다.