일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 프로젝트
- python
- 독립영화플랫폼
- join()
- til
- 타사인증
- ART_Cinema
- JavaScript
- 북마크만들기
- Bookmark
- MYSQL
- mongodb
- 장고 프로젝트 순서
- 개발
- 장고 개발 순서
- Exercism
- Django
- MyPick31
- passport.js
- Blog
- 장고 프로젝트
- 파이썬 웹프로그래밍 장고
- Django Blog
- 예술영화추천
- 알고리즘
- 북마크앱
- Algorithm
- Node.js
- 자바스크립트
- 장고
- Today
- Total
Juni_Dev_log
(node.js) [Part.8] 뷰 템플릿 적용하기 - ejs 뷰 템플릿 사용하기 본문
최근에 만들어진 새로운 언어들은 대부분 MVC 패턴(Model-View-Controller 패턴)을 사용한다.
즉, 눈에 보이는 부분은 View / 뷰로 표현되는 데이터를 제공하는 것은 Model / 처리되는 과정을 담당하는 것은 Controller 로 구분하여 구성하면 구조를 더 쉽게 이해할 수 있다.
노드와 익스프레스도 지금까지 만든 각각의 기능을 뷰, 모델, 컨트롤러로 나눌 수 있다.
사용자 요청을 처리하는 라우팅 함수 -> 컨트롤러(Controller)
데이터베이스에 데이터를 저장하거나 조회하는 함수 -> 모델(Model)
사용자에게 결과를 보여 주기 위해서 만든 파일 -> 뷰(View)
그중에서, 뷰에 해당하는 부분을 살펴보면, 지금까지 사용자에게 결과를 응답으로 보낼 때 자바스크립트 코드를 직접 입력하는 방식을 사용했다.
그런데, 이 방식은 각각의 요청을 처리하는 함수마다 응답 코드를 문자열로 넣어줘야하므로 웹 문서를 코드 안에 입력하는 과정에서 오탈자가 생기기 쉽다. 따라서, 웹 문서의 기본적인 형태를 별도의 파일로 미리 만들어두고 사용하는 것이 좋다.
이제부터는 응답 웹 문서의 기본 형태를 뷰 템플릿 파일에 만들어 두고 사용한다.
뷰 템플릿을 사용하면, 웹 문서의 기본 형태는 뷰 템플릿으로 만들고 데이터베이스에서 조회한 데이터를 이 템플릿 안의 적당한 위치에 넣어 웹 문서를 만들게 된다.
이렇게 뷰 템플릿을 사용해 결과 웹 문서를 자동으로 생성한 후 응답을 보내는 역할을 하는 것이 뷰 엔진(View-Engine)이다.
- 웹 브라우저에서 보내온 요청은 웹 서버인 익스프레스에서 컨트롤러로 보낸다.
- 익스프레스에서는 특정 패스로 들어온 요청을 라우팅 함수에서 처리하므로 라우팅 함수를 컨트롤러라고 한다.
- 컨트롤러 안에서는 사용자 요청을 처리하기 위해 mongoose 스키마와 모델 객체를 이용해 데이터베이스를 조회하거나 데이터베이스에 저장한다.
- 이런 역할을 하는 것이 모델이며, 모델에서 처리한 결과는 뷰 엔진으로 전달된다.
뷰 엔진은 뷰 템플릿 파일에서 웹 문서의 기본 형태를 읽어 들여 사용자가 보게 될 최종 웹 문서를 만든 후 클라이언트에 응답을 보낸다.
여러가지 방식으로 뷰 템플릿을 만들 수 있는데, 익스프레스에서는 ejs, pug 등 여러가지 뷰 엔진을 지원한다.
그중에서도 ejs 템플릿을 사용해보도록 한다.
ejs 를 사용하면, 필요한 부분에만 변수를 삽입하거나 중간중간 자바스크립트 코드를 넣을수도 있다.
따라서 웹 페이지에 익숙한 웹 개발자에게 아주 쉬운 형식이다.
뷰 템플릿으로 로그인 웹 문서 만들기
DefaultExample을 복사해서 ViewExample 프로젝트를 만든다.
이 프로젝트 안에는 사용자 정보를 처리하는 함수들이 포함되어 있는데, 그중에서 로그인 기능을 처리한 후 응답하는 과정에 뷰 템플릿을 적용해보도록 한다.
먼저 app.js 를 열고 뷰엔진을 ejs로 지정한다.
#app.js
1
2
3
4
5
6
7
|
...
// 뷰 엔진 설정
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
console.log('뷰 엔진이 ejs로 설정되었습니다.');
...
|
cs |
- app 객체의 set 메소드는 속성을 설정하는 역할을 한다.
- views 속성 값으로 views 폴더를 지정한다. (views 폴더를 만들어야한다.)
- view engine 속성 값으로 ejs를 설정한다.
이제 login 함수를 살펴보자.
#user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
...
var login = function(req, res) {
console.log('user(user2.js) 모듈 안에 있는 login 호출됨.');
// 요청 파라미터 확인
var paramId = req.body.id || req.query.id;
var paramPassword = req.body.password || req.query.password;
console.log('요청 파라미터 : ' + paramId + ', ' + paramPassword);
// 데이터베이스 객체 참조
var database = req.app.get('database');
// 데이터베이스 객체가 초기화된 경우, authUser 함수 호출하여 사용자 인증
if (database.db) {
authUser(database, paramId, paramPassword, function(err, docs) {
// 에러 발생 시, 클라이언트로 에러 전송
if (err) {
console.error('사용자 로그인 중 에러 발생 : ' + err.stack);
res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
res.write('<h2>사용자 로그인 중 에러 발생</h2>');
res.write('<p>' + err.stack + '</p>');
res.end();
return;
}
// 조회된 레코드가 있으면 성공 응답 전송
if (docs) {
console.dir(docs);
// 조회 결과에서 사용자 이름 확인
var username = docs[0].name;
res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
res.write('<h1>로그인 성공</h1>');
res.write('<div><p>사용자 아이디 : ' + paramId + '</p></div>');
res.write('<div><p>사용자 이름 : ' + username + '</p></div>');
res.write("<br><br><a href='/public/login.html'>다시 로그인하기</a>");
res.end();
}
...
|
cs |
사용자 인증이 성공했을 때 클라이언트에 응답 웹 문서를 보내기 위해서 여러 가지 태그를 입력한 것을 볼 수 있다.
사용자의 아이디와 이름이 변수에 들어 있으므로 + 기호로 다른 문자열과 함께 붙인 다음 응답 객체의 write() 메소드를 호출하여 응답을 보낸다.
이렇게 클라이언트에 응답을 보내기 위해 입력한 코드 중에서 HTML 태그 부분만 새로운 뷰 템플릿 파일로 만든다.
[views] 폴더에 login_success.ejs 파일을 만들고 코드를 입력한다.
#login_success.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 성공 페이지</title>
</head>
<body>
<h1>로그인 성공</h1>
<div><p>사용자 아이디 : <% = userid %></p></div>
<div><p>사용자 이름 : <% = username %></p></div>
<br><br><a href='/public/login.html'>다시 로그인하기</a>
</body>
</html>
|
cs |
중간에 <% %> 기호가 들어있는데, 이 기호는 자바스크립트 코드를 넣어주는 코드이다.
이 기호 중 앞에 있는 기호에 = 이 붙으면, 바로 뒤에 변수를 넣을 수 있으며, 그 변수의 값을 웹 문서에 출력할 수 있다.
뷰 엔진은 이 템플릿 파일을 읽어 들이고 userid와 username 변수의 값으로 해당 부분의 값을 대체한 후 그 결과를 만들어낸다. 이제 이 템플릿 파일응 이용해 응답 웹 문서를 만든 후, 클라이언트에게 응답을 보내도록 user.js 파일의 코드를 수정한다.
#user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
...
// 조회된 레코드가 있으면 성공 응답 전송
if (docs) {
console.dir(docs);
// 조회 결과에서 사용자 이름 확인
var username = docs[0].name;
// 뷰 템플릿을 사용하여 렌더링 후 전송
var context = {userid:paramId, username:username};
req.app.render('login_success', context, function(err, html){
if(err){
console.error('뷰 렌더링 중 오류 발생 : ' + err.stack);
res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
res.write('<h2>뷰 렌더링 중 오류 발생</h2>');
res.write('<p>'+ err.stack +'</p>');
res.end();
return;
}
console.log('rendered : ' + html);
res.end(html);
});
}
...
|
cs |
익스프레스 서버 객체인 app에는 render() 메소드가 들어있다.
이 메소드를 호출하면 뷰 엔진이 템플릿 파일을 읽어 들인 후, 파라미터로 전달한 context 객체의 속성으로 들어 있는 값들을 적용하고 그 결과를 콜백함수로 돌려준다.
콜백 함수로 전달되는 html 파라미터에는 사용자가 보게 될 최종 웹 문서 코드가 들어가있다.
따라서, res.end() 메소드를 호출하면서 이 html 객체를 파라미터로 전달하면 클라이언트로 응답을 보내게 된다.
- 뷰 엔진은 뷰 템플릿 파일을 로딩한 후, context 객체의 속성들을 사용해 결과 웹 문서를 만들어 내므로 이 context 객체에 userid 와 username 속성을 추가한다.
- context 객체를 전달받는 템플릿 파일에서는 <% = userid %> <% = username %> 코드를 추가하여 변수에 들어있는 문자열을 출력한다.
이제 ejs 모듈을 설치한다.
%npm install ejs --save
app.js 파일을 실행하고 login.html 파일을 연다. 작성하고 로그인하면 해당 웹 문서가 나온다.
★ ejs 를 설치했는데, 계속 ejs 모듈을 찾을 수 없다는 오류가 나왔을 때
app.engine('ejs', require('ejs').__express)
코드를 app.js 뷰 엔진 설정에 같이 설정해주면, "ejs 모듈의 참조 경로를 지정할 수 있다."
로그인 성공 페이지는 이전에 보았던 것과 같지만, 만들어진 과정은 다르다.
즉, 이전에는 코드에 태그를 직접 입력한 웹 문서가 응답으로 보낸 것이었지만, 지금은 login_success.ejs 파일에 입력한 태그들이 표시된 것이다.
서버의 콘솔 창을 보면 render() 메소드를 호출했을 때, 뷰 템플릿으로부터 만들어진 결과 웹 문서의 코드를 확인할 수 있다.
뷰 템플릿으로 사용자 리스트 웹 문서 만들기
사용자 리스트 요청에 대한 응답으로 보여 줄 뷰 템플릿을 만들어보자.
user.js 파일 안에 들어있는 listuser 함수의 내용은 다음과 같다.
user.js 안에 있는 listuser 함수가 들어있는 부분을 참고하자.
#user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
var listuser = function(req, res) {
console.log('user(user2.js) 모듈 안에 있는 listuser 호출됨.');
// 데이터베이스 객체 참조
var database = req.app.get('database');
// 데이터베이스 객체가 초기화된 경우, 모델 객체의 findAll 메소드 호출
if (database.db) {
// 1. 모든 사용자 검색
database.UserModel.findAll(function(err, results) {
// 에러 발생 시, 클라이언트로 에러 전송
if (err) {
console.error('사용자 리스트 조회 중 에러 발생 : ' + err.stack);
res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
res.write('<h2>사용자 리스트 조회 중 에러 발생</h2>');
res.write('<p>' + err.stack + '</p>');
res.end();
return;
}
if (results) {
console.dir(results);
res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
res.write('<h2>사용자 리스트</h2>');
res.write('<div><ul>');
for (var i = 0; i < results.length; i++) {
var curId = results[i]._doc.id;
var curName = results[i]._doc.name;
res.write(' <li>#' + i + ' : ' + curId + ', ' + curName + '</li>');
}
res.write('</ul></div>');
res.end();
}
...
|
cs |
데이터베이스에 조회한 사용자 리스트는 results 라는 배열 객체에 들어있다.
따라서 forEach 또는 for 문을 사용해 배열 요소를 확인할 수 있는데 여기에서는 for문을 사용하고 있다.
웹 문서를 만들어 내는 부분을 삭제하고 listuser.ejs 파일을 만든다.
#listuser.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>사용자 리스트 페이지</title>
</head>
<body>
<h2>사용자 리스트</h2>
<div>
<ul>
<% for (var i=0; i <results.length; i++){
var curId = results[i]._doc.id;
var curName = results[i]._doc.name;%>
<li>#<%= i %>- 아이디 : <%= curId %>, 이름 : <%= curName %></li>
<% } %>
</ul>
</div>
<br><br><a href="public/listuser.html">다시 요청하기</a>
</body>
</html>
|
cs |
- <% %> 코드 사이에 자바스크립트 코드를 넣을 수 있다.
- <ul> 태그 사이로 for문 전체를 옮긴후 사용자 아이디와 사용자 이름을 출력해야할 부분을 구분하여 입력한다.
# user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
...
if (results) {
console.dir(results);
res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
// 뷰 템플릿을 이용하여 렌더링한 후 전송
var context = {results:results};
req.app.render('listuser',context,function(err,html){
if(err){
throw err;
}
res.end(html);
});
}
...
|
cs |
- 뷰 템플릿에 적용할 context 객체에는 사용자 리스트가 들어 있는 배열 객체를 results 속성 이름 그대로 넣어준다.
뷰 템플릿으로 사용자 추가 웹 문서 만들기
adduser.html 을 뷰 템플릿으로 만들어보자.
이번에는 여러 개의 뷰 템플릿 파일에서 공통으로 사용되는 일부 내용을 또 다른 뷰 템플릿 파일로 만들었다가 삽입해서 아용하는 방법을 알아보자.
웹 문서에 들어가는 태그 중 <head>태그는 대부분의 웹 문서에서 공통으로 사용되므로 별도의 ejs 파일로 만든 후 listuser.ejs 파일에서 읽어들여 함께 보여준다.
#user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
...
// 결과 객체 있으면 성공 응답 전송
if (addedUser) {
console.dir(addedUser);
res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
res.write('<h2>사용자 추가 성공</h2>');
res.end();
} else { // 결과 객체가 없으면 실패 응답 전송
res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
res.write('<h2>사용자 추가 실패</h2>');
res.end();
}
...
|
cs |
이것을 adduser.ejs 템플릿 파일로 만들어보자.
#adduser.ejs
1
2
3
4
5
6
7
8
9
10
11
12
|
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>헤드 부분 - ejs에서 inClude됨</title>
</head>
<body>
<h2><%= title %></h2>
<br><br><a href="/public/login.html">로그인으로 - ejs에서 inClude됨</a>
</body>
</html>
|
cs |
<head>태그 부분은 head.ejs 부분을 따로 만든다.
#head.ejs
1
2
3
4
|
<head>
<meta charset="utf-8">
<title>헤드 부분 - ejs에서 inClude됨</title>
</head>
|
cs |
<a> 태그도 여러번 쓰이기에, footer.ejs를 만들어본다.
#footer.ejs
1
|
<br><br><a href="/public/login.html">로그인으로 - ejs에서 inClude됨</a>
|
cs |
이제 adduser.ejs 파일을 다음과 같이 수정하여 분리한 파일을 삽입한다.
#adduser.ejs
1
2
3
4
5
6
7
8
9
|
<!DOCTYPE html>
<html>
<% include ./head.ejs %>
<body>
<h2><%= title %></h2>
<% include ./footer.ejs %>
</body>
</html>
|
cs |
였지만... ejs 가 버전 업그레이드가 되면서 경로 설정하는 것이 바뀌었다.
# (최신버전) adduser.ejs
1
2
3
4
5
6
7
8
9
10
11
|
<!DOCTYPE html>
<html>
<%-include('head.ejs')%>
<body>
<h2><%=title %></h2>
<%-include('footer.ejs')%>
</body>
</html>
|
cs |
* Includes
Includes either have to be an absolute path, or, if not, are assumed as relative to the template with the include call. For example if you are including ./views/user/show.ejs from ./views/users.ejs you would use <%- include('user/show') %>.
You must specify the filename option for the template with the include call unless you are using renderFile().
You'll likely want to use the raw output tag (<%-) with your include to avoid double-escaping the HTML output.
<ul> <% users.forEach(function(user){ %> <%- include('user/show', {user: user}) %> <% }); %></ul>
Includes are inserted at runtime, so you can use variables for the path in the include call (for example <%- include(somePath) %>).
Variables in your top-level data object are available to all your includes, but local variables need to be passed down.
NOTE: Include preprocessor directives (<% include user/show %>) are not supported in v3.0+.
www.npmjs.com/package/ejs/v/3.1.5
- <% %> 사이에 include 키워드를 사용하면, 별도로 분리되어 있는 ejs 파일을 삽입할 수 있다. 이 때 파일 이름은 상대경로로 지정한다.
이제 뷰 템플릿으로 결과 웹 문서를 만들 수 있도록 user.ejs 파일을 열어 수정한다.
#user.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
var adduser = function(req, res) {
console.log('user(user2.js) 모듈 안에 있는 adduser 호출됨.');
var paramId = req.body.id || req.query.id;
var paramPassword = req.body.password || req.query.password;
var paramName = req.body.name || req.query.name;
console.log('요청 파라미터 : ' + paramId + ', ' + paramPassword + ', ' + paramName);
// 데이터베이스 객체 참조
var database = req.app.get('database');
// 데이터베이스 객체가 초기화된 경우, addUser 함수 호출하여 사용자 추가
if (database.db) {
addUser(database, paramId, paramPassword, paramName, function(err, addedUser) {
// 동일한 id로 추가하려는 경우 에러 발생 - 클라이언트로 에러 전송
if (err) {
console.error('사용자 추가 중 에러 발생 : ' + err.stack);
res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
res.write('<h2>사용자 추가 중 에러 발생</h2>');
res.write('<p>' + err.stack + '</p>');
res.end();
return;
}
// 결과 객체 있으면 성공 응답 전송
if (addedUser) {
console.dir(addedUser);
res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
// 뷰 템플릿을 이용하여 렌더링한 후 전송
var context = {title:'사용자 추가 성공'};
req.app.render('adduser',context,function(err,html){
if(err){
console.error('뷰 렌더링 중 오류 발생 : ' + err.stack);
res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
res.write('<h2>뷰 렌더링 중 오류 발생</h2>');
res.write('<p>'+ err.stack +'</p>');
res.end();
return;
}
console.log("rendered : " + html);
res.end(html);
})
} else { // 결과 객체가 없으면 실패 응답 전송
res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
res.write('<h2>사용자 추가 실패</h2>');
res.end();
}
});
} else { // 데이터베이스 객체가 초기화되지 않은 경우 실패 응답 전송
res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
res.write('<h2>데이터베이스 연결 실패</h2>');
res.end();
}
};
|
cs |
이제 뷰 템플릿 파일을 만들고 ejs 뷰 엔진으로 변환한 후, 응답으로 보내는 방법에 대해서 알아보았다.
'Theorem (정리) > node.js' 카테고리의 다른 글
(node.js) [Part.9] 패스포트로 사용자 인증하기 - 패스포트로 로그인하기 (0) | 2021.02.01 |
---|---|
(node.js) [Part.8] 뷰 템플릿 적용하기 - pug 뷰 템플릿 사용하기 (0) | 2021.01.31 |
(node.js) [Part.7] 익스프레스 프로젝트를 모듈화하기 - UI 라이브러리로 웹 문서를 예쁘게 꾸미기 (semantic UI) (0) | 2021.01.28 |
(node.js) [Part.7] 익스프레스 프로젝트를 모듈화하기 - 설정 파일 만들기 (0) | 2021.01.25 |
(node.js) [Part.7] 익스프레스 프로젝트를 모듈화하기 - 사용자 정보 관련 기능을 모듈화하기 (0) | 2021.01.23 |